- 发布于
Docker容器化实战指南:从开发到生产环境的完整部署
- 作者

- 姓名
- 全能波
- GitHub
- @weicracker
Docker容器化实战指南:从开发到生产环境的完整部署
Docker容器化技术为应用部署提供了标准化、可移植的解决方案。本文将分享Docker在前端和全栈项目中的实战应用经验。
Docker基础概念
Dockerfile最佳实践
# 前端React应用Dockerfile
# 多阶段构建 - 构建阶段
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 安装依赖(利用Docker缓存层)
RUN npm ci --only=production && npm cache clean --force
# 复制源代码
COPY . .
# 构建应用
RUN npm run build
# 生产阶段 - 使用nginx服务静态文件
FROM nginx:alpine AS production
# 安装必要工具
RUN apk add --no-cache curl
# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf
# 从构建阶段复制构建产物
COPY /app/dist /usr/share/nginx/html
# 复制启动脚本
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# 健康检查
HEALTHCHECK \
CMD curl -f http://localhost/ || exit 1
# 暴露端口
EXPOSE 80
# 设置用户(安全考虑)
USER nginx
# 启动命令
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
# nginx.conf - Nginx配置文件
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
# 性能优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/json
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML文件不缓存
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# SPA路由支持
location / {
try_files $uri $uri/ /index.html;
}
# API代理
location /api/ {
proxy_pass http://backend:3000/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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;
proxy_cache_bypass $http_upgrade;
}
# 健康检查端点
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
}
# docker-entrypoint.sh - 启动脚本
#!/bin/sh
set -e
# 环境变量替换
if [ -n "$API_URL" ]; then
echo "Setting API_URL to $API_URL"
find /usr/share/nginx/html -name "*.js" -exec sed -i "s|__API_URL__|$API_URL|g" {} \;
fi
if [ -n "$APP_TITLE" ]; then
echo "Setting APP_TITLE to $APP_TITLE"
find /usr/share/nginx/html -name "*.html" -exec sed -i "s|__APP_TITLE__|$APP_TITLE|g" {} \;
fi
# 执行原始命令
exec "$@"
Node.js后端Dockerfile
# Node.js后端应用Dockerfile
FROM node:18-alpine AS base
# 安装系统依赖
RUN apk add --no-cache \
dumb-init \
curl \
&& rm -rf /var/cache/apk/*
# 创建应用用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nodejs -u 1001
# 设置工作目录
WORKDIR /app
# 复制package文件
COPY package*.json ./
# 开发依赖阶段
FROM base AS deps
RUN npm ci --include=dev
# 构建阶段
FROM base AS build
COPY /app/node_modules ./node_modules
COPY . .
# 构建应用(如果有TypeScript等需要编译的代码)
RUN npm run build
# 生产依赖阶段
FROM base AS prod-deps
RUN npm ci --only=production && npm cache clean --force
# 生产阶段
FROM base AS production
# 复制生产依赖
COPY /app/node_modules ./node_modules
# 复制构建产物
COPY /app/dist ./dist
COPY /app/package*.json ./
# 复制其他必要文件
COPY /app/public ./public
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
# 健康检查
HEALTHCHECK \
CMD curl -f http://localhost:$PORT/health || exit 1
# 暴露端口
EXPOSE $PORT
# 切换到非root用户
USER nodejs
# 使用dumb-init作为PID 1
ENTRYPOINT ["dumb-init", "--"]
# 启动应用
CMD ["node", "dist/server.js"]
# .dockerignore - Docker忽略文件
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage
.nyc_output
.coverage
.vscode
.idea
*.log
dist
build
.DS_Store
Thumbs.db
Docker Compose编排
开发环境配置
# docker-compose.dev.yml - 开发环境
version: '3.8'
services:
# 前端开发服务
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
- REACT_APP_API_URL=http://localhost:3001/api
depends_on:
- backend
networks:
- app-network
restart: unless-stopped
# 后端API服务
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
ports:
- "3001:3000"
- "9229:9229" # Node.js调试端口
volumes:
- ./backend:/app
- /app/node_modules
environment:
- NODE_ENV=development
- PORT=3000
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=myapp_dev
- DB_USER=postgres
- DB_PASSWORD=password
- REDIS_URL=redis://redis:6379
- JWT_SECRET=dev-secret-key
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
command: npm run dev:debug
# PostgreSQL数据库
postgres:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
- POSTGRES_DB=myapp_dev
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
restart: unless-stopped
# Redis缓存
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
networks:
- app-network
restart: unless-stopped
command: redis-server --appendonly yes
# 数据库管理工具
adminer:
image: adminer:latest
ports:
- "8080:8080"
environment:
- ADMINER_DEFAULT_SERVER=postgres
depends_on:
- postgres
networks:
- app-network
restart: unless-stopped
# 邮件测试服务
mailhog:
image: mailhog/mailhog:latest
ports:
- "1025:1025" # SMTP
- "8025:8025" # Web UI
networks:
- app-network
restart: unless-stopped
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridge
# Dockerfile.dev - 前端开发Dockerfile
FROM node:18-alpine
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install
# 暴露端口
EXPOSE 3000
# 启动开发服务器
CMD ["npm", "start"]
# Dockerfile.dev - 后端开发Dockerfile
FROM node:18-alpine
# 安装系统依赖
RUN apk add --no-cache curl
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm install
# 暴露端口
EXPOSE 3000 9229
# 启动开发服务器
CMD ["npm", "run", "dev"]
生产环境配置
# docker-compose.prod.yml - 生产环境
version: '3.8'
services:
# Nginx反向代理
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- nginx_logs:/var/log/nginx
depends_on:
- frontend
- backend
networks:
- app-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
# 前端应用
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
target: production
environment:
- API_URL=https://api.example.com
- APP_TITLE=My Production App
networks:
- app-network
restart: unless-stopped
deploy:
replicas: 2
resources:
limits:
memory: 128M
reservations:
memory: 64M
# 后端API服务
backend:
build:
context: ./backend
dockerfile: Dockerfile
target: production
environment:
- NODE_ENV=production
- PORT=3000
- DB_HOST=postgres
- DB_PORT=5432
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- REDIS_URL=redis://redis:6379
- JWT_SECRET=${JWT_SECRET}
- EMAIL_HOST=${EMAIL_HOST}
- EMAIL_USER=${EMAIL_USER}
- EMAIL_PASS=${EMAIL_PASS}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
restart: unless-stopped
deploy:
replicas: 3
resources:
limits:
memory: 512M
reservations:
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
# PostgreSQL数据库
postgres:
image: postgres:15-alpine
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./database/backup:/backup
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
# Redis缓存
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
networks:
- app-network
restart: unless-stopped
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
command: redis-server --appendonly yes --maxmemory 200mb --maxmemory-policy allkeys-lru
volumes:
postgres_data:
redis_data:
nginx_logs:
networks:
app-network:
driver: bridge
# .env.prod - 生产环境变量
DB_NAME=myapp_prod
DB_USER=myapp_user
DB_PASSWORD=secure_password_here
JWT_SECRET=super_secure_jwt_secret_key
EMAIL_HOST=smtp.example.com
EMAIL_USER=noreply@example.com
EMAIL_PASS=email_password_here
部署脚本与CI/CD
部署脚本
#!/bin/bash
# deploy.sh - 部署脚本
set -e
# 配置变量
PROJECT_NAME="myapp"
DOCKER_REGISTRY="your-registry.com"
VERSION=${1:-latest}
ENVIRONMENT=${2:-production}
echo "🚀 Starting deployment of $PROJECT_NAME:$VERSION to $ENVIRONMENT"
# 检查Docker和Docker Compose
if ! command -v docker &> /dev/null; then
echo "❌ Docker is not installed"
exit 1
fi
if ! command -v docker-compose &> /dev/null; then
echo "❌ Docker Compose is not installed"
exit 1
fi
# 创建必要目录
mkdir -p logs backups
# 备份数据库(生产环境)
if [ "$ENVIRONMENT" = "production" ]; then
echo "📦 Creating database backup..."
docker-compose -f docker-compose.prod.yml exec -T postgres pg_dump -U $DB_USER $DB_NAME > "backups/backup-$(date +%Y%m%d-%H%M%S).sql"
fi
# 拉取最新镜像
echo "📥 Pulling latest images..."
docker-compose -f docker-compose.$ENVIRONMENT.yml pull
# 构建镜像
echo "🔨 Building images..."
docker-compose -f docker-compose.$ENVIRONMENT.yml build --no-cache
# 停止旧容器
echo "🛑 Stopping old containers..."
docker-compose -f docker-compose.$ENVIRONMENT.yml down
# 启动新容器
echo "🚀 Starting new containers..."
docker-compose -f docker-compose.$ENVIRONMENT.yml up -d
# 等待服务启动
echo "⏳ Waiting for services to start..."
sleep 30
# 健康检查
echo "🏥 Performing health checks..."
check_service() {
local service=$1
local url=$2
local max_attempts=30
local attempt=1
while [ $attempt -le $max_attempts ]; do
if curl -f -s "$url" > /dev/null; then
echo "✅ $service is healthy"
return 0
fi
echo "⏳ Waiting for $service... (attempt $attempt/$max_attempts)"
sleep 10
((attempt++))
done
echo "❌ $service health check failed"
return 1
}
# 检查各服务健康状态
check_service "Frontend" "http://localhost/health"
check_service "Backend" "http://localhost/api/health"
# 清理旧镜像
echo "🧹 Cleaning up old images..."
docker image prune -f
# 显示运行状态
echo "📊 Deployment status:"
docker-compose -f docker-compose.$ENVIRONMENT.yml ps
echo "✅ Deployment completed successfully!"
echo "🌐 Application is available at: http://localhost"
# 发送通知(可选)
if [ -n "$SLACK_WEBHOOK" ]; then
curl -X POST -H 'Content-type: application/json' \
--data "{\"text\":\"🚀 $PROJECT_NAME:$VERSION deployed to $ENVIRONMENT successfully!\"}" \
$SLACK_WEBHOOK
fi
总结
Docker容器化的核心要点:
- 镜像构建:多阶段构建、层缓存优化、安全最佳实践
- 容器编排:Docker Compose、服务依赖、健康检查
- 环境管理:开发/生产环境分离、配置管理
- 部署自动化:CI/CD流水线、滚动更新、回滚策略
- 监控运维:日志收集、性能监控、故障排查
Docker容器化技术为现代应用部署提供了标准化、可扩展的解决方案,是DevOps实践中不可或缺的重要工具。