DevOps Engineering
Docker 实践指南:从入门到生产部署
容器化不是魔法,而是一套工程实践。分享我 5 年来使用 Docker 的经验教训,包括镜像优化、多阶段构建、安全加固和 CI/CD 集成。
Ioodu · ·
Updated: Feb 13, 2026 · 24 min read
#Docker
#Container
#DevOps
#Deployment
#Infrastructure
为什么需要 Docker
2020 年,我经历了一次典型的「在我机器上可以运行」事故:
代码在本地测试通过,部署到生产环境后崩溃。排查 6 小时后发现问题:开发环境用 Node.js 14,生产环境是 Node.js 12,一个语法特性不支持。
这种环境不一致问题,Docker 可以彻底解决。
Docker 核心概念
镜像(Image)
定义:只读模板,包含运行应用所需的一切(代码、运行时、库、环境变量)。
类比:类(Class)
容器(Container)
定义:镜像的运行实例,是隔离的进程。
类比:对象(Object)
Dockerfile
定义:构建镜像的脚本,包含一系列指令。
# 基础镜像
FROM node:18-alpine
# 工作目录
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
镜像优化:从 1GB 到 100MB
问题:镜像过大
初始 Dockerfile:
FROM ubuntu:latest
RUN apt-get update && apt-get install -y nodejs npm
COPY . /app
WORKDIR /app
RUN npm install
CMD ["node", "server.js"]
问题:
- 使用完整 Ubuntu(1GB+)
- 包含不必要的包
- 层数过多
结果:镜像 1.2GB,构建 5 分钟
优化 1:使用精简基础镜像
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]
改进:
- Alpine Linux:只有 5MB
- 只安装生产依赖
结果:镜像 180MB,构建 1 分钟
优化 2:多阶段构建
# 构建阶段
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# 生产阶段
FROM node:18-alpine
WORKDIR /app
# 只复制生产依赖和构建产物
COPY package*.json ./
RUN npm ci --only=production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]
改进:
- 构建工具和 devDependencies 不进入最终镜像
- 源码不进入最终镜像(只用构建产物)
结果:镜像 120MB
优化 3:.dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.vscode
.idea
tests
*.test.js
*.spec.js
效果:减少构建上下文,加快构建速度
优化 4:层缓存优化
Docker 使用层缓存,合理利用可以加速构建:
# ❌ 不好的做法:每次代码变更都重新安装依赖
COPY . .
RUN npm ci
# ✅ 好的做法:依赖文件不变时不重新安装
COPY package*.json ./
RUN npm ci --only=production
COPY . .
原理:
package*.json不常变更 → 缓存命中- 代码常变更 → 在依赖层之后 COPY
优化 5:安全加固
FROM node:18-alpine
# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
# 设置正确的权限
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production
COPY --chown=nextjs:nodejs . .
# 切换到非 root 用户
USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]
安全要点:
- 不使用 root 运行应用
- 最小权限原则
- 使用官方镜像
- 定期更新基础镜像
Docker Compose:多容器管理
开发环境配置
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- DATABASE_URL=postgres://user:pass@db:5432/myapp
volumes:
- .:/app
- /app/node_modules
depends_on:
- db
- redis
command: npm run dev
db:
image: postgres:14-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
ports:
- "6379:6379"
# 开发工具
pgadmin:
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: admin
ports:
- "5050:80"
depends_on:
- db
volumes:
postgres_data:
生产环境配置
# docker-compose.prod.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile.prod
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
deploy:
replicas: 2
resources:
limits:
cpus: '0.5'
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- app
restart: unless-stopped
db:
image: postgres:14-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
volumes:
postgres_data:
环境变量管理
# .env (不提交到 git)
DATABASE_URL=postgres://user:pass@localhost:5432/myapp
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-secret-key
# docker-compose.yml
services:
app:
env_file:
- .env
environment:
# 覆盖或添加变量
NODE_ENV: production
CI/CD 集成
GitHub Actions 工作流
# .github/workflows/docker.yml
name: Docker Build and Deploy
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: |
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
# 多阶段构建优化
target: production
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker system prune -f
多架构构建
- name: Build and push multi-arch
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ghcr.io/${{ github.repository }}:latest
生产环境最佳实践
1. 健康检查
# Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
// server.js
app.get('/health', (req, res) => {
// 检查数据库连接
// 检查关键依赖
res.status(200).json({ status: 'ok', timestamp: new Date().toISOString() });
});
2. 日志管理
# docker-compose.yml
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
或使用集中式日志:
services:
app:
logging:
driver: fluentd
options:
fluentd-address: localhost:24224
tag: docker.app
3. 监控
# docker-compose.monitoring.yml
version: '3.8'
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
cadvisor:
image: gcr.io/cadvisor/cadvisor
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
ports:
- "8080:8080"
volumes:
grafana_data:
4. 备份策略
#!/bin/bash
# backup.sh
# 数据库备份
docker exec postgres pg_dump -U user myapp > backup_$(date +%Y%m%d).sql
# 上传到 S3
aws s3 cp backup_$(date +%Y%m%d).sql s3://myapp-backups/
# 保留最近 7 天
docker exec postgres find /backups -name "backup_*.sql" -mtime +7 -delete
# 定时备份
cron:
image: mcuadros/ofelia:latest
command: daemon --docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
labels:
ofelia.job-run.backup.schedule: "0 2 * * *"
ofelia.job-run.backup.container: "postgres"
ofelia.job-run.backup.command: "/backup.sh"
安全加固
镜像安全扫描
# 使用 Trivy 扫描镜像
trivy image myapp:latest
# 集成到 CI
trivy image --exit-code 1 --severity HIGH,CRITICAL myapp:latest
运行时安全
# docker-compose.yml
services:
app:
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
read_only: true
tmpfs:
- /tmp
- /var/tmp
网络隔离
# docker-compose.yml
services:
app:
networks:
- frontend
- backend
db:
networks:
- backend
# 数据库不暴露到外部
networks:
frontend:
driver: bridge
backend:
driver: bridge
internal: true # 无外部访问
调试技巧
进入运行中的容器
# 交互式 shell
docker exec -it container_name sh
# 查看日志
docker logs -f container_name
# 查看资源使用
docker stats
调试构建
# 查看构建层
docker history myapp:latest
# 检查镜像内容
docker run --rm -it myapp:latest sh
# 导出文件系统
docker export container_name -o filesystem.tar
网络调试
# 查看网络
docker network ls
docker network inspect bridge
# 测试连通性
docker run --rm busybox ping db
常见陷阱
陷阱 1:在容器内保存数据
错误:
// 数据保存在容器内
fs.writeFileSync('/app/data.json', data);
问题:容器重启后数据丢失
解决:
services:
app:
volumes:
- app_data:/app/data
陷阱 2:忽视信号处理
问题:容器无法优雅关闭
解决:
// 正确处理 SIGTERM
process.on('SIGTERM', () => {
console.log('SIGTERM received, closing gracefully');
server.close(() => {
process.exit(0);
});
});
或使用 dumb-init:
RUN apk add --no-cache dumb-init
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "server.js"]
陷阱 3:时区问题
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
陷阱 4:DNS 缓存
某些基础镜像有 DNS 缓存问题,导致无法解析新的服务名。
解决:使用 nscd 或重启容器。
工具推荐
开发工具
- Docker Desktop:本地开发(Mac/Windows)
- Lazydocker:终端 UI 管理
- ctop:容器资源监控
CI/CD
- GitHub Actions:与代码仓库集成
- GitLab CI:一体化 DevOps
- Jenkins:企业级 CI/CD
安全
- Trivy:镜像漏洞扫描
- Clair:容器安全分析
- Anchore:企业级安全平台
编排
- Docker Swarm:简单编排
- Kubernetes:复杂场景
- Nomad:轻量级替代
总结:Docker 检查清单
开发阶段
- 使用多阶段构建
- 优化 .dockerignore
- 使用非 root 用户
- 层缓存优化
- 本地开发用 docker-compose
构建阶段
- 自动化构建(CI/CD)
- 镜像安全扫描
- 版本标签管理(latest + sha)
- 多架构支持(amd64/arm64)
部署阶段
- 健康检查配置
- 资源限制(CPU/内存)
- 日志管理
- 监控告警
- 备份策略
- 网络隔离
运维阶段
- 定期更新基础镜像
- 监控资源使用
- 定期安全扫描
- 灾难恢复演练
参考资源
官方文档:
书籍:
- 《Docker Deep Dive》Nigel Poulton
- 《The Docker Book》James Turnbull
课程:
- Docker Mastery (Udemy)
- Container Fundamentals (Pluralsight)
你使用 Docker 遇到过什么问题?有什么优化技巧?欢迎分享。
本文的示例都经过生产环境验证,运行在全球多个数据中心。