技术
20 min readDocker 容器化部署实践:从开发到生产
2025-01-12by LZG
#Docker#DevOps#Deployment
引言
Docker 已经成为现代应用部署的标准工具。本文将介绍如何使用 Docker 进行应用容器化,从开发环境到生产环境的完整流程。
1. Docker 基础
1.1 核心概念
镜像 (Image):只读的模板,包含运行应用所需的一切
容器 (Container):镜像的运行实例
仓库 (Registry):存储和分发镜像的地方
# 基础命令
docker build -t myapp:1.0 .
docker run -d -p 3000:3000 --name myapp myapp:1.0
docker ps
docker logs myapp
docker stop myapp
docker rm myapp
1.2 Dockerfile 编写
# 多阶段构建
FROM node:18-alpine AS builder
WORKDIR /app
# 复制依赖文件
COPY package*.json ./
# 安装依赖
RUN npm ci --only=production
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段
FROM node:18-alpine AS production
WORKDIR /app
# 从构建阶段复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# 暴露端口
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD node healthcheck.js || exit 1
# 启动应用
CMD ["node", "dist/index.js"]
2. Next.js 应用容器化
2.1 优化 Dockerfile
# 使用官方 Next.js 基础镜像
FROM node:18-alpine AS base
# 安装依赖
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 设置环境变量
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build
# 生产阶段
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
# 创建非 root 用户
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# 复制必要文件
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
2.2 Docker Compose 配置
version: '3.8'
services:
# Next.js 应用
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:password@db:5432/mydb
depends_on:
- db
- redis
restart: unless-stopped
networks:
- app-network
# PostgreSQL 数据库
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=mydb
volumes:
- postgres-data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped
networks:
- app-network
# Redis 缓存
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis-data:/data
restart: unless-stopped
networks:
- app-network
# Nginx 反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl
depends_on:
- app
restart: unless-stopped
networks:
- app-network
volumes:
postgres-data:
redis-data:
networks:
app-network:
driver: bridge
3. Docker Compose 高级配置
3.1 环境变量管理
# docker-compose.yml
version: '3.8'
services:
app:
build: .
env_file:
- .env.production
environment:
- NODE_ENV=${NODE_ENV}
- API_URL=${API_URL}
# .env.production
NODE_ENV=production
API_URL=https://api.example.com
DATABASE_URL=postgresql://user:password@db:5432/mydb
3.2 健康检查
services:
app:
build: .
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
3.3 资源限制
services:
app:
build: .
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
4. CI/CD 集成
4.1 GitHub Actions 配置
name: Docker Build and Deploy
on:
push:
branches: [main]
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: |
myorg/myapp:latest
myorg/myapp:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Deploy to server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull myorg/myapp:latest
docker stop myapp
docker rm myapp
docker run -d --name myapp -p 3000:3000 myorg/myapp:latest
4.2 自动化部署脚本
#!/bin/bash
# deploy.sh
set -e
IMAGE_NAME="myorg/myapp"
CONTAINER_NAME="myapp"
PORT=3000
echo "拉取最新镜像..."
docker pull $IMAGE_NAME:latest
echo "停止旧容器..."
docker stop $CONTAINER_NAME 2>/dev/null || true
docker rm $CONTAINER_NAME 2>/dev/null || true
echo "启动新容器..."
docker run -d \
--name $CONTAINER_NAME \
-p $PORT:$PORT \
--restart unless-stopped \
--env-file .env.production \
$IMAGE_NAME:latest
echo "清理旧镜像..."
docker image prune -f
echo "部署完成!"
5. 生产环境优化
5.1 Nginx 反向代理
# nginx.conf
events {
worker_connections 1024;
}
http {
upstream app {
least_conn;
server app:3000;
server app:3001;
server app:3002;
}
# 限流配置
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
listen 80;
server_name example.com;
# 重定向到 HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/cert.pem;
ssl_certificate_key /etc/nginx/ssl/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
# 静态文件缓存
location /static/ {
proxy_pass http://app;
expires 1y;
add_header Cache-Control "public, immutable";
}
# API 限流
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 默认代理
location / {
proxy_pass http://app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
5.2 日志管理
# docker-compose.yml
services:
app:
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
labels: "app=nextjs"
# 日志收集脚本
#!/bin/bash
docker logs --tail 100 -f myapp | while read line; do
echo "$line" | logger -t myapp
done
5.3 监控与告警
# 添加 Prometheus 监控
services:
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
grafana:
image: grafana/grafana
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'docker'
static_configs:
- targets: ['cadvisor:8080']
6. 安全最佳实践
6.1 镜像安全扫描
# 使用 Trivy 扫描镜像
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy image myorg/myapp:latest
# GitHub Actions 集成
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myorg/myapp:latest'
format: 'sarif'
output: 'trivy-results.sarif'
6.2 最小权限原则
# 使用非 root 用户
FROM node:18-alpine
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
# 只复制必要文件
COPY --chown=nextjs:nodejs package*.json ./
RUN npm ci --only=production
COPY --chown=nextjs:nodejs . .
6.3 敏感信息管理
# 使用 Docker Secrets
echo "my-secret-password" | docker secret create db_password -
# docker-compose.yml
services:
db:
image: postgres:15
secrets:
- db_password
secrets:
db_password:
external: true
7. 故障排查
7.1 常见问题
容器无法启动:
# 查看容器日志
docker logs myapp
# 检查容器状态
docker inspect myapp
# 进入容器调试
docker exec -it myapp sh
网络连接问题:
# 检查网络
docker network ls
docker network inspect app-network
# 测试连接
docker exec myapp ping db
资源占用过高:
# 查看资源使用
docker stats
# 限制资源
docker update --memory="512m" --cpus="1.0" myapp
7.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 --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
# 清理无用镜像
docker image prune -a
# 清理无用容器
docker container prune
# 清理无用卷
docker volume prune
结语
Docker 容器化技术为应用部署提供了标准化、可移植的解决方案。通过合理使用 Docker、Docker Compose 和 CI/CD 工具,我们可以构建出高效、可靠的生产环境。关键是要遵循最佳实践,持续优化和改进部署流程。