摘要

还在手动 SSH 登录服务器部署项目?Out 了!本文分享 Docker 容器化部署的完整实战经验,包括镜像构建优化、多阶段构建技巧、容器网络配置、数据持久化方案和 CI/CD 集成。每个坑都是真金白银换来的,帮你从"部署两小时"变成"发布五分钟"。

图片


开篇引入

说实话,三年前我第一次接触 Docker 的时候,内心是拒绝的。

"不就是个打包工具吗?能有多复杂?"

结果第一个生产环境部署,我就栽了个大跟头。凌晨两点,容器起不来,日志全是报错,用户访问 502,老板在群里@我:"什么时候能好?"

那一晚我明白了:容器化不是把代码塞进镜像就完事了,这里面的门道,够写一本书。

三年过去,我从手动 SSH 部署进化到一键发布,从单容器到 Kubernetes 集群,踩过的坑没有 100 也有 80。今天把最痛的 7 个坑整理出来,希望能帮你少走点弯路。

图片


坑一:镜像构建又慢又大

问题场景

刚开始写 Dockerfile,我是这么干的:

FROM node:18
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main.js"]

看着挺简洁对吧?实际问题一大堆:

  • 构建慢:每次改一行代码,npm install 重新跑一遍,10 分钟起步

  • 镜像大:基础镜像 + node_modules + 构建工具,轻松 1.5GB+

  • 安全隐患:生产镜像里塞满了开发依赖

解决方案:多阶段构建

后来我学乖了,用多阶段构建:

# 构建阶段
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
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./

ENV NODE_ENV=production
EXPOSE3000
CMD ["node", "dist/main.js"]

效果对比

指标

优化前

优化后

构建时间

10 分钟

2 分钟

镜像大小

1.5GB

180MB

启动速度

30 秒

5 秒

关键技巧

  1. **用 npm ci 代替 npm install**:ci 是 clean install 的缩写,专门给 CI/CD 设计的,速度更快且保证依赖版本一致

  2. 先复制 package.json:利用 Docker 层缓存,依赖不变时直接跳过安装

  3. 用 Alpine 基础镜像:体积小、安全,但注意有些包可能需要额外编译依赖


坑二:容器网络配置混乱

问题场景

"为什么容器里访问不到数据库?"

"为什么两个容器互相 ping 不通?"

这是我被问得最多的两个问题。Docker 网络模型确实有点反直觉,我刚开始也懵。

解决方案:理解三种网络模式

1. bridge 模式(默认)

每个容器有独立 IP,通过端口映射对外暴露:

docker run -p 3000:3000 my-app

适合单机部署,简单直接。

2. host 模式

容器直接用宿主机网络,性能最好但隔离性差:

docker run --network host my-app

我一般只在性能敏感场景用这个,比如高频交易系统。

3. overlay 网络(多机通信)

多容器协作时,创建自定义网络:

docker network create app-network

docker run -d --network app-network --name db postgres
docker run -d --network app-network --name api my-api

这样 api 容器里直接用 db 做主机名就能访问数据库,不用记 IP,容器重启也不影响

血泪教训

有一次生产事故,我把数据库容器和 API 容器放在不同网络里,死活连不上。排查两小时,最后发现是网络配置问题。

建议:用 docker network inspect <network-name> 随时检查网络拓扑,别靠猜。


坑三:数据持久化没做好

问题场景

"容器删了,数据也没了?"

对,默认情况下容器是"用完即扔"的,数据存在容器层,容器一删全没了。

解决方案:三种持久化方案

1. Volume(推荐)

Docker 管理的持久化存储:

docker volume create db-data
docker run -v db-data:/var/lib/postgresql/data postgres

优点:跨容器共享、备份方便、性能好

2. Bind Mount

直接挂载宿主机目录:

docker run -v /home/user/data:/app/data my-app

优点:开发调试方便,直接改宿主机文件

缺点:依赖宿主机路径,迁移麻烦

3. tmpfs

内存存储,重启就丢:

docker run --tmpfs /app/tmp my-app

适合存临时文件、session 数据。

我的选择

生产环境:Volume

开发环境:Bind Mount(改代码即时生效)

敏感数据:加密 Volume + 定期备份


坑四:环境变量管理混乱

问题场景

"测试环境配置怎么跑到生产了?"

"API 密钥怎么提交到 Git 了?"

环境变量管理不好,轻则配置错误,重则密钥泄露。

解决方案:分层管理

1. .env 文件(开发环境)

