单元五 Docker 发布与部署 · 第 19 课
容器化:把周报助手装进镜像
这一课把周报助手打包成镜像,在服务器上跑起来,再通过一条加密的“隧道”,在自己电脑的浏览器里打开它。
本课目标
- 让 AI 写 Dockerfile 和 .dockerignore,并看懂它们。
- 用
scp把项目传到服务器。 - 在服务器上构建镜像、运行容器。
- 用 SSH 隧道在自己电脑上访问服务器里的服务。
第一步:写 Dockerfile(在自己电脑上)
cd ~/ai-course/weekly-report
codex请为这个项目写 Dockerfile 和 .dockerignore,要求:
- 基础镜像用 node:24-alpine。
- 只复制运行需要的文件:package.json、server.js、public 文件夹。项目没有第三方依赖,不需要 npm install。
- 设置 NODE_ENV=production、HOST=0.0.0.0 和 PORT=3000,声明 3000 端口。容器里必须监听所有网卡,端口映射才能把请求送进来。
- 用镜像自带的普通用户 node 运行,不要用 root。
- 启动命令为 node server.js。
- .dockerignore 排除 .git、.env、node_modules 和日志文件,确保 Key 不会被打包进镜像。
- 每一段加一行中文注释说明作用。
另外检查一下 server.js:收到 SIGTERM 信号时要能正常退出,这样 docker stop 时不用等待 10 秒。
先说计划,确认后再写。写好的 Dockerfile 大致是这样(逐行说明见注释):
# 基础镜像:官方 Node.js 24,alpine 版体积小。
FROM node:24-alpine
WORKDIR /app
# 只复制运行需要的文件;密钥通过环境变量传入,不进镜像。
COPY package.json server.js ./
COPY public ./public
# 容器里要监听所有网卡,端口映射才能把请求送进来。
ENV NODE_ENV=production HOST=0.0.0.0 PORT=3000
EXPOSE 3000
# 用普通用户运行,不用 root。
USER node
CMD ["node", "server.js"]| 指令 | 意思 |
|---|---|
FROM | 从哪个基础镜像开始,这里是装好了 Node.js 24 的精简 Linux |
WORKDIR | 镜像里的工作目录 |
COPY | 把文件复制进镜像 |
ENV | 设置环境变量的默认值;HOST=0.0.0.0 让程序在容器里监听所有网卡 |
EXPOSE | 说明程序使用的端口 |
USER | 以哪个用户运行 |
CMD | 容器启动时执行的命令 |
确认没问题后存档:
git add -A
git commit -m "添加 Dockerfile"第二步:把项目传到服务器
scp 是基于 SSH 的复制命令,用法和 cp 类似,只是可以跨机器。在自己电脑上运行:
scp -r "$HOME\ai-course\weekly-report" ai-server:scp -r ~/ai-course/weekly-report ai-server:ai-server: 后面什么都不写,表示复制到服务器的家目录。完成后,服务器上会有 ~/weekly-report 文件夹。
服务器上有两个文件夹,别搞混:
~/weekly-report放源代码,用来构建镜像;~/apps/weekly-report放部署配置(.env等),用来运行。
第三步:在服务器上构建镜像
ssh ai-server看到服务器的提示符后,再运行:
cd ~/weekly-report
docker build -t weekly-report:v1 .-t weekly-report:v1:给镜像起名叫weekly-report,版本标签是v1。- 最后的
.表示用当前文件夹里的 Dockerfile 和文件。
第一次构建要下载 Node.js 基础镜像,稍等片刻。完成后查看:
docker images列表里有 weekly-report,标签 v1。
第四步:运行容器
docker run --rm --name weekly-report-test --env-file ~/apps/weekly-report/.env -p 127.0.0.1:3000:3000 weekly-report:v1| 参数 | 意思 |
|---|---|
--rm | 停止后自动删除容器 |
--name | 给容器起个名字 |
--env-file | 从第 17 课写好的 .env 读取环境变量,Key 就是这样传进去的 |
-p 127.0.0.1:3000:3000 | 把容器的 3000 端口映射到服务器的 3000 端口,并且只允许服务器自己访问 |
看到“周报助手已启动”之类的输出就成功了。这个终端会被占用,先别关。
第五步:通过 SSH 隧道访问
服务器的 3000 端口只对服务器自己开放,外网访问不了,这正是我们想要的。那怎么在自己电脑上打开它?用 SSH 隧道:把自己电脑的 3000 端口,经过加密的 SSH 连接,接到服务器的 3000 端口。
在自己电脑上新开一个终端:
ssh -L 3000:127.0.0.1:3000 ai-server它会像平常一样登录服务器,同时打通隧道。保持这个窗口开着,在浏览器访问 http://localhost:3000,看到的就是服务器上运行的周报助手。贴上测试数据,生成一份周报试试。
你应该看到
浏览器里的周报助手和本地运行时一样,能用 AI 生成周报。
试完后:
- 在运行容器的终端按
Ctrl + C,容器停止并被自动删除; - 在隧道终端输入
exit关闭隧道。
确认 Key 不在镜像里
镜像里的 Key 可能藏在三个地方:构建时执行过的指令、预设的环境变量、复制进去的文件。逐一检查:
docker history --no-trunc weekly-report:v1 > history-check.txt && grep -c "sk-" history-check.txt; rm -f history-check.txtdocker history 列出构建这个镜像时执行过的每一条指令(包括 ENV 写进去的值),这里只数有几行带 sk-。应该输出 0;如果没有输出数字而是报错,这次检查不算数。
docker run --rm weekly-report:v1 sh -c 'env; grep -r "" /app' > image-check.txt && grep -c "sk-" image-check.txt; rm -f image-check.txt这条命令在临时容器里列出全部环境变量和 /app 下所有文件的内容,存进一个临时文件,只数带 sk- 的行数,最后删掉临时文件。应该输出 0;如果没有输出数字而是报错,说明容器没跑起来,这次检查不算数,先解决报错。
两项都是 0,再加上 Dockerfile 里没有写 Key、只复制了三样文件,也没有“先放进文件再删掉”的步骤,.dockerignore 又把 .env 排除在构建之外,就可以确认 Key 没有被打包进镜像。它只在运行时从 .env 传入。以后如果 Dockerfile 变复杂了,这两条检查就不一定够,要让 AI 帮你重新评估。
常见问题
本地访问 localhost:3000 看到的是旧的本地版本? 你电脑上的 npm start 还开着,占用了 3000 端口。先停掉它,再建立隧道。
AI 一直退回规则版,提示 401? 检查 .env:等号两边有没有空格,Key 有没有加引号。
docker build 报 COPY failed? 文件名和 Dockerfile 里写的不一致,或者在错误的文件夹里运行了 docker build。确认当前在 ~/weekly-report。
bind: address already in use? 服务器上的 3000 端口已经被占用,可能是之前的容器还在运行,用 docker ps 查看并 docker stop 它。
自检
- 我能说出 Dockerfile 里每条指令的作用。
- 我用
scp把项目传到了服务器。 -
docker images里有weekly-report:v1。 - 我通过 SSH 隧道在浏览器里用上了服务器上的周报助手。
- 镜像里没有 Key。