Docker 各语言部署指南
概述
容器化部署是现代应用交付的标准方式。本文档针对不同语言和框架给出 Docker 容器化的最佳实践,涵盖基础镜像选择、多阶段构建、性能优化、安全配置等关键环节。
Java 应用容器化
JDK 版本选择
推荐使用 eclipse-temurin 镜像,它基于 Eclipse Temurin(原 AdoptOpenJDK)构建,提供 OpenJDK 的官方二进制发行版,支持 LTS 版本:
eclipse-temurin:21-jre-alpine— Java 21 (LTS),推荐新项目使用eclipse-temurin:17-jre-alpine— Java 17 (LTS),当前主流eclipse-temurin:11-jre-alpine— Java 11 (LTS),存量项目
生产环境始终使用 JRE 而非 JDK 镜像以减少体积。
JVM 内存参数
容器化环境下必须显式设置 JVM 内存参数,避免 JVM 自动检测与容器资源限制不一致:
dockerfile
# Dockerfile
FROM eclipse-temurin:17-jre-alpine
# JVM 参数通过环境变量注入
ENV JAVA_OPTS="\
-XX:+UseContainerSupport \
-XX:InitialRAMPercentage=50.0 \
-XX:MaxRAMPercentage=75.0 \
-XX:+PrintFlagsFinal \
-Djava.security.egd=file:/dev/./urandom"
COPY target/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]参数说明:
| 参数 | 说明 |
|---|---|
-XX:+UseContainerSupport | 让 JVM 感知容器 cgroup 内存/CPU 限制(Java 10+ 默认开启) |
-XX:InitialRAMPercentage=50.0 | 初始堆为容器内存的 50% |
-XX:MaxRAMPercentage=75.0 | 最大堆为容器内存的 75%,预留内存给元空间、堆外内存 |
-Djava.security.egd=file:/dev/./urandom | 避免 SecureRandom 在容器中因熵不足而阻塞 |
多阶段构建 — Maven
dockerfile
# === 构建阶段 ===
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /build
COPY pom.xml .
# 先下载依赖,利用 Docker 缓存层
RUN mvn dependency:go-offline -B
COPY src ./src
RUN mvn package -DskipTests -B
# === 运行阶段 ===
FROM eclipse-temurin:17-jre-alpine
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
USER appuser
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"]多阶段构建 — Gradle
dockerfile
# === 构建阶段 ===
FROM eclipse-temurin:17-jdk-alpine AS builder
WORKDIR /build
COPY build.gradle settings.gradle gradlew ./
COPY gradle ./gradle
RUN ./gradlew dependencies --no-daemon
COPY src ./src
RUN ./gradlew bootJar --no-daemon
# === 运行阶段 ===
FROM eclipse-temurin:17-jre-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/build/libs/*.jar app.jar
USER appuser
ENV JAVA_OPTS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar"].dockerignore
dockerignore
target/
build/
*.jar
*.war
*.log
.git/
.idea/
*.iml
.DS_StoreNode.js 应用容器化
基础镜像选择
推荐使用 node:XX-alpine 作为基础镜像,Alpine 版本体积小巧(约 30MB 基础),安全性高。
版本选择建议:
| 镜像 | 体积 | 适用场景 |
|---|---|---|
node:22-alpine | ~130MB | 新项目,Node.js 22 (LTS) |
node:20-alpine | ~120MB | 当前主流,Node.js 20 (LTS) |
node:18-alpine | ~110MB | 存量项目 |
多阶段构建
dockerfile
# === 构建阶段 ===
FROM node:20-alpine AS builder
WORKDIR /build
# 先复制依赖配置文件,利用 Docker 缓存层
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 如果包含 TypeScript 编译,需要 devDependencies
RUN npm ci
COPY . .
RUN npm run build
# === 运行阶段 ===
FROM node:20-alpine
# 安装 tini 作为 init 进程
RUN apk add --no-cache tini
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 复制编译产物和 production 依赖
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
COPY package.json .
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/main.js"]PM2 进程管理
对于需要多进程或多应用管理的场景,使用 PM2:
dockerfile
# === 构建阶段 ===
FROM node:20-alpine AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# === 运行阶段 ===
FROM node:20-alpine
RUN apk add --no-cache tini
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
COPY --from=builder /build/dist ./dist
COPY --from=builder /build/node_modules ./node_modules
COPY package.json .
# 全局安装 PM2
RUN npm install -g pm2
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["pm2-runtime", "dist/main.js", "-i", "max"]PM2 配置示例(ecosystem.config.js):
javascript
module.exports = {
apps: [{
name: 'app',
script: 'dist/main.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production'
},
max_memory_restart: '500M',
error_file: '/dev/null',
out_file: '/dev/null'
}]
};.dockerignore
dockerignore
node_modules/
dist/
.git/
.env
*.log
.DS_Store
.idea/
coverage/Python 应用容器化
基础镜像选择
推荐使用 python:XX-alpine 镜像,基于 Alpine Linux,体积小巧。
版本选择建议:
| 镜像 | 体积 | 适用场景 |
|---|---|---|
python:3.12-alpine | ~50MB | Python 3.12,新项目推荐 |
python:3.11-alpine | ~50MB | Python 3.11,主流选择 |
python:3.9-alpine | ~45MB | 存量项目兼容 |
pip 依赖缓存与虚拟环境
dockerfile
# === 构建阶段 ===
FROM python:3.12-alpine AS builder
# 安装编译依赖(某些 Python 包需要编译)
RUN apk add --no-cache gcc musl-dev libffi-dev
WORKDIR /build
# 先复制依赖文件,利用 Docker 缓存层
COPY requirements.txt .
# 创建虚拟环境并安装依赖
RUN python -m venv /venv && \
/venv/bin/pip install --no-cache-dir -r requirements.txt
# === 运行阶段 ===
FROM python:3.12-alpine
# 运行时只需必要系统库
RUN apk add --no-cache libffi
# 创建非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
WORKDIR /app
# 从构建阶段复制虚拟环境
COPY --from=builder /venv /venv
# 复制应用代码
COPY . .
USER appuser
# 使用虚拟环境中的 Python
ENV PATH="/venv/bin:$PATH"
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "4", "--access-logfile", "-", "--error-logfile", "-"]使用 Gunicorn 多 worker
对 Flask / Django 等框架的 Gunicorn 配置:
dockerfile
# 基于上面运行阶段的扩展
# Gunicorn 配置可通过环境变量注入
ENV GUNICORN_WORKERS=4
ENV GUNICORN_TIMEOUT=120
CMD ["sh", "-c", "gunicorn app:app \
--bind 0.0.0.0:8000 \
--workers $GUNICORN_WORKERS \
--worker-class sync \
--timeout $GUNICORN_TIMEOUT \
--access-logfile - \
--error-logfile - \
--keep-alive 5"]Gunicorn worker 数量计算方法:
推荐 workers = 2 * CPU 核心数 + 1
例如 2 核容器 → 5 个 workers.dockerignore
dockerignore
__pycache__/
*.pyc
*.pyo
.env
venv/
.venv/
.git/
.idea/
*.egg-info/
dist/
.DS_Store前端静态资源容器化
构建策略
前端项目使用 Node.js 构建,Nginx 运行,典型的多阶段构建模式。
基础 Dockerfile
dockerfile
# === 构建阶段 ===
FROM node:20-alpine AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# === 运行阶段 ===
FROM nginx:1.27-alpine
# 复制构建产物
COPY --from=builder /build/dist /usr/share/nginx/html
# 复制自定义 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 创建非 root 用户(Nginx Alpine 默认以 nginx 用户运行)
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1Nginx 配置 — gzip 压缩
nginx
# nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# gzip 压缩
gzip on;
gzip_min_length 256;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml image/svg+xml;
gzip_vary on;
gzip_proxied any;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# SPA 路由配置:所有路由回退到 index.html
location / {
try_files $uri $uri/ /index.html;
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}结合环境变量注入
dockerfile
# === 构建阶段 ===
FROM node:20-alpine AS builder
WORKDIR /build
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# === 运行阶段 ===
FROM nginx:1.27-alpine
# 安装 envsubst 用于运行时注入环境变量
RUN apk add --no-cache gettext
COPY --from=builder /build/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 环境变量注入脚本
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]docker-entrypoint.sh:
bash
#!/bin/sh
# 运行时注入环境变量到 JS 文件
if [ -n "$API_BASE_URL" ]; then
find /usr/share/nginx/html -type f -name "*.js" \
-exec sed -i "s|__API_BASE_URL__|$API_BASE_URL|g" {} \;
fi
exec "$@".dockerignore
dockerignore
node_modules/
dist/
.git/
.env
*.log
.DS_Store
.idea/数据库容器化
MySQL
基础 Dockerfile
dockerfile
FROM mysql:8.4
# 时区设置
ENV TZ=Asia/Shanghai
# 字符集配置
COPY my.cnf /etc/mysql/conf.d/my.cnf
# 初始化脚本
COPY init.sql /docker-entrypoint-initdb.d/
EXPOSE 3306my.cnf
ini
[mysqld]
# 字符集
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# 连接字符集
init-connect = 'SET NAMES utf8mb4'
# 时区
default-time-zone = '+08:00'
# 性能优化
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
max_connections = 200
# 日志
general_log = 0
slow_query_log = 1
long_query_time = 2
[client]
default-character-set = utf8mb4
[mysql]
default-character-set = utf8mb4初始化脚本示例
sql
-- init.sql
CREATE DATABASE IF NOT EXISTS app_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER 'app_user'@'%' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON app_db.* TO 'app_user'@'%';
FLUSH PRIVILEGES;数据持久化
yaml
# docker-compose.yml 片断
services:
mysql:
image: mysql:8.4
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: app_db
ports:
- "3306:3306"
volumes:
mysql_data:Redis
基础 Dockerfile
dockerfile
FROM redis:7.4-alpine
# 复制自定义配置
COPY redis.conf /usr/local/etc/redis/redis.conf
# 数据持久化目录
RUN mkdir -p /data && chown redis:redis /data
USER redis
EXPOSE 6379
CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]redis.conf
conf
# 绑定地址(仅允许容器内访问)
bind 0.0.0.0
# 端口
port 6379
# 密码认证
requirepass your_redis_password
# 持久化 — RDB
save 900 1
save 300 10
save 60 10000
dbfilename dump.rdb
# 持久化 — AOF
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
# 内存管理
maxmemory 512mb
maxmemory-policy allkeys-lru
# 慢查询日志
slowlog-log-slower-than 10000
slowlog-max-len 128docker-compose 配置
yaml
services:
redis:
image: redis:7.4-alpine
command: redis-server --requirepass your_password --appendonly yes
volumes:
- redis_data:/data
ports:
- "6379:6379"
volumes:
redis_data:MongoDB
基础 Dockerfile
dockerfile
FROM mongo:7.0
# 复制初始化脚本
COPY init.js /docker-entrypoint-initdb.d/
EXPOSE 27017初始化脚本
javascript
// init.js
db = db.getSiblingDB('app_db');
db.createUser({
user: 'app_user',
pwd: 'your_password',
roles: [
{ role: 'readWrite', db: 'app_db' }
]
});
// 创建集合
db.createCollection('users');
db.createCollection('orders');
// 创建索引
db.users.createIndex({ email: 1 }, { unique: true });
db.orders.createIndex({ userId: 1, createdAt: -1 });docker-compose 配置
yaml
services:
mongodb:
image: mongo:7.0
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: admin_password
MONGO_INITDB_DATABASE: app_db
volumes:
- mongo_data:/data/db
- ./init.js:/docker-entrypoint-initdb.d/init.js
ports:
- "27017:27017"
volumes:
mongo_data:最佳实践
非 root 用户运行
所有容器应使用非 root 用户运行,降低安全风险。
dockerfile
# Alpine 基础镜像
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# Debian/Ubuntu 基础镜像
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
# 如需使用特定端口(如 80/443),在 Dockerfile 中先暴露再切换用户
# 或使用 1024 以上的非特权端口健康检查
健康检查应包含三个维度:
| 维度 | 检查方式 | 说明 |
|---|---|---|
| 存活检查 | HEALTHCHECK | 容器是否仍在运行 |
| 就绪检查 | 通过编排平台配置 | 服务是否可接受流量 |
| 依赖检查 | 应用启动时检查 | 数据库/中间件是否就绪 |
通用健康检查示例:
dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1资源限制
在容器编排平台中始终设置资源限制:
yaml
# Kubernetes Deployment 片断
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"资源限制参考:
| 应用类型 | CPU 请求 | CPU 上限 | 内存请求 | 内存上限 |
|---|---|---|---|---|
| Java 应用 | 1 核 | 2 核 | 512Mi | 1Gi |
| Node.js 应用 | 500m | 1 核 | 256Mi | 512Mi |
| Python 应用 | 500m | 1 核 | 256Mi | 512Mi |
| Nginx 静态资源 | 200m | 500m | 128Mi | 256Mi |
| MySQL | 2 核 | 4 核 | 2Gi | 4Gi |
| Redis | 1 核 | 2 核 | 512Mi | 1Gi |
| MongoDB | 2 核 | 4 核 | 2Gi | 4Gi |
日志处理
容器日志应输出到标准输出/标准错误,由容器运行时统一收集:
dockerfile
# Java — 日志输出到 stdout
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app.jar 2>&1"]
# Node.js — 默认输出到 stdout/stderr
# 无需特殊配置
# Python Gunicorn — 输出到 stdout
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--access-logfile", "-", "--error-logfile", "-"]
# Nginx — 输出到 stdout/stderr
# 在 nginx.conf 中配置:
# access_log /dev/stdout;
# error_log /dev/stderr;Nginx 日志配置:
nginx
# 在 nginx.conf 中添加
access_log /dev/stdout;
error_log /dev/stderr;镜像体积优化
各语言典型镜像体积对比:
| 语言 | 构建方式 | 最终镜像体积 |
|---|---|---|
| Java | 多阶段构建 (JRE) | ~180MB |
| Node.js | 多阶段构建 (Alpine) | ~150MB |
| Python | 多阶段构建 (Alpine) | ~100MB |
| 前端静态资源 | Node 构建 + Nginx | ~30MB |
通用优化技巧:
- 多阶段构建 — 构建环境与运行环境分离
- 选择 Alpine 镜像 — 比标准镜像小 5-10 倍
- 合理编写 .dockerignore — 避免将不必要的文件发送到 Docker daemon
- 利用构建缓存 — 将不常变化的层提前复制(如 package.json、requirements.txt)
- 清理构建中间产物 — 仅在运行阶段保留运行所需的最小文件集
安全加固清单
dockerfile
# 1. 使用特定版本号而非 latest 标签
FROM eclipse-temurin:17-jre-alpine
# 2. 安装安全更新
RUN apk update && apk upgrade --no-cache
# 3. 删除 setuid/setgid 权限(非 root 用户不需要)
RUN find / -perm /6000 -type f -exec chmod a-s {} \; || true
# 4. 使用非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 5. 只读根文件系统(配合编排平台)
# 在 Kubernetes 中配置 readOnlyRootFilesystem: true
# 6. 禁止特权提升
# 在 Kubernetes 中配置 securityContext:
# allowPrivilegeEscalation: false
# capabilities:
# drop: ["ALL"]多阶段构建通用模板
构建阶段 (builder) 运行阶段 (runtime)
┌─────────────────┐ ┌─────────────────┐
│ JDK / Node / │ │ JRE / Alpine │
│ Python 完整SDK │ ───→ │ + 编译产物 │
│ + 依赖安装 │ 复制 │ + 运行时依赖 │
│ + 代码编译 │ │ │
│ + 单元测试 │ │ 非 root 用户 │
└─────────────────┘ └─────────────────┘
体积: 1GB+ 体积: ~100MB