讲解
把前面所有章节串起来,做一次完整的应用容器化。目标:一个有依赖的 Node.js HTTP 服务,产出可发布的生产镜像。这份 Dockerfile 是可直接抄进项目的模板,每个决策都对应前面学过的章节:基础镜像选 node:22-alpine(锁版本、小体积);先 COPY 依赖清单再装依赖(缓存章节的「清单先行」);npm ci 代替 npm install(按 lock 文件精确安装,可复现);--omit=dev 不装开发依赖;.dockerignore 排除 node_modules 与 .env(防泄密 + 提速);非 root 运行(node 镜像自带 uid 1000 的 node 用户,直接 USER node);CMD exec 形式启动。
构建、运行、验证之后,还要回答「怎么发布」:打语义化标签推仓库(registry 章节),服务器上 docker compose 拉起(compose 章节),数据外置(volumes 章节),配置走环境变量(env 章节),日志写 stdout(logs 章节)——一个镜像就这样走完了从代码到生产的全链路。
扩展练习建议:把服务改成多阶段构建(本章依赖简单未强制需要),接入上一章的 compose 栈替换 nginx,给服务加 /healthz 端点配 HEALTHCHECK。模板是死的,把每个决策背后的章节原理讲清楚,才算学会。
示例
准备应用:一个带依赖的 HTTP 服务(依赖用 semver,一个零依赖之外的小包):
mkdir -p nodeapp
cat > nodeapp/package.json <<'EOF'
{
"name": "docker-demo-app",
"version": "1.0.0",
"private": true,
"main": "server.js",
"scripts": { "start": "node server.js" },
"dependencies": { "semver": "^7.6.0" }
}
EOF
cat > nodeapp/server.js <<'EOF'
const http = require('http');
const semver = require('semver');
const port = process.env.PORT || 3000;
http.createServer((req, res) => {
res.setHeader('content-type', 'text/plain; charset=utf-8');
res.end('demo app, node ' + process.version + ', semver valid: ' + semver.valid('1.2.3') + '\n');
}).listen(port, () => console.log('listening on ' + port));
EOF
printf 'node_modules\n.env\nnpm-debug.log\n' > nodeapp/.dockerignore
生成 lock 文件(可复现安装的前提),用容器里的 npm 保证版本一致:
cd nodeapp && docker run --rm -v "$PWD:/app" -w /app node:22-alpine npm install --package-lock-only --omit=dev >/dev/null && ls package-lock.json && cd ..
生产向 Dockerfile:清单先行、npm ci、非 root:
# context: nodeapp
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY server.js ./
USER node
EXPOSE 3000
CMD ["node", "server.js"]
构建、以环境变量改端口运行、验证、清理——完整走一遍发布前的本地闭环:
cd nodeapp && docker build -t ohmydocs-verify-nodeapp:1.0.0 . && cd ..
docker run -d --name ohmydocs-verify-app -p 18087:3000 -e PORT=3000 ohmydocs-verify-nodeapp:1.0.0
sleep 2 && curl -sf http://localhost:18087
docker exec ohmydocs-verify-app id -u
docker rm -f ohmydocs-verify-app
常见坑
- COPY . . 把 node_modules 盖进镜像:宿主机装的依赖(平台、版本可能不对)覆盖容器里 npm ci 的结果——.dockerignore 排除它。
- 用 npm install 部署:lock 形同虚设,依赖悄悄漂移;CI 与镜像构建一律 npm ci。
- 忘了 lock 文件进上下文:COPY package-lock.json 报 not found 多半是 .gitignore 误伤了 lock——lock 必须提交。
- root 运行 Node 服务:node 镜像自带 node 用户,加一行 USER node 就能摘掉 root,没有不做的理由。
小结
模板要点:锁版本基础镜像、清单先行、npm ci --omit=dev、.dockerignore、USER node、exec 形式 CMD;构建→运行→验证→发布走完整链路。下一章再实战一个有状态服务:MySQL。