从零搭 Docker 环境折腾了一整天,我悟了

故事的开始:一个"简单"的需求

事情是这样的:老板说"把咱们的项目容器化一下"。我心想,不就是 Docker 吗?装个环境而已,能有多难?

然后我从早上九点折腾到晚上九点,期间摔了三次键盘、喝了五杯咖啡、骂了十几次娘。最终成功了,但头发掉了不少。

第一关:Dockerfile 怎么写?

先搭个 Nginx + Node.js 的环境。听起来简单,但我第一次写的 Dockerfile 长这样:

FROM node:16
FROM nginx:latest
# 等等,两个 FROM 是啥意思???

好家伙,多阶段构建不是这么用的啊!查了半天才搞明白,正确的做法是分阶段:

# 第一阶段:构建
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# 第二阶段:运行
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

看到没?两个 FROM 是先后关系,不是并列关系。第一个阶段编译,第二个阶段跑。这样最终镜像只有几十 MB,而不是几个 G。

第二关:Docker Compose 的毒打

单容器没问题了,但项目需要 Nginx + Node.js 后端 + 数据库。手动启动三个容器?你是原始人吗?上 docker-compose.yml

version: '3'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - node-app

  node-app:
    build: ./app
    ports:
      - "3000:3000"
    environment:
      - DB_HOST=db
    depends_on:
      - db

  db:
    image: postgres:13
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: secret

volumes:
  pgdata:

这里最大的坑是什么?网络。一开始我写了 localhost:5432 连数据库,死活连不上。后来才知道,Docker Compose 里服务之间要用服务名做 hostname,也就是 db:5432。这个坑我踩了半小时。

第三关:卷挂载,数据持久化的救星

容器一重启数据就没了?那你做个寂寞。卷挂载(volume mount)要刻进 DNA 里:

volumes:
  - ./data:/app/data        # 绑定挂载,本地目录
  - app_data:/app/uploads   # 命名卷,Docker 管理

这两个的区别是:绑定挂载你在本地能直接看到文件,适合开发;命名卷 Docker 帮你管,适合生产。选错了后果很严重——生产环境用绑定挂载,迁移的时候你就哭吧。

我悟了:Docker 的核心是"不可变基础设施"

折腾了一整天,我终于理解了 Docker 的精髓:一次构建,到处运行。你本地跑起来了,到服务器上也能跑,到同事电脑上也能跑。"在我电脑上是好的"——这句话以后不再是个梗了。

当然,Docker 也有坑:镜像层缓存。不改 package.json 就别重新 npm install 啊,放过你的 CI 吧!

# 正确的 layer 缓存姿势
COPY package*.json ./   # 先复制依赖文件
RUN npm install          # 缓存这一层
COPY . .                 # 再复制源码

顺序不对,每次构建都重新 npm install,你说气不气?

结尾

所以啊,Docker 不难,但细节多。第一次搞肯定要踩坑,但踩过一次就记住了。最后送大家一句话:容器化之前先想清楚你要解决什么问题——别为了 Docker 而 Docker,最后容器里面跑了个 curl 就完事了。

comments powered by Disqus