很多刚接触容器化的朋友,对 Docker 的理解还停留在“把它跑起来”的阶段。如果你只是在自己的笔记本上敲一行 docker run 看着日志滚动,那确实没什么好讲的。但一旦要把项目放到公网服务器上,面对真实的流量、复杂的依赖和潜在的安全风险,事情就完全不一样了。
笔者在早期运维自己的几个小项目时,也犯过直接把开发环境的 Dockerfile 丢到生产服务器上构建的错。结果就是镜像体积臃肿、构建速度慢如蜗牛,甚至因为权限问题被扫库。今天我们不聊虚的,直接从架构师的视角,把“Docker 怎么部署项目”这件事拆解成一套可落地的 SOP(标准作业程序)。
一、核心原理:为什么你的 Docker 部署总是“水土不服”?
💡 推荐阅读:Docker 容器又双叒叕起不来?别慌,这几个高频报错我帮你踩平了
Docker 部署的本质,是利用 Linux 内核的 Namespace(命名空间) 做资源隔离,用 Cgroups(控制组) 做资源限制。很多站长部署失败,根源在于混淆了“镜像”和“容器”的生命周期。
一个最常见的误区是:把数据库数据和代码一起打包进镜像。这会导致每次更新代码,数据库就被重置一次。正确的姿势是数据与计算分离:镜像只包含无状态的应用程序和运行时环境,所有的状态(数据库文件、用户上传的附件、日志)必须通过 Volume(数据卷) 挂载到宿主机。
二、生产环境架构规范:别再用 Root 跑容器了
💡 延伸阅读:服务器半夜被挖矿、SSH 被暴力破解?聊聊我踩坑多年的 Linux 安全体检清单
在测试环境,我们习惯用 root 用户或者默认的 root 组运行容器,因为方便。但在生产环境,这是大忌。一旦容器内的进程被提权,宿主机就裸奔了。
1. 镜像构建规范:多阶段构建是底线
不要在一个 Dockerfile 里既装编译工具链,又跑应用。这会让你的镜像动辄 1GB 以上,拉取时极其痛苦。以 Node.js 项目为例,标准的生产级 Dockerfile 应该长这样:
# 第一阶段:构建层
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# 第二阶段:运行层
FROM node:18-alpine
WORKDIR /app
# 创建一个非 root 用户
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/main.js"]
这个配置里有两个关键点:多阶段构建 让最终镜像只包含运行时必需的文件;非 Root 用户 限制了容器逃逸后的破坏范围。
2. 网络架构规范:自定义 Bridge 网络
不要用默认的 docker0 网络。默认网络下,容器之间只能通过 IP 通信,重启后 IP 变化会导致连接失败。笔者建议为每个项目创建一个独立的 自定义 Bridge 网络。
docker network create my-app-net
这样,你的应用容器和数据库容器都加入 my-app-net 后,就可以直接用容器名(如 db)作为主机名互相访问,DNS 解析由 Docker 内置服务自动完成。
三、性能与安全加固调优清单
💡 深度技术指南:Docker 跑开发环境到底香不香?一份来自老站长的深度选型对比笔记
当项目跑起来之后,如果不做资源限制,一个内存泄漏的 Bug 就能让整台服务器宕机。以下参数是笔者在每台生产服务器上都会强制配置的:
| 调优维度 | 配置项 | 推荐值 / 说明 |
|---|---|---|
| 资源限制 | --memory / --cpus |
根据应用实际峰值设置,建议内存限制为物理内存的 70%,防止 OOM 导致宿主机卡死。 |
| 日志轮转 | --log-opt max-size |
必须设置,例如 max-size=10m。否则容器日志会无限增长,直到撑爆磁盘。 |
| 重启策略 | --restart |
生产环境推荐 unless-stopped,避免服务器重启后容器不自动拉起。 |
| 只读文件系统 | --read-only |
对于纯静态或 API 服务,开启后容器内无法写入任何文件,极大提升安全性。 |
此外,如果你打算在公网暴露服务,千万不要直接映射 3306 或 6379 端口。数据库容器应该只加入内部网络,不对外暴露任何端口。所有外部流量必须经过 Nginx 反向代理或 API 网关。
四、实战 SOP:从代码提交到上线验证
有了上面的规范,我们梳理一套标准的上线流程。这里以一台全新的云服务器为例。顺便提一句,如果你还在用那些超售严重的廉价 VPS,Docker 的隔离层反而会放大 IO 瓶颈,建议选择大厂云服务器,磁盘 IOPS 有保障,跑容器才稳。
步骤 1:环境初始化
# 安装 Docker 引擎(官方脚本)
curl -fsSL https://get.docker.com | sh
# 将当前用户加入 docker 组,避免每次 sudo
sudo usermod -aG docker $USER
# 配置 Docker 守护进程日志轮转(全局)
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
EOF
sudo systemctl restart docker
步骤 2:构建与推送镜像
不要在服务器上直接 git clone 然后 docker build,这会占用服务器宝贵的 CPU 和内存。正确做法是在本地或 CI/CD 流水线中构建好镜像,推送到镜像仓库(如 Docker Hub、阿里云 ACR),服务器只负责 docker pull。
步骤 3:使用 Docker Compose 编排
虽然 docker run 能跑,但参数一多就容易漏。生产环境强烈建议使用 docker-compose.yml 管理。一个典型的 Web 应用编排文件如下:
version: '3.8'
services:
web:
image: registry.example.com/my-app:latest
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000" # 只监听本地,由 Nginx 转发
environment:
- NODE_ENV=production
- DB_HOST=db
networks:
- app-net
deploy:
resources:
limits:
memory: 512M
db:
image: postgres:15-alpine
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=your_strong_password
networks:
- app-net
volumes:
pgdata:
networks:
app-net:
driver: bridge
步骤 4:验证测试与健康检查
启动后,不要只看 docker ps 显示 Up 就以为万事大吉。你需要做三层验证:
- 进程层:
docker logs --tail 50 web查看是否有报错堆栈。 - 网络层:
docker exec web curl -I http://localhost:3000/health确认应用内部健康检查通过。 - 外部层:通过域名访问,确认 Nginx 转发正常,HTTPS 证书有效。
最后,记得配置域名解析。笔者见过不少站长,服务器部署得完美无缺,结果域名没备案或者 DNS 解析到了旧 IP,导致用户根本访问不了。选择靠谱的域名注册商和云解析服务,能省去很多不必要的麻烦。
五、总结
Docker 部署项目不是一锤子买卖,而是一套持续优化的工程实践。记住三个核心原则:镜像最小化、权限最小化、数据外部化。把 Dockerfile 当作代码来维护,把 docker-compose.yml 当作架构图来审视。当你把这些规范变成肌肉记忆后,你会发现,把项目部署到任何一台新服务器上,都只是几分钟的事情。