Docker 容器化部署实战
"在我电脑上能跑啊。"
这句话大概是程序员最常说的遗言了。Docker 就是为了解决这个问题而生的。
以下是我部署个人项目时积累的一些实战经验,不是从文档里抄的,是真踩过坑的。
一个能用的 Dockerfile
先看一个 Node.js 项目的典型 Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]看着简单,但里面有几个容易忽略的细节。
先复制 package.json 再复制代码
很多教程把 COPY . . 写在前面,一步到位。
问题是:你改了一行业务代码,Docker 就要重新 npm install。因为文件变了,缓存失效。
先复制 package.json,装完依赖再复制代码,这样只要依赖不变,npm install 那层就走缓存。构建速度能快好几倍。
用 npm ci 而不是 npm install
npm ci 严格按照 package-lock.json 安装,速度更快,结果更可预测。生产环境用这个更靠谱。
用 alpine 镜像
node:18 的镜像有 900MB+,node:18-alpine 只有 100MB 出头。体积差了近 10 倍。
除非你的项目依赖一些需要 glibc 的原生模块,否则都推荐用 alpine。
Docker Compose 编排
单个容器跑个 Demo 可以,正经项目一般是 App + 数据库 + 缓存,这时候需要 Compose:
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
depends_on:
- db
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
db:
image: postgres:15-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:几个注意事项:
depends_on只保证启动顺序,不保证数据库"准备好了"。如果 App 启动时数据库还没初始化完,会报连接错误。需要在应用层做重试- Volume 要声明,否则容器重建后数据就没了
- 数据库密码用环境变量传入,别硬编码在配置里
我踩过的坑
.dockerignore 忘了写
第一次构建,镜像整整 2GB。后来发现把 node_modules 和 .git 都打进去了。
加个 .dockerignore:
node_modules
.git
.env
*.log镜像立刻缩到 200MB。
时区问题
容器默认是 UTC 时区,日志里的时间和本地差八小时。排查问题的时候差点疯了。
解决方案是在 Dockerfile 里加一行:
RUN apk add --no-cache tzdata
ENV TZ=Asia/Shanghai健康检查没配
容器"启动了"不代表"能用了"。App 可能还在初始化数据库连接。
Compose 里可以加 healthcheck:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3部署流程
我现在的部署流程:
- 本地开发用 Docker Compose,保证环境一致
- 推代码到 GitHub
- 服务器上
git pull && docker compose up -d --build - 完事
虽然没有 CI/CD 那么自动化,但对个人项目来说够用了。等项目大了再上 GitHub Actions。
最后
Docker 入门不难,难的是把各种细节处理好。
上面这些坑我每个至少浪费了半小时,写下来希望别人能少走弯路。
