Docker 学习笔记(四):Dockerfile,把项目打成自己的镜像
Docker 学习笔记四Dockerfile把项目打成自己的镜像前几篇讲的是怎么拉别人做好的镜像怎么用docker run启动容器怎么理解 Docker 网络。但是学 Docker 最关键的一步是如何把自己的项目做成镜像。这个过程靠的就是 Dockerfile。1. Dockerfile 是什么Dockerfile 是一个文本文件里面写着构建镜像的步骤。你可以把它理解成Dockerfile 镜像制作说明书比如一个最小 Node 项目的 DockerfileFROM node:22-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [npm, start]然后执行dockerbuild-tmy-node-app:1.0.就能构建出一个镜像dockerimages运行dockerrun-d--namemy-node-app-p3000:3000 my-node-app:1.02. Dockerfile 的核心指令2.1FROM基于哪个镜像FROM node:22-alpine任何 Dockerfile 基本都从FROM开始。它表示我的镜像不是从零开始而是基于一个已有镜像继续加工。比如FROM nginx:alpine FROM node:22-alpine FROM mongo:7实际项目中不建议随便写latest因为它会变化。更推荐写明确版本FROM node:22-alpine2.2WORKDIR设置工作目录WORKDIR /app后面的命令默认都在/app目录下执行。相当于cd/app如果目录不存在Docker 会自动创建。2.3COPY复制文件到镜像里COPY package*.json ./ COPY . .第一句把本地的package.json、package-lock.json复制到镜像的/app。第二句把当前目录其他文件复制进去。为什么不直接先COPY . .因为 Docker 构建有缓存机制。更推荐这样写COPY package*.json ./ RUN npm ci COPY . .这样只要依赖文件没变npm ci这一层就可以复用缓存加快构建。2.4RUN构建阶段执行命令RUN npm ci RUN npm run buildRUN是在构建镜像时执行。它和CMD的区别非常重要指令执行时机RUNdocker build 构建镜像时CMDdocker run 启动容器时2.5ENV设置默认环境变量ENV NODE_ENVproduction这样容器运行时默认有这个环境变量。但更敏感的配置比如数据库密码不建议写死在 Dockerfile 里。更推荐运行时传入dockerrun-eMONGO_URLxxx my-api:1.0或者在 Compose 里配置。2.6EXPOSE声明容器使用哪个端口EXPOSE 3000注意EXPOSE 只是声明不等于自动映射端口。真正让宿主机访问容器还要靠-p3000:3000EXPOSE更像是告诉别人这个容器内部服务监听的是 3000 端口。2.7CMD容器启动命令CMD [npm, start]它表示容器启动后默认执行什么命令。推荐使用 JSON 数组形式CMD [node, dist/main.js]而不是CMD node dist/main.js数组形式更清晰也更适合信号处理。3. 为 NestJS 后端写 Dockerfile假设项目是 NestJS 后端nest-server/ src/ package.json package-lock.json tsconfig.json nest-cli.json可以写FROM node:22-alpine AS deps WORKDIR /app COPY package*.json ./ RUN npm ci FROM node:22-alpine AS builder WORKDIR /app COPY --fromdeps /app/node_modules ./node_modules COPY . . RUN npm run build FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENVproduction COPY package*.json ./ RUN npm ci --omitdev COPY --frombuilder /app/dist ./dist EXPOSE 3000 CMD [node, dist/main.js]这是一个多阶段构建。它的思路是第一阶段 deps安装完整依赖 第二阶段 builder编译 TypeScript 第三阶段 runner只保留生产运行需要的文件好处是最终镜像更干净。4. 为 React 前端写 Dockerfile假设前端是 React/Viteweb/ src/ package.json index.html vite.config.tsDockerfileFROM node:22-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine AS runner COPY --frombuilder /app/dist /usr/share/nginx/html EXPOSE 80 CMD [nginx, -g, daemon off;]思路Node 阶段负责安装依赖、打包前端 Nginx 阶段只负责托管 dist 静态文件前端最终不需要 Node.js 运行环境只需要 Nginx 托管静态资源。5..dockerignore很重要很多人写 Dockerfile会忘记.dockerignore。它类似.gitignore用于告诉 Docker 构建时不要把某些文件复制进去。建议node_modules dist build .git .gitignore Dockerfile .dockerignore npm-debug.log .env .env.*如果不写.dockerignore可能会导致构建上下文很大本地node_modules被复制进镜像.env等敏感文件进镜像构建速度变慢。6. 构建镜像在 Dockerfile 所在目录执行dockerbuild-tmy-api:1.0.解释部分含义docker build构建镜像-t my-api:1.0镜像名和标签.构建上下文是当前目录查看镜像dockerimages运行镜像dockerrun-d--namemy-api-p3000:3000 my-api:1.0查看日志dockerlogs-fmy-api7. 镜像标签怎么理解镜像名通常长这样my-api:1.0其中my-api 是镜像名 1.0 是 tag如果不写 tag默认是latest。dockerbuild-tmy-api.等价于dockerbuild-tmy-api:latest.但在实际项目里不建议完全依赖latest。更推荐dockerbuild-tmy-api:2026-06-29.dockerbuild-tmy-api:v1.0.0.dockerbuild-tmy-api:commit-abc123.这样出问题时更容易回滚。8. 第四篇小结Dockerfile 的主线是选择基础镜像 ↓ 设置工作目录 ↓ 复制依赖声明文件 ↓ 安装依赖 ↓ 复制项目代码 ↓ 构建项目 ↓ 声明端口 ↓ 指定启动命令常见指令指令作用FROM基础镜像WORKDIR工作目录COPY复制文件RUN构建阶段执行命令ENV默认环境变量EXPOSE声明端口CMD容器启动命令下一篇讲 Docker Compose不用手写一堆docker run用一个 YAML 同时启动前端、后端和 MongoDB。参考资料Dockerfile reference: https://docs.docker.com/reference/dockerfile/Build, tag, and publish an image: https://docs.docker.com/get-started/docker-concepts/building-images/build-tag-and-publish-an-image/Docker build reference: https://docs.docker.com/reference/cli/docker/buildx/build/