Dockerfile 是"做奶茶的配方"?用 todos 全栈项目看懂 Docker 构建与发布

Dockerfile 是"做奶茶的配方"?用 todos 全栈项目看懂 Docker 构建与发布

上篇文章我们讲了一条请求在 Docker 里怎么走(nginx 反向代理 + 端口映射),评论区不少朋友说想看"怎么把一个项目真正发布成 Docker 镜像"。这篇就接着来。

先看一句话:

蜜雪冰城的 SOP:先加奶、再加奶、放 3 勺糖、摇匀。 任何人照着做,出来的味道都一样,就成了连锁店。

Dockerfile 就是这份 SOP 的程序化版本。

它是一个纯文本的"配方文件",里面按步骤写着"怎么做菜"。Docker 拿到这个文件,就能自动做出一模一样的镜像------不靠人记忆、不靠口口相传,任何人、任何电脑,产出完全一致。

这篇文章用一个真实的 todos 全栈项目(前端 React + 后端 NestJS + nginx),带你把它从头构建成镜像、再发布的完整流程。


一、Dockerfile:一份能自动执行的食谱

普通的菜谱是给人看的,Dockerfile 是给 Docker 引擎 读的。它长这样:

dockerfile 复制代码
# 1. 选一个基础镜像:上面装好了 Node 环境
FROM node

# 2. 在容器里切到 /app 文件夹,设为工作目录
WORKDIR /app

# 3. 复制"原料"(代码)进来
COPY package*.json ./

# 4. 按步骤"下锅":安装依赖
RUN npm install

# 5. 把剩余代码全部拷进容器
COPY . .

# 6. 告诉 Docker 这个容器该暴露哪个端口
EXPOSE 3000

# 7. 上桌:容器启动时执行这个命令
CMD ["node", "server.js"]

每一行都是一个指令,从上到下顺序执行,就像配方里"先加奶、再加奶、放 3 勺糖、摇匀"。

Dockerfile 指令 类比蜜雪 SOP
FROM 选什么牌子的原料 / 锅底
WORKDIR 在工作台上切菜(切到哪个目录)
COPY 把准备好的食材放进锅
RUN 下锅烹饪(执行命令)
EXPOSE 告诉打包箱这个服务会开哪个窗口
CMD 上桌要做什么(容器启动命令)

记法FROM 起头,COPY/RUN 中间干活,CMD 收尾启动。看到 Dockerfile 先找这三个,结构就清楚了。


二、构建到发布:四步走

把食谱变成真正能跑起来的服务,核心就这 4 条命令:

bash 复制代码
docker build -t my-todos .   # 1. 按 Dockerfile 构建镜像
docker login                  # 2. 登录镜像仓库(比如 Docker Hub / 私有仓库)
docker push my-todos          # 3. 把镜像推到远程仓库
docker pull my-todos          # 4. 在别的机器上拉下来跑

一条条拆开看:

docker build -t my-todos .

-t 给镜像起名字,末尾的 . 表示"用当前目录下的 Dockerfile"。

text 复制代码
docker build -t 镜像名 .
   ↑ 名字中间是命令     ↑ 找当前目录的 Dockerfile

理解点 :build 不是"看一遍文件",而是把 Dockerfile 的每一条指令执行一遍,每一层都生成一个缓存层。所以改个小配置再 build 会很快------只有变化的层会重做。

docker login → ③ docker push

镜像构建好后,光在本地没用。要发布给团队 / 服务器用,就推去镜像仓库(类似 git 的远程仓库,但是装的是"光盘"而不是代码)。

bash 复制代码
docker login      # 登录仓库,验证身份
docker push my-todos   # 把本地镜像上传到仓库

上传之后,任何能访问仓库的人 / 机器都能 docker pull my-todos 拉下来。

docker pull + 跑起来

把仓库里的镜像拉下来,run 一个容器就完成了部署:

bash 复制代码
docker pull my-todos
docker run -p 3000:3000 my-todos

Dockerfile 是发布项目的标准方式之一 ------它的价值在于:"构建一次,到处运行"。 本地开发环境、测试环境、生产环境,都是同一个镜像,杜绝"我这能跑你那不能跑"。


三、实战:todos 全栈项目怎么组成镜像

前面是理论,来看我们真实的 todos 项目长什么样。它是一个三件套:

text 复制代码
todos-fullstack/
├── todos/          # 前端:React + TypeScript + zustand
├── todos-backend/  # 后端:NestJS + Todo Module
└── (nginx)         # 反向代理:把前端 80 → 后端 3000

项目架构非常典型:

text 复制代码
浏览器
  │  :80
  ▼
nginx(反向代理)
  │  转发
  ▼
NestJS 后端  :3000
  ▲
  └── Todo Module(CRUD:增删改查)

每个部分都有自己的 Dockerfile

前端(React + Vite):

dockerfile 复制代码
FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build          # 打包出静态文件

FROM nginx
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 80

后端(NestJS):

dockerfile 复制代码
FROM node:20
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build          # 编译 TS 到 dist
EXPOSE 3000
CMD ["node", "dist/main.js"]

跨域问题在这套架构里怎么解

前端请求后端时,浏览器会拦跨域(前端是 80,后端是 3000,端口不同 = 跨域)。两种主流解法:

解法一:Nginx 反向代理(推荐,生产用)前端和后端同源------所有请求都走 80,由 nginx 转发:

nginx 复制代码
server {
    listen 80;
    location /api/ {
        proxy_pass http://backend:3000;   # 80 → 3000,前端无感知
    }
}

同源后浏览器根本不认为在跨域,一劳永逸。

解法二:后端开启 CORS(开发用) 后端直接允许跨域,适合本地调试。NestJS 里一行:

typescript 复制代码
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors({ origin: true, credentials: true });
  await app.listen(3000);
}

记住:生产环境多用 Nginx 反代(同源),开发环境可以偷懒用 CORS。 两者目的都是解决跨域,只是放的位置不同。


四、命令速查

收尾把构建发布相关的命令整理成一张表:

命令 作用 类比
docker build -t 名 . 按 Dockerfile 构建镜像 按 SOP 做了一杯奶茶
docker login 登录镜像仓库 出示员工卡进连锁店后台
docker push 名 镜像推到远程仓库 把配方发到总部中央库
docker pull 名 从仓库拉镜像 从总部取配方
docker run 名 跑一个容器 照配方做一杯递给你
docker images 看本地镜像列表 看看库存

结尾

回到开头那个比喻:

Dockerfile 就是蜜雪冰城的 SOP。 它把"怎么从零做一个能跑的 todo 应用"写成了人(引擎)能照做的指令:FROM 选环境 → COPY 拷代码 → RUN 装依赖 → CMD 启动。构建成镜像后 push / pull,就完成了标准化发布。

一句话收尾:

Dockerfile 是发布项目的标准方式之一------把"我这能跑"变成"哪都能跑"。

留个问题给你:我们演示的后端用 node dist/main.js 直接跑。如果要把后端也压进 nginx 反代后面的容器网络 里(而不是端口直通),proxy_pass 应该写 localhost 还是别的?这正好衔接上一篇的 Docker 网络话题,欢迎评论区聊聊。


相关推荐
March.s5 小时前
Docker 容器技术完全指南:从生态到架构的深度解析
docker
wdfk_prog6 小时前
ROS教程:01 搭建 ROS1 Noetic Docker 开发环境
运维·缓存·docker·容器·ros
szephyr7 小时前
Docker + Compose 实战:把本地项目一键搬到云服务器,附完整配置
运维·docker·容器·部署·云服务器
wdfk_prog7 小时前
ros教程02:创建 catkin Workspace、Package 与第一个 ROS1 C++ Node
运维·缓存·docker·容器·ros
名字还没想好☜11 小时前
Docker 网络实战:bridge/host/none/自定义网络、容器互通与端口映射踩坑
运维·docker·kubernetes
迷茫的大专生11 小时前
高可用总结
redis·mysql·nginx·高可用
March.s13 小时前
Docker 容器与网络深度指南:从入门到精通
docker
探索云原生13 小时前
KubeClipper 1.7.0 发布:Operation 优化与 Kubernetes 1.37 支持
linux·docker·云原生·kubernetes·go
szephyr13 小时前
Nginx 反向代理实战:从 502、跨域到 HTTPS,一次打通
运维·nginx·https·部署·反向代理
迷茫的大专生13 小时前
服务器部署与 MySQL 运维学习笔记:从 Ansible 到高可用与性能优化
mysql·nginx·ansible·mysql优化·keepalive