# .env.local
DATABASE_URL=postgres://localhost:5432/dev_db
API_KEY=dev_key_123

注意.env.local 要进 .gitignore,别提交!

2. Docker Compose 环境变量

version: '3.8'
services:
  api:
    image: my-api
    env_file:
      - .env.production
    environment:
      - NODE_ENV=production

3. 密钥管理(生产环境)

用 Docker Swarm Secrets 或 Kubernetes Secrets:

echo "my_secret_password" | docker secret create db_password -

核心原则

  • 开发配置本地化,不提交代码

  • 生产配置自动化,走 CI/CD 流水线

  • 密钥加密存储,不写死在代码里


坑五:日志收集不到位

问题场景

"线上出问题了,去哪看日志?"

"容器重启了,之前的日志还能查到吗?"

默认情况下,Docker 日志存在容器里,容器删了就没了。

解决方案:集中式日志收集

方案一:docker logs + 日志驱动

docker run --log-driver=json-file --log-opt max-size=10m my-app

限制单个日志文件大小,避免撑爆磁盘。

方案二:ELK 栈(推荐)

version: '3.8'
services:
api:
    image:my-api
    logging:
      driver:fluentd
      options:
        fluentd-address:localhost:24224

fluentd:
    image:fluent/fluentd
    ports:
      -"24224:24224"

elasticsearch:
    image:elasticsearch:8.11.0

kibana:
    image:kibana:8.11.0

方案三:云服务商日志服务

阿里云 SLS、腾讯云 CLS、AWS CloudWatch,省心但贵。

我的建议

小项目:docker logs + 日志轮转

中大型项目:ELK 或云日志服务

关键指标:日志保留至少 30 天,支持关键词搜索和告警。


坑六:健康检查没配置

问题场景

"容器明明挂了,为什么 Docker 还显示 running?"

因为进程还在,只是业务逻辑崩了。Docker 不知道,以为一切正常。

解决方案:配置 HEALTHCHECK

FROM node:18-alpine
WORKDIR /app
COPY . .

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1

CMD ["node", "dist/main.js"]

参数解释

  • --interval=30s:每 30 秒检查一次

  • --timeout=3s:超时 3 秒算失败

  • --start-period=40s:启动后 40 秒内不检查(给启动时间)

  • --retries=3:连续 3 次失败才标记 unhealthy

配合 Docker Compose 自动重启

services:
  api:
    restart: always
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      retries: 3

这样容器挂了会自动重启,不用半夜起来救火


坑七:CI/CD 集成没打通

问题场景

"每次发布都要手动打包、手动上传、手动重启?"

这都 2026 年了,真没必要。

解决方案:GitHub Actions 自动化

name: Deploy

on:
push:
    branches:[main]

jobs:
build-and-deploy:
    runs-on:ubuntu-latest
    steps:
      -uses:actions/checkout@v4
      
      -name:BuildDockerImage
        run:dockerbuild-tmy-app:${{github.sha}}.
      
      -name:PushtoRegistry
        run:|
          docker login -u ${{ secrets.DOCKER_USER }} -p ${{ secrets.DOCKER_PASS }}
          docker push my-app:${{ github.sha }}
      
      -name:DeploytoServer
        uses:appleboy/ssh-action@master
        with:
          host:${{secrets.SERVER_HOST}}
          username:${{secrets.SERVER_USER}}
          key:${{secrets.SSH_KEY}}
          script: |
            docker pull my-app:${{ github.sha }}
            docker stop api || true
            docker rm api || true
            docker run -d --name api my-app:${{ github.sha }}

效果

  • 提交代码 → 自动构建 → 自动部署

  • 全程 5 分钟,不用人工干预

  • 出问题了?git revert 一键回滚


总结

三年容器化实践,我最深的体会是:工具只是手段,稳定高效才是目的。

Docker 不是银弹,它解决了一些问题,也带来了一些新问题。但总体来说,从手动运维到容器化,效率提升是实实在在的

最后给几个建议:

  1. 从小项目开始练手,别一上来就搞生产环境

  2. 多读官方文档,很多坑官方都给了最佳实践

  3. 监控和日志要提前配好,别等出问题了再补

  4. 定期更新基础镜像,安全补丁不能省


互动时间

你在容器化部署中踩过哪些坑?欢迎在评论区分享,我挑三个最痛的坑,下期专门写文章分析解决方案。

觉得有用? 点赞 + 在看,让更多开发者少踩点坑。

关注我,下期聊《Kubernetes 入门:从 Docker 到 K8s,我的迁移实战》。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