Docker 生产部署实战:从镜像构建到上线回滚的完整模板
Docker 生产环境部署指南|附源码
在很多团队中,Docker 早已不只是“开发环境一致性”的工具,而是生产环境交付、扩容、回滚和运维自动化的重要基础设施。相比传统部署方式,Docker 能把应用、运行时、依赖库、环境变量、启动命令等封装成统一镜像,从而减少“我本地能跑、线上跑不了”的问题。但要把 Docker 真正用于生产环境,不能只会写一个简单的 Dockerfile,还需要考虑镜像体积、安全权限、日志、网络、数据持久化、健康检查、资源限制、自动重启、灰度发布和故障回滚等一系列工程实践。
本文将从生产环境视角,系统讲解 Docker 部署的完整流程,并提供一套可直接参考的源码示例,包括 Dockerfile、docker-compose.yml、Nginx 配置、环境变量文件以及常用部署脚本。你可以根据自己的项目类型,将其中的示例改造成 Node.js、Java、Go、Python 或 PHP 项目的生产部署模板。
一、为什么生产环境要使用 Docker?
传统部署通常依赖服务器上提前安装好的运行环境。例如部署一个 Node.js 项目,需要服务器安装指定版本的 Node.js、npm、PM2、Nginx,还要配置环境变量、日志路径和系统服务。如果服务器迁移、扩容或重装,运维人员需要重新搭建一遍环境,过程繁琐且容易出错。
Docker 的核心价值在于:将应用运行所需的一切依赖打包成镜像,通过容器统一运行。生产环境使用 Docker 主要有以下优势:
-
环境一致性高
开发、测试、预发、生产环境可以使用同一个镜像,减少环境差异导致的问题。 -
部署速度快
构建完成镜像后,服务器只需要拉取镜像并启动容器即可,不需要重复安装复杂依赖。 -
回滚简单
每次发布都可以对应一个镜像版本,例如app:v1.0.0、app:v1.0.1。如果新版本异常,直接切回旧镜像即可。 -
便于扩容
容器天然适合横向扩展,可以快速启动多个实例,再通过反向代理或负载均衡分发流量。 -
隔离性更好
不同应用可以运行在不同容器中,依赖、端口、文件系统相对隔离,降低互相影响的风险。
不过,Docker 并不等于自动安全、自动高可用。生产环境仍然需要遵循规范,避免把开发环境的“随手写法”直接搬到线上。
二、生产环境部署的基本架构
一个常见的 Docker 生产部署架构如下:
用户请求
↓
Nginx / SLB / CDN
↓
Docker 容器中的应用服务
↓
数据库 / Redis / 对象存储 / 消息队列
在单机部署场景中,通常可以使用 docker compose 管理多个容器,例如:
app:业务应用容器nginx:反向代理容器redis:缓存容器,可选mysql:数据库容器,可选,但生产环境更推荐使用独立数据库服务prometheus/grafana:监控组件,可选
如果业务规模较大,可以升级到 Kubernetes、Docker Swarm 或云厂商容器服务。但对于中小型项目、企业后台、官网、接口服务而言,Docker + Docker Compose + Nginx 已经足够稳定可靠。
三、项目目录结构示例
下面是一套适合生产环境的基础目录结构:
docker-production-demo/
├── app/
│ ├── package.json
│ ├── package-lock.json
│ └── server.js
├── deploy/
│ ├── nginx.conf
│ ├── deploy.sh
│ └── rollback.sh
├── .env
├── Dockerfile
├── docker-compose.yml
└── README.md
这里以一个简单的 Node.js HTTP 服务为例。即使你的项目不是 Node.js,也可以参考它的部署思想:应用只负责提供服务,Nginx 负责对外暴露入口,配置通过环境变量注入,日志输出到标准输出,由 Docker 或宿主机统一采集。
四、示例应用源码
app/server.js:
const http = require('http');
const port = process.env.PORT || 3000;
const env = process.env.NODE_ENV || 'production';
const server = http.createServer((req, res) => {
if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', env }));
return;
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
message: 'Docker production demo is running',
env,
time: new Date().toISOString()
}));
});
server.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
app/package.json:
{
"name": "docker-production-demo",
"version": "1.0.0",
"description": "Docker production deployment demo",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {}
}
这个服务提供两个接口:
/:返回服务状态/health:用于 Docker 健康检查或负载均衡探活
生产环境一定建议提供健康检查接口。它可以帮助我们判断容器是否真正可用,而不仅仅是“进程还活着”。
五、编写生产可用的 Dockerfile
Dockerfile:
FROM node:20-alpine AS base
WORKDIR /app
COPY app/package*.json ./
RUN npm ci --omit=dev
COPY app/ .
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
USER node
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget -qO- http://127.0.0.1:3000/health || exit 1
CMD ["npm", "start"]
这个 Dockerfile 有几个关键点:
1. 使用轻量基础镜像
node:20-alpine 体积较小,适合大多数 Node.js 服务。镜像越小,传输越快,攻击面也相对更小。
2. 使用 npm ci
生产环境推荐使用 npm ci,它会严格按照 package-lock.json 安装依赖,保证依赖版本可复现。
3. 只安装生产依赖
--omit=dev 可以避免安装测试、构建工具等开发依赖,减少镜像体积。
4. 不使用 root 用户运行应用
USER node 能降低容器被攻击后的风险。很多初学者会忽略这一点,但在生产环境中,这是非常重要的安全实践。
5. 添加健康检查
HEALTHCHECK 可以让 Docker 自动判断容器健康状态。当应用无法响应 /health 时,容器会被标记为 unhealthy,便于运维排查和自动化处理。
六、编写 docker-compose.yml
docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile
image: docker-production-demo:${APP_VERSION:-latest}
container_name: docker-production-app
restart: unless-stopped
env_file:
- .env
expose:
- "3000"
networks:
- app-network
deploy:
resources:
limits:
cpus: "1.00"
memory: 512M
reservations:
memory: 128M
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
nginx:
image: nginx:1.27-alpine
container_name: docker-production-nginx
restart: unless-stopped
depends_on:
- app
ports:
- "80:80"
volumes:
- ./deploy/nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- app-network
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
networks:
app-network:
driver: bridge
这里使用了两个容器:app 和 nginx。应用容器不直接暴露宿主机端口,只通过内部网络提供 3000 端口;Nginx 作为统一入口,对外暴露 80 端口。
需要注意的是,deploy.resources 在普通 docker compose 场景下支持情况取决于 Docker 版本。如果资源限制没有生效,也可以改用:
mem_limit: 512m
cpus: 1.0
七、Nginx 反向代理配置
deploy/nginx.conf:
upstream app_backend {
server app:3000;
keepalive 32;
}
server {
listen 80;
server_name _;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
location /health {
proxy_pass http://app_backend/health;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
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_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
Nginx 配置中,server app:3000 的 app 是 Docker Compose 中定义的服务名。Docker 内部 DNS 会自动解析服务名,因此不需要写容器 IP。生产环境中不要依赖容器 IP,因为容器重建后 IP 可能变化。
如果需要 HTTPS,可以在 Nginx 前面接入云厂商负载均衡、CDN,也可以使用 Certbot 或 acme.sh 自动申请证书。
八、环境变量配置
.env:
NODE_ENV=production
PORT=3000
APP_VERSION=1.0.0
环境变量不要硬编码到代码中,更不要把数据库密码、密钥、Token 写进镜像。推荐做法是:
- 普通配置放
.env - 敏感配置使用云厂商密钥管理服务
- 或通过 CI/CD 平台注入环境变量
.env文件不要提交到公开仓库
在生产环境中,配置和镜像应当分离。镜像只描述应用如何运行,具体连接哪个数据库、使用什么密钥、开启哪些功能开关,应由部署环境决定。
九、部署脚本
deploy/deploy.sh:
#!/usr/bin/env bash
set -e
APP_VERSION=${1:-latest}
echo "Deploying version: ${APP_VERSION}"
export APP_VERSION=${APP_VERSION}
docker compose build app
docker compose up -d
docker compose ps
echo "Deployment completed."
使用方式:
chmod +x deploy/deploy.sh
./deploy/deploy.sh 1.0.0
如果你使用远程镜像仓库,例如 Docker Hub、阿里云 ACR、腾讯云 TCR 或 Harbor,部署流程通常是:
docker build -t registry.example.com/demo/app:1.0.0 .
docker push registry.example.com/demo/app:1.0.0
docker pull registry.example.com/demo/app:1.0.0
docker compose up -d
生产环境更推荐由 CI/CD 系统完成镜像构建和推送,服务器只负责拉取指定版本镜像并启动。
十、回滚脚本
deploy/rollback.sh:
#!/usr/bin/env bash
set -e
ROLLBACK_VERSION=$1
if [ -z "$ROLLBACK_VERSION" ]; then
echo "Usage: ./deploy/rollback.sh "
exit 1
fi
echo "Rolling back to version: ${ROLLBACK_VERSION}"
export APP_VERSION=${ROLLBACK_VERSION}
docker compose up -d
docker compose ps
echo "Rollback completed."
使用方式:
chmod +x deploy/rollback.sh
./deploy/rollback.sh 0.9.0
真正可靠的回滚前提是:每次发布都必须有明确版本号。不要长期使用 latest 作为生产版本,否则你很难确认当前线上运行的到底是哪一次构建产物。
十一、生产环境关键配置建议
1. 容器必须设置重启策略
推荐使用:
restart: unless-stopped
这样当 Docker 服务重启或容器异常退出时,容器可以自动恢复。相比 always,unless-stopped 更适合人工停止后不希望自动启动的场景。
2. 日志必须限制大小
如果不限制 Docker 日志,容器长期运行后可能撑满磁盘。建议配置:
logging:
driver: json-file
options:
max-size: "20m"
max-file: "5"
对于更复杂的生产环境,可以将日志统一采集到 ELK、Loki、云日志服务或 OpenTelemetry 平台。
3. 数据必须持久化
如果应用使用 MySQL、PostgreSQL、Redis 等有状态服务,必须配置 volume。否则容器删除后数据也会丢失。例如:
volumes:
mysql-data:
不过,生产环境数据库更建议使用独立云数据库或专门的数据库服务器,而不是和应用放在同一台机器的 Compose 文件里。这样更便于备份、扩容、容灾和权限管理。
4. 不要把密钥写进镜像
错误示例:
ENV DB_PASSWORD=123456
正确做法是运行时注入:
env_file:
- .env
或者使用 CI/CD、Kubernetes Secret、Docker Secret、云密钥管理系统等方式管理敏感信息。
5. 镜像要定期更新
基础镜像可能存在安全漏洞,因此不能多年不更新。建议定期执行镜像扫描,例如使用 Trivy:
trivy image docker-production-demo:1.0.0
发现高危漏洞后,应及时升级基础镜像或依赖包。
十二、上线前检查清单
在正式上线前,建议逐项确认以下内容:
- 应用是否提供
/health健康检查接口 - 容器是否配置
restart策略 - 日志是否配置大小限制
- 镜像是否使用明确版本号
- 是否避免使用 root 用户运行应用
.env是否未提交到公开仓库- 数据库、Redis 等数据是否持久化或使用独立服务
- Nginx 超时时间是否符合业务场景
- 端口是否只暴露必要服务
- 是否具备回滚方案
- 是否配置监控和告警
- 是否有定期备份策略
这份清单看似基础,但很多生产事故正是由这些细节遗漏引起的。例如磁盘被日志写满、容器异常退出后无人发现、误删容器导致数据丢失、使用 latest 镜像无法回滚等。
十三、常用运维命令
查看容器状态:
docker compose ps
查看应用日志:
docker compose logs -f app
查看 Nginx 日志:
docker compose logs -f nginx
重新构建并启动:
docker compose up -d --build
停止服务:
docker compose down
进入容器:
docker exec -it docker-production-app sh
查看镜像:
docker images
清理无用资源:
docker system prune
清理命令要谨慎使用,尤其是带 -a 参数时,可能删除未被容器使用的镜像。生产环境执行清理前,一定要确认不会影响回滚。
十四、CI/CD 发布流程建议
成熟的 Docker 生产部署一般不会在服务器上手动构建镜像,而是使用 CI/CD。一个推荐流程如下:
- 开发者提交代码到 Git 仓库
- CI 系统运行测试和代码检查
- 测试通过后构建 Docker 镜像
- 镜像使用 Git Tag 或 Commit SHA 作为版本号
- 推送镜像到私有镜像仓库
- CD 系统通知服务器拉取新镜像
- 执行
docker compose up -d - 检查健康状态
- 如果健康检查失败,自动回滚旧版本
版本号可以使用:
app:1.0.0
app:20250101-120000
app:git-a1b2c3d
其中 git-a1b2c3d 这类版本号非常适合排查问题,因为它能直接对应到某一次代码提交。
十五、总结
Docker 让生产环境部署变得更加标准化、自动化和可回滚,但它并不是简单地“把应用放进容器”就万事大吉。真正可靠的 Docker 生产部署,需要同时关注镜像构建、运行权限、配置管理、日志治理、健康检查、资源限制、网络隔离、数据持久化、安全扫描和发布回滚。
本文提供的示例虽然是一个简化版项目,但已经覆盖了生产部署中最核心的实践:使用非 root 用户运行容器、通过 Nginx 反向代理服务、使用 .env 管理配置、配置健康检查、限制日志大小、设置自动重启策略,并提供部署与回滚脚本。对于大多数中小型项目来说,这套方案可以作为上线模板;对于更复杂的系统,也可以在此基础上演进到 Kubernetes、服务网格、集中日志、链路追踪和自动伸缩架构。
如果你正在准备将项目容器化上线,建议不要一开始就追求复杂平台,而是先把基础规范做好:镜像可复现、配置可管理、日志可追踪、服务可探活、故障可回滚。只要这些关键能力具备,Docker 就能成为生产环境中非常可靠的部署底座。