DockerDockerfileNode.js工程化

Dockerfile 与 .dockerignore:Node.js 镜像构建指南

从构建上下文、Dockerfile 分层和多阶段构建讲起,给出 Node.js 项目的 Dockerfile 与 .dockerignore 示例,并解释缓存、镜像体积和敏感文件边界。

·更新于 ·阅读约 11 分钟·计算中...
Dockerfile 与 .dockerignore:Node.js 镜像构建指南

Dockerfile 描述如何构建镜像,.dockerignore 决定哪些文件不会进入构建上下文。优化 Node.js 镜像时,两者必须一起设计:先缩小上下文,再合理排列依赖安装和源码复制步骤,最后用多阶段构建只保留运行所需内容。

目录

先理解构建上下文

执行下面的命令时,最后的 . 表示当前目录是构建上下文:

docker build -t my-app:latest .

Dockerfile 中的 COPYADD 只能访问上下文里的文件。上下文包含大量日志、缓存和 node_modules 时,即使 Dockerfile 没有复制它们,也会增加扫描或传输成本。

Node.js 项目的 .dockerignore

node_modules
.next
dist
coverage
.git
.env*
*.log
Dockerfile*
docker-compose*.yml
README.md

根据构建需要调整:如果构建阶段需要某个环境模板,不要把它排除;真实密钥则不应该通过 COPY 写入镜像。.dockerignore 支持 ! 反向包含,且最后一个匹配规则决定结果:

*.md
!README.md

多个 Dockerfile 可以使用各自的忽略文件,例如 build.Dockerfile.dockerignore。Docker 官方说明,Dockerfile 专用忽略文件会优先于上下文根目录的 .dockerignore

常用 Dockerfile 指令

指令 用途
FROM 指定基础镜像或开始新的构建阶段
WORKDIR 设置后续命令的工作目录
COPY 从构建上下文复制文件
RUN 在构建阶段执行命令并创建镜像层
ENV 设置镜像运行环境变量
ARG 声明仅构建阶段使用的参数
EXPOSE 记录容器预期监听端口,不会自动发布端口
USER 切换后续命令和运行进程的用户
CMD 设置容器默认启动命令

能用 COPY 时不要因为习惯改用 ADDADD 还支持远程 URL、Git 和自动解压等额外行为,只有明确需要时再使用。

利用缓存安装依赖

先复制依赖清单,再安装依赖,最后复制源码:

# syntax=docker/dockerfile:1
FROM node:20-alpine AS deps
WORKDIR /app

COPY package.json package-lock.json ./
RUN npm ci

FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

只要 lockfile 没变,Docker 就有机会复用依赖层。若先 COPY . .,任何源码变化都会让后续依赖安装层失效。

多阶段运行镜像

Next.js standalone 项目可以只复制运行产物:

FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs \
  && adduser --system --uid 1001 nextjs

COPY --from=build --chown=nextjs:nodejs /app/public ./public
COPY --from=build --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=build --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs
EXPOSE 3000
CMD ["node", "server.js"]

构建阶段可以包含编译工具和开发依赖,运行阶段只保留产物。最终镜像更小,攻击面也更少。具体复制路径取决于框架输出,不要未经验证直接套用。

CMD、ENTRYPOINT 与 shell 形式

推荐用 exec 形式:

CMD ["node", "server.js"]

它不会额外启动 shell,信号传递更直接。需要固定可执行程序、只让用户追加参数时再考虑 ENTRYPOINT。一个 Dockerfile 中只有最后一个 CMD 生效。

环境变量与密钥

  • ARGENV 都不适合保存真正的构建密钥,因为它们可能出现在镜像历史或元数据中。
  • 使用 BuildKit secret mount 处理构建期私有凭据。
  • 运行期密钥通过部署平台、Compose secrets 或容器编排系统注入。
  • 不要复制 .env 后再删除;文件可能已经存在于前一镜像层。

构建与检查

docker build --pull -t my-app:latest .
docker image ls my-app
docker history my-app:latest
docker run --rm -p 3000:3000 my-app:latest

--no-cache 会重新执行构建层,但不会自动拉取更新后的基础镜像;需要全新基础镜像时同时使用 --pull。日常构建不应默认禁用缓存,否则会掩盖 Dockerfile 分层问题并浪费时间。

常见问题

  • COPY 找不到文件:检查文件是否在构建上下文内,或是否被 .dockerignore 排除。
  • 改一行代码却重新安装全部依赖:调整 COPY 顺序,把 lockfile 放在源码之前。
  • 镜像仍然很大:检查最终阶段是否复制了完整源码、开发依赖或构建缓存。
  • 容器收不到停止信号:确认使用 exec 形式启动,并避免不必要的 shell 包装。
  • 镜像中出现密钥:立即轮换密钥,再清理构建流程;删除当前层文件并不能清除旧层。

日常管理容器和镜像的命令可参考 Docker 常用命令大全

参考资料

订阅 FreeMac

每周精选:免费 Mac 软件评测、可信来源更新、替代方案和少折腾指南。