为什么要花力气把项目塞进 Docker?
很多团队在项目初期图省事,直接在服务器上装环境、跑进程。刚开始确实爽,但一旦涉及多台服务器、频繁发版或者环境迁移,痛点就全冒出来了:明明本地跑得好好的,一到服务器就各种幺蛾子;升级个依赖库,老服务直接罢工。Docker 容器化部署的核心价值,就是把应用连同它的运行环境一起打包,做到“一次构建,到处运行”。
这篇文章,笔者就带着大家从零开始,把一套标准的项目 Docker 部署流程完整走一遍。文章不会讲太多虚头巴脑的理论,全部是实操命令和踩坑经验,建议收藏备用。
第一步:基础镜像与 Dockerfile 的编写
一切容器化的起点,是写一个合格的 Dockerfile。很多人觉得随便写写能跑就行,但一个结构不合理的镜像,轻则构建缓慢,重则漏洞百出。
1.1 选择合适的基础镜像
以最常见的 Node.js 和 Java 项目为例。基础镜像不要盲目追求“大而全”,alpine 系列体积小、攻击面小,是生产环境的首选。但要注意,alpine 使用的是 musl libc,部分依赖原生模块的项目可能会编译报错,这时候可以退而求其次选择 slim 版本。
# Node.js 项目示例
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
这里使用了多阶段构建,第一阶段只负责安装依赖,第二阶段只拷贝必要的产物。这样最终镜像里不会残留编译工具链,体积能小一半以上。
1.2 注意 .dockerignore 文件
不少新手会忽略这个文件。没有它,COPY . . 会把本地的 node_modules、.git 目录统统打进去,不仅构建慢,还可能把本地特有的配置泄露到镜像里。务必在项目根目录创建 .dockerignore:
node_modules
.git
*.log
.env
Dockerfile
.dockerignore
第二步:镜像构建与私有仓库推送
Dockerfile 写好后,就该构建镜像了。但请注意,生产环境千万不要直接使用 Docker Hub 的公共仓库来存放业务镜像,一方面速度不稳定,另一方面存在安全隐患。建议使用云服务商提供的镜像仓库服务,或者自建 Harbor。
# 构建镜像,注意最后的点代表当前目录
docker build -t registry.yourcompany.com/project/web:v1.2.0 .
# 登录私有仓库
docker login registry.yourcompany.com
# 推送镜像
docker push registry.yourcompany.com/project/web:v1.2.0
这里强烈建议打上语义化版本号,而不是偷懒用 latest 标签。一旦线上出问题,你需要能够快速回滚到上一个具体版本,latest 是做不到这一点的。
第三步:docker-compose 编排多服务依赖
现在的项目很少是单体的,通常前面挂 Nginx,后面连 MySQL、Redis。如果一个个 docker run 去启动,管理起来会非常痛苦。docker-compose.yml 就是用来解决这个问题的。
version: '3.8'
services:
web:
image: registry.yourcompany.com/project/web:v1.2.0
ports:
- "3000:3000"
environment:
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
restart: always
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/conf.d:/etc/nginx/conf.d
- ./ssl:/etc/nginx/ssl
depends_on:
- web
restart: always
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
restart: always
volumes:
mysql_data:
注意看,这里用了 restart: always 策略,保证服务器重启后服务能自动拉起。对于数据库这类有状态服务,一定要声明 volumes 做持久化,否则容器一删,数据全没了。
提到 Nginx,如果你在配置反向代理时经常遇到 304 状态码,不清楚是缓存命中还是配置有问题,建议读一读我们之前的这篇实战分析:Nginx 反向代理频繁出现 304 状态码?别慌,这篇文章帮你彻底搞懂它,能少走很多弯路。
第四步:服务器环境初始化与安全基线
镜像准备好了,编排文件也写好了,接下来就是把项目部署到一台全新的云服务器上。这里笔者多说一句,购买云服务器时,千万别只看 CPU 核心数和内存大小,磁盘 IO 和网络带宽往往才是瓶颈。之前我们专门写过一篇关于“高分低能”服务器的测评陷阱:云服务器测评分数高,网站却卡成狗?测评与调优的正确打开方式,别再本末倒置,建议入手服务器前先看一眼。
服务器拿到手,第一件事不是装 Docker,而是做安全加固。修改 SSH 默认端口、禁用 root 密码登录、配置防火墙白名单,这些基础操作必须做扎实。更详细的防护清单,可以参考我们的这篇实战总结:Linux 服务器安全防护到底要做哪些?这份实战清单帮你堵住 90% 的漏洞。
第五步:部署上线与健康检查
环境准备好后,在服务器上安装 Docker 和 Compose 插件,然后拉取代码或直接拉取镜像,执行启动命令:
# 拉取项目编排文件
git clone your-project-repo.git
cd your-project-repo
# 启动所有服务(后台运行)
docker compose up -d
# 查看服务状态
docker compose ps
# 查看实时日志
docker compose logs -f
启动完成后,一定要做健康检查。不要只看容器状态是 Up 就完事了,很多服务启动失败后进程还在,容器依然显示 Up。建议在 Compose 文件中为每个服务配置 healthcheck 指令,或者用 curl 请求业务接口的 /health 端点来验证。
# 手动模拟健康检查
curl -I http://localhost:3000/health
如果返回 200,说明服务正常。如果返回 502 或超时,就要去查日志了,看看是不是数据库连接串写错了,或者端口映射冲突。
第六步:持续迭代与版本回滚
部署上线不是终点。后续每次更新代码,只需要重新构建镜像并推送,然后在服务器上执行:
# 拉取最新镜像并滚动更新
docker compose pull
docker compose up -d
# 如果发现新版本有问题,回滚到上一个版本
docker compose down
docker compose up -d --no-deps web:v1.1.0
这种部署方式的优势在于,回滚成本极低。只要镜像仓库里保留着历史版本,随时可以秒级切回。这也是容器化相比传统部署方式最大的优势之一。
关于容器化部署的性能问题,很多人担心 Docker 会有额外的性能损耗。其实在 CPU 和内存层面,容器几乎是无感的,真正的损耗主要在磁盘 IO 和网络转发上。我们之前做过一组详细的实测数据对比:Docker容器化部署到底快不快?性能损耗实测与生产环境调优全解析,看完你就知道该怎么针对性地调优了。
写在最后的一点建议
容器化部署的坑其实不少,比如时区问题、日志收集问题、容器内 crontab 不生效的问题,这些都需要在实际项目中慢慢积累。但无论如何,把环境标准化、把部署流程化,是每个走向成熟的团队必须迈过的一道坎。
如果你用的还是笨重的物理机部署,或者每次上线都要手动敲十几条命令,不妨从今天开始,尝试把第一个服务容器化。哪怕只是一个内部小工具,跑通了流程,后面的路就顺了。另外,如果项目是基于 WordPress 搭建的,容器化之后性能优化同样不能忽视,可以参考这份清单:WordPress 打开慢如蜗牛?这份性能优化加速实战清单,让你的网站飞起来。
🎁 全套实操配置文件与 AI 提效资料包免费下载
本文涉及的全套 Docker Compose 配置文件、服务器运维避坑手册及 AI 提效指令库已完整打包上传至夸克网盘,可直接免费极速转存:
💡 提示:推荐使用手机【夸克网盘 App】打开保存,新用户首月免费赠送 1TB 超大空间与免流量极速下载特权。