Docker 上生产前,这些坑一定要提前避开
Docker 生产环境部署指南|生产环境实测
在很多团队的技术演进过程中,Docker 往往是从“开发环境统一”开始被引入的:开发者不再需要在本机反复安装不同版本的 JDK、Node.js、MySQL、Redis,只需要一条 docker compose up 就能拉起整套环境。但真正把 Docker 用到生产环境时,问题会变得复杂得多:镜像如何构建才安全?容器资源如何限制?日志如何收集?数据如何持久化?服务如何升级回滚?宿主机如何调优?安全边界在哪里?
本文结合生产环境实践,系统梳理 Docker 在生产环境部署中的关键要点,适合准备用 Docker 部署后端服务、中间件、前端应用、任务服务的团队参考。
一、生产环境使用 Docker 的核心价值
Docker 在生产环境中的价值,不只是“部署方便”,更重要的是它带来了应用交付方式的标准化。
1. 环境一致性
传统部署方式中,开发环境、测试环境、预发环境、生产环境往往存在差异:
- JDK 版本不同;
- 系统依赖包不同;
- 配置文件路径不同;
- 启动脚本不一致;
- 运维人员手工操作导致不可追踪。
Docker 将应用和运行环境一起打包进镜像,使应用在不同环境中具备高度一致性。只要镜像相同,理论上运行结果就应保持一致。
2. 部署可重复
生产部署最怕“这次能部署成功,下次不一定”。Docker 镜像是不可变交付物,结合 CI/CD 流水线,可以做到:
- 每次构建都有版本号;
- 每次发布都有镜像记录;
- 每次回滚都有明确目标;
- 部署过程脚本化、自动化。
3. 资源隔离
Docker 通过 Linux Namespace 和 Cgroups 提供一定程度的隔离能力。虽然它不是虚拟机,安全隔离能力不能等同于 VM,但对于大多数 Web 应用、任务服务、网关服务而言,Docker 的隔离能力已经足够实用。
4. 快速扩缩容
容器启动速度通常远快于传统虚拟机。对于无状态应用,可以快速拉起多个副本,配合负载均衡实现横向扩展。
二、生产环境部署前的整体规划
在正式部署 Docker 之前,不能只考虑“容器怎么启动”,还要提前规划以下几个方面:
| 规划项 | 重点问题 |
|---|---|
| 镜像仓库 | 使用私有仓库还是公有仓库?是否需要权限控制? |
| 网络规划 | 容器端口如何暴露?服务之间如何通信? |
| 数据存储 | 数据库、文件、日志是否需要持久化? |
| 配置管理 | 配置文件、环境变量、密钥如何管理? |
| 资源限制 | CPU、内存、磁盘 IO 是否有限制? |
| 日志监控 | 日志在哪里?指标如何采集?告警如何触发? |
| 安全策略 | 是否禁用特权容器?是否限制 root 权限? |
| 升级回滚 | 发布失败如何快速恢复? |
如果这些问题没有提前设计,Docker 很容易从“提高效率的工具”变成“隐藏风险的黑盒”。
三、生产环境宿主机准备
Docker 的稳定运行离不开宿主机。生产环境建议使用稳定的 Linux 发行版,例如:
- Ubuntu Server LTS;
- Debian Stable;
- CentOS Stream / Rocky Linux / AlmaLinux;
- 企业内部统一维护的 Linux 镜像。
1. 内核与系统要求
Docker 依赖 Linux 内核特性,生产环境建议使用较新的稳定内核。对于老旧系统,要特别关注:
- OverlayFS 支持情况;
- Cgroups 版本;
- iptables/nftables 兼容性;
- 文件句柄限制;
- 内核参数配置。
可通过以下命令查看系统内核版本:
uname -a
查看系统版本:
cat /etc/os-release
2. 时间同步
生产环境必须配置时间同步,否则日志排查、监控指标、证书校验、分布式系统一致性都会受到影响。
推荐使用 chrony:
sudo apt install -y chrony
sudo systemctl enable chrony
sudo systemctl start chrony
检查时间同步状态:
chronyc tracking
3. 文件句柄限制
高并发服务经常遇到文件句柄不足的问题。可以查看当前限制:
ulimit -n
生产环境建议根据服务规模提高限制,例如:
65535
在 /etc/security/limits.conf 中配置:
* soft nofile 65535
* hard nofile 65535
同时也要关注 systemd 服务限制,必要时修改 Docker 服务的限制配置。
四、Docker 安装与基础配置
1. 安装 Docker Engine
以 Ubuntu 为例,推荐使用官方仓库安装 Docker Engine,而不是使用系统默认源中的旧版本。
sudo apt update
sudo apt install -y ca-certificates curl gnupg
添加 Docker 官方 GPG key 和仓库后安装:
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
安装完成后检查版本:
docker version
docker compose version
启动并设置开机自启:
sudo systemctl enable docker
sudo systemctl start docker
2. 配置 Docker daemon
Docker 的核心配置文件通常位于:
/etc/docker/daemon.json
生产环境推荐配置示例:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
},
"storage-driver": "overlay2",
"exec-opts": ["native.cgroupdriver=systemd"],
"live-restore": true
}
配置说明:
log-driver:默认使用json-file,但必须限制日志大小;max-size:单个日志文件最大大小;max-file:日志文件保留数量;storage-driver:推荐使用overlay2;live-restore:Docker daemon 重启时,尽量保持容器运行;cgroupdriver:建议与系统保持一致,使用 systemd。
修改后重启 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
五、镜像构建最佳实践
镜像是 Docker 部署的核心。生产环境中,镜像构建质量直接影响安全性、稳定性和部署效率。
1. 使用精简基础镜像
不要随意使用体积过大的基础镜像。比如 Java 应用可以选择:
FROM eclipse-temurin:17-jre
Node.js 应用可以选择:
FROM node:20-alpine
但需要注意,alpine 使用 musl libc,某些依赖可能存在兼容问题。如果涉及复杂原生依赖,使用 Debian slim 镜像可能更稳妥。
2. 多阶段构建
多阶段构建可以减少最终镜像体积,避免将编译工具、源码、临时文件带入生产镜像。
以 Go 应用为例:
FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server ./cmd/server
FROM alpine:3.20
WORKDIR /app
COPY --from=builder /app/server /app/server
EXPOSE 8080
ENTRYPOINT ["/app/server"]
最终镜像只包含可执行文件和必要运行环境,体积更小,攻击面更低。
3. 不要在镜像中写死配置
错误示例:
ENV MYSQL_PASSWORD=123456
生产环境不应把密码、密钥、Token 写进镜像。镜像应保持环境无关,配置通过以下方式注入:
- 环境变量;
- 配置文件挂载;
- Secret 管理工具;
- 配置中心;
- CI/CD 注入。
4. 固定基础镜像版本
不要在生产中长期使用:
FROM node:latest
latest 不代表稳定,只代表默认标签。基础镜像一旦更新,可能导致构建结果不可预期。
推荐写法:
FROM node:20.11.1
或者使用 digest 锁定:
FROM node@sha256:xxxx
六、容器运行参数建议
1. 设置容器名称和重启策略
生产环境建议明确容器名称,并设置重启策略:
docker run -d \
--name app-server \
--restart=unless-stopped \
-p 8080:8080 \
app-server:1.0.0
常见重启策略:
| 策略 | 说明 |
|---|---|
no |
默认,不自动重启 |
always |
无论退出原因都重启 |
unless-stopped |
除非手动停止,否则自动重启 |
on-failure |
非 0 退出码时重启 |
生产环境常用 unless-stopped。
2. 限制 CPU 和内存
不限制资源是生产环境常见隐患。一个异常容器可能吃满宿主机资源,影响其他服务。
示例:
docker run -d \
--name app-server \
--memory=1g \
--memory-swap=1g \
--cpus=1.5 \
app-server:1.0.0
参数说明:
--memory=1g:限制最大内存;--memory-swap=1g:限制内存加 swap,总量为 1g;--cpus=1.5:最多使用 1.5 个 CPU 核心。
对于 Java 应用,除了限制容器内存,还要正确设置 JVM 参数,例如:
JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0"
否则 JVM 可能误判可用内存,导致 OOM。
3. 禁止不必要的特权模式
不要随意使用:
--privileged
特权模式会赋予容器过高权限,突破容器隔离边界。除非运行特殊系统级组件,否则生产环境应避免使用。
可以使用更细粒度的能力控制:
--cap-drop=ALL
--cap-add=NET_BIND_SERVICE
4. 使用只读文件系统
对于无状态应用,可以考虑启用只读根文件系统:
docker run -d \
--read-only \
--tmpfs /tmp \
app-server:1.0.0
这样即使应用被入侵,也能降低攻击者写入恶意文件的风险。
七、Docker Compose 生产部署示例
虽然大规模集群通常会使用 Kubernetes,但对于中小型项目、单机多服务部署、内部系统,Docker Compose 仍然非常实用。
下面是一个生产环境 Compose 示例:
services:
app:
image: registry.example.com/app-server:1.0.0
container_name: app-server
restart: unless-stopped
ports:
- "8080:8080"
environment:
TZ: Asia/Shanghai
SPRING_PROFILES_ACTIVE: prod
JAVA_OPTS: "-XX:MaxRAMPercentage=75.0"
volumes:
- ./config/application-prod.yml:/app/config/application-prod.yml:ro
- ./logs/app:/app/logs
depends_on:
- redis
networks:
- app-net
deploy:
resources:
limits:
cpus: "1.5"
memory: 1024M
redis:
image: redis:7.2
container_name: redis
restart: unless-stopped
command: ["redis-server", "/usr/local/etc/redis/redis.conf"]
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
- ./redis/data:/data
networks:
- app-net
networks:
app-net:
driver: bridge
需要注意的是,deploy.resources 在普通 docker compose 模式下部分配置可能不生效,资源限制更推荐使用 Compose 的兼容参数,或在 Swarm/Kubernetes 中管理。
启动服务:
docker compose up -d
查看服务:
docker compose ps
查看日志:
docker compose logs -f app
停止服务:
docker compose down
八、日志管理:生产环境必须限制日志大小
Docker 默认使用 json-file 保存容器标准输出日志。如果应用持续输出大量日志,而没有设置日志轮转,磁盘很容易被打满。
查看容器日志路径:
docker inspect --format='{{.LogPath}}' app-server
建议在 /etc/docker/daemon.json 中统一配置日志限制:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
也可以在 Compose 中单独配置:
logging:
driver: json-file
options:
max-size: "100m"
max-file: "5"
生产环境日志管理建议:
- 应用日志优先输出到标准输出;
- 由 Docker 或日志采集器统一收集;
- 配置日志轮转,防止磁盘打满;
- 重要日志进入 ELK、Loki、Kafka 或云日志平台;
- 日志中不要打印密码、Token、身份证号等敏感信息。
九、数据持久化:不要把重要数据只放在容器内
容器本身是易变的,删除容器后,容器内写入层数据也会丢失。因此数据库、上传文件、缓存持久化数据必须挂载到宿主机目录或 Docker Volume。
1. 使用宿主机目录挂载
docker run -d \
--name mysql \
-v /data/mysql:/var/lib/mysql \
mysql:8.0
优点是路径直观,方便备份和排查。缺点是需要自己管理权限、目录结构。
2. 使用 Docker Volume
docker volume create mysql-data
docker run -d \
--name mysql \
-v mysql-data:/var/lib/mysql \
mysql:8.0
Docker Volume 由 Docker 管理,适合标准化部署。
3. 数据目录权限
挂载目录时,经常出现容器内进程无权限写入的问题。需要确认容器内进程 UID/GID,并调整宿主机目录权限:
chown -R 1000:1000 /data/app
不要为了省事直接:
chmod -R 777 /data/app
这在生产环境中非常危险。
十、网络与反向代理
生产环境不建议所有容器都直接暴露端口。更常见的做法是:
- 内部服务只在 Docker 网络内通信;
- 对外统一通过 Nginx、Traefik、Caddy 或云负载均衡暴露;
- HTTPS 证书由网关层统一管理。
Nginx 反向代理示例
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://app-server:8080;
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;
}
}
如果 Nginx 和应用容器在同一个 Docker 网络中,可以直接使用服务名访问。
十一、健康检查与自动恢复
容器“运行中”不代表服务“可用”。应用可能进程存在,但接口已无法响应。因此生产环境建议配置健康检查。
Dockerfile 示例:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
Compose 示例:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 5s
retries: 3
应用侧应提供轻量级健康检查接口,例如:
GET /health
健康检查不应依赖过多外部服务,否则可能造成误判。可以区分:
- 存活检查:进程是否正常;
- 就绪检查:是否可以接收流量;
- 深度检查:数据库、Redis、MQ 是否可用。
十二、发布、升级与回滚策略
生产部署不能只考虑“上线”,更要考虑“失败后怎么回退”。
1. 镜像版本命名
建议镜像标签包含:
- 应用名;
- Git commit;
- 构建时间;
- 语义化版本号。
例如:
registry.example.com/app-server:1.3.2-8f3a9c1
不建议生产直接部署:
app-server:latest
因为无法明确当前运行的是哪个版本。
2. 标准发布流程
一个可靠的 Docker 发布流程通常包括:
- 开发提交代码;
- CI 执行单元测试;
- 构建 Docker 镜像;
- 扫描镜像漏洞;
- 推送到私有镜像仓库;
- 部署到测试环境;
- 部署到预发环境;
- 人工或自动审批;
- 部署到生产环境;
- 监控观察;
- 异常时回滚。
3. 回滚方式
Docker 回滚通常很简单:重新部署上一版本镜像。
docker stop app-server
docker rm app-server
docker run -d \
--name app-server \
--restart=unless-stopped \
-p 8080:8080 \
registry.example.com/app-server:1.3.1
如果使用 Compose,可以修改镜像标签后执行:
docker compose pull
docker compose up -d
需要特别注意:应用回滚不等于数据库回滚。如果新版本执行了不可逆数据库变更,单纯回滚镜像可能仍然失败。因此数据库变更必须遵循兼容性原则。
十三、安全加固建议
Docker 不是安全沙箱。生产环境中必须进行必要安全加固。
1. 不使用 root 用户运行应用
Dockerfile 中建议创建普通用户:
FROM eclipse-temurin:17-jre
RUN useradd -r -u 10001 appuser
WORKDIR /app
COPY app.jar /app/app.jar
USER appuser
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
这样即使应用被入侵,攻击者获得的也是普通用户权限。
2. 镜像漏洞扫描
可以使用:
- Trivy;
- Grype;
- Clair;
- 云厂商镜像扫描服务。
示例:
trivy image registry.example.com/app-server:1.0.0
发现高危漏洞后,应及时升级基础镜像或依赖包。
3. 控制 Docker Socket
/var/run/docker.sock 权限非常敏感。挂载 Docker Socket 到容器内等同于赋予容器控制宿主机 Docker 的能力。
危险示例:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
除非非常清楚风险,否则不要这样做。
4. 最小权限原则
生产容器建议:
- 禁用特权模式;
- 限制 Linux capabilities;
- 使用只读文件系统;
- 不挂载敏感宿主机目录;
- 不在镜像和环境变量中暴露敏感密钥;
- 定期更新基础镜像;
- 只开放必要端口。
十四、监控与告警
生产环境必须建立监控体系。Docker 相关监控通常包含:
1. 宿主机指标
- CPU 使用率;
- 内存使用率;
- 磁盘使用率;
- 磁盘 IO;
- 网络流量;
- 系统负载;
- 文件句柄数量。
2. 容器指标
- 容器 CPU;
- 容器内存;
- 容器重启次数;
- 容器网络流量;
- 容器磁盘读写;
- 容器健康状态。
可以使用:
- Prometheus + Grafana;
- cAdvisor;
- Node Exporter;
- Loki;
- ELK;
- 云监控。
查看容器资源使用:
docker stats
但 docker stats 只适合临时排查,不能替代长期监控系统。
3. 告警建议
至少应配置以下告警:
- 宿主机磁盘使用率超过 80%;
- 容器异常退出或频繁重启;
- 应用接口错误率升高;
- CPU 长时间超过阈值;
- 内存使用接近限制;
- 日志中出现大量异常;
- 健康检查失败;
- 数据库连接池耗尽。
十五、生产环境常见问题与实测经验
1. 磁盘被 Docker 日志打满
这是生产中最常见的问题之一。表现为:
- 应用突然无法写入文件;
- 数据库异常;
- 容器无法启动;
df -h显示根分区 100%。
解决方法:
docker system df
查看 Docker 占用空间。对于日志,需要提前配置日志轮转,而不是等磁盘满了再处理。
2. 容器内时间不一致
如果容器时区不正确,日志时间会混乱。可以在 Compose 中设置:
environment:
TZ: Asia/Shanghai
或者挂载宿主机时区文件:
volumes:
- /etc/localtime:/etc/localtime:ro
3. 容器内存限制后 Java 应用 OOM
Java 应用在容器中运行时,要确认 JVM 是否正确识别容器内存限制。建议使用较新的 JDK,并设置:
-XX:MaxRAMPercentage=75.0
避免堆内存占满容器,给 Metaspace、线程栈、直接内存留出空间。
4. 使用 latest 导致版本不可追踪
很多团队为了方便使用 latest,结果发布后无法确认生产环境实际运行版本。生产必须使用明确版本标签。
5. 容器重启后数据丢失
如果数据库、上传目录没有挂载卷,删除容器就会导致数据丢失。生产环境中,所有重要数据都必须外置持久化,并定期备份。
十六、生产部署检查清单
上线前建议逐项检查:
- [ ] 镜像是否固定版本,而不是
latest; - [ ] Dockerfile 是否使用多阶段构建;
- [ ] 镜像中是否没有密钥、密码、Token;
- [ ] 容器是否设置重启策略;
- [ ] 容器是否限制 CPU 和内存;
- [ ] 日志是否配置大小限制;
- [ ] 数据目录是否持久化;
- [ ] 配置文件是否只读挂载;
- [ ] 是否避免使用
--privileged; - [ ] 是否使用普通用户运行应用;
- [ ] 是否配置健康检查;
- [ ] 是否接入监控和告警;
- [ ] 是否有回滚方案;
- [ ] 是否完成数据备份;
- [ ] 是否验证过预发环境;
- [ ] 是否记录当前发布版本。
十七、推荐目录结构
对于单机 Docker Compose 部署,可以采用如下目录结构:
/opt/app
├── docker-compose.yml
├── .env
├── config
│ └── application-prod.yml
├── logs
│ └── app
├── redis
│ ├── redis.conf
│ └── data
├── nginx
│ ├── nginx.conf
│ └── conf.d
└── scripts
├── deploy.sh
├── rollback.sh
└── backup.sh
这种结构清晰直观,便于备份、迁移和排查问题。
十八、一个简单的部署脚本示例
#!/bin/bash
set -e
APP_DIR="/opt/app"
IMAGE="registry.example.com/app-server"
VERSION="$1"
if [ -z "$VERSION" ]; then
echo "Usage: ./deploy.sh "
exit 1
fi
cd $APP_DIR
echo "Deploying version: $VERSION"
sed -i "s#image: ${IMAGE}:.*#image: ${IMAGE}:${VERSION}#g" docker-compose.yml
docker compose pull
docker compose up -d
docker compose ps
echo "Deploy finished."
实际生产中,部署脚本还应增加:
- 发布前备份;
- 健康检查;
- 发布失败自动回滚;
- 操作日志记录;
- 通知机制。
结语
Docker 在生产环境中不是简单地把应用“装进容器”就完成了。真正可靠的 Docker 生产部署,需要从宿主机、镜像、运行参数、网络、存储、日志、监控、安全、发布回滚等多个方面进行系统设计。
从生产实测经验来看,Docker 本身足够稳定,大多数事故并不是 Docker 引起的,而是因为部署规范缺失:日志没有限制、资源没有隔离、数据没有持久化、版本不可追踪、配置和密钥管理混乱、监控告警缺位。
因此,生产环境使用 Docker 的关键不是“会不会启动容器”,而是能否建立一套稳定、可审计、可回滚、可监控、可持续演进的容器化部署体系。
如果你的团队刚开始在生产环境使用 Docker,建议先从单机 Compose 标准化部署做起,逐步完善镜像构建、日志收集、监控告警、安全扫描和自动化发布。当服务规模继续扩大、发布频率提高、机器数量增多时,再进一步引入 Kubernetes、服务网格、GitOps 等更复杂的云原生体系。这样既能控制复杂度,也能让 Docker 真正成为提升交付效率和系统稳定性的基础设施。