Dockerfile 编写与镜像构建 Dockerfile 编写与镜像构建Dockerfile 是什么Dockerfile 是一个文本文件包含一系列指令Docker 根据这些指令自动构建镜像。可以理解为 “镜像的菜谱”—— 告诉 Docker 一步步如何做出一个镜像。Dockerfile 指令速查指令用途示例FROM指定基础镜像必须是第一条指令FROM python:3.11-slimWORKDIR设置工作目录WORKDIR /appCOPY复制文件到镜像COPY . /appADD复制文件支持 URL 和自动解压ADD app.tar.gz /appRUN构建时执行命令RUN pip install -r requirements.txtCMD容器启动时的默认命令CMD [python, app.py]ENTRYPOINT容器启动时的入口命令ENTRYPOINT [python]ENV设置环境变量ENV APP_ENVproductionEXPOSE声明容器监听的端口文档用途EXPOSE 8080VOLUME声明数据卷挂载点VOLUME [/data]ARG构建时的参数ARG VERSION1.0LABEL元数据标签LABEL maintainerdarrenUSER指定运行用户USER appuserHEALTHCHECK健康检查HEALTHCHECK CMD curl -f http://localhost/CMD vs ENTRYPOINT这是初学者最容易混淆的两个指令CMD —— 默认命令可被覆盖FROM ubuntu CMD [echo, Hello World]docker run myimage # 输出: Hello World docker run myimage echo Hi # 输出: HiCMD 被覆盖ENTRYPOINT —— 入口命令不易被覆盖FROM ubuntu ENTRYPOINT [echo] CMD [Hello World]docker run myimage # 输出: Hello WorldENTRYPOINT CMD docker run myimage Hi # 输出: HiENTRYPOINT 覆盖的 CMD最佳实践ENTRYPOINT: 定义容器的 “身份”它是什么CMD: 定义默认参数可以被用户覆盖ENTRYPOINT [python] CMD [app.py] # docker run myimage → python app.py # docker run myimage test.py → python test.py构建上下文与 .dockerignore构建上下文执行docker build时Docker 会将指定目录的所有文件发送给 Docker Daemondocker build -t myapp . # ^ 这个点就是构建上下文当前目录.dockerignore和.gitignore类似告诉 Docker 哪些文件不需要发送到构建上下文。以下是完整的配置示例及每个忽略项的作用说明# .dockerignore # 1. Git 版本控制目录包含仓库元数据镜像构建无需依赖排除可减少上下文体积 .git # 2. Node.js 依赖目录通常体积大且可通过 npm install 重新安装排除后避免重复传输 node_modules # 3. Python 编译缓存目录运行时自动生成无需带入镜像 __pycache__ # 4. Python 编译后的字节码文件同上排除可减少上下文大小 *.pyc # 5. 环境变量文件包含敏感信息如密钥或本地配置不应进入镜像 .env # 6. Python 虚拟环境目录本地开发依赖镜像内可重新创建 .venv # 7. 项目说明文档仅用于阅读与镜像运行无关 README.md # 8. Docker Compose 配置文件用于容器编排非镜像构建必需 docker-compose*.yml # 可选扩展根据项目类型补充 # log/ # 日志目录本地日志无需带入镜像 # build/ # 编译产物目录如前端打包后的dist可按需复制而非整个build # *.log # 日志文件避免本地日志污染镜像 # .DS_Store # MacOS 系统文件无业务意义 # .vscode/ # 编辑器配置文件仅本地开发使用为什么重要如果项目有大量不相关的文件比如node_modules不使用.dockerignore会导致构建上下文传输缓慢甚至可能将敏感文件意外带入镜像同时增加镜像体积。镜像分层原理深入Dockerfile 中的每条指令都会创建一个新的镜像层FROM python:3.11-slim # Layer 1: 基础镜像约 120MB WORKDIR /app # Layer 2: 设置工作目录几乎 0 COPY requirements.txt . # Layer 3: 复制依赖文件几 KB RUN pip install -r requirements.txt # Layer 4: 安装依赖可能几十 MB COPY . . # Layer 5: 复制应用代码 CMD [python, app.py] # Layer 6: 元数据0 size缓存机制Docker 构建时会利用缓存加速如果某一层的指令和上下文没有变化直接使用缓存一旦某一层缓存失效它之后的所有层都会重新构建所以把变化频率低的指令放前面变化频率高的放后面。# ✗ 不好每次代码改变都要重新安装依赖 COPY . . RUN pip install -r requirements.txt # ✓ 好只有 requirements.txt 变了才重新安装依赖 COPY requirements.txt . RUN pip install -r requirements.txt COPY . .实战示例示例 1Python Flask 应用完整项目结构flask-app/ ├── .dockerignore # 参考上文配置 ├── Dockerfile ├── app.py # 应用代码 └── requirements.txt # 依赖清单requirements.txtflask2.3.3 gunicorn21.2.0 # 生产环境 WSGI 服务器app.pyfrom flask import Flask app Flask(__name__) app.route(/) def hello(): return Hello Docker! app.route(/health) # 健康检查接口 def health(): return {status: ok}, 200 if __name__ __main__: app.run(host0.0.0.0, port5000)Dockerfile# 使用精简的基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 复制依赖文件并安装利用缓存 COPY requirements.txt . # --no-cache-dir避免pip缓存减少镜像体积 RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 声明端口文档用途实际映射需靠docker run -p EXPOSE 5000 # 健康检查配置 HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD curl -f http://localhost:5000/health || exit 1 # 生产环境推荐使用gunicorn而非内置服务器 CMD [gunicorn, --bind, 0.0.0.0:5000, app:app]实现步骤# 1. 进入项目目录 cd flask-app # 2. 构建 Docker 镜像 docker build -t flask-app:v1 . # 3. 启动容器后台运行端口映射 5000:5000 docker run -d --name flask-container -p 5000:5000 flask-app:v1 # 4. 查看容器运行状态 docker ps # 5. 访问应用测试 curl http://localhost:5000 curl http://localhost:5000/health # 6. 查看容器日志 docker logs flask-container # 7. 停止并删除容器结束使用 docker stop flask-container docker rm flask-container示例 2Node.js 应用完整项目结构node-app/ ├── .dockerignore # 参考上文配置 ├── Dockerfile ├── server.js # 应用代码 ├── package.json # 依赖清单 └── package-lock.json # 依赖锁定文件package.json{ name: docker-node-app, version: 1.0.0, dependencies: { express: ^4.18.2 }, scripts: { start: node server.js } }server.jsconst express require(express); const app express(); const port 3000; app.get(/, (req, res) { res.send(Hello Docker Node.js! ); }); app.get(/health, (req, res) { res.json({ status: ok }); }); app.listen(port, 0.0.0.0, () { console.log(App running on port ${port}); });Dockerfile# 使用alpine精简镜像体积更小 FROM node:20-alpine # 设置工作目录 WORKDIR /app # 复制依赖清单利用缓存 COPY package*.json ./ # npm ci严格按照package-lock.json安装比npm install更稳定 RUN npm ci --onlyproduction # 复制应用代码 COPY . . # 声明端口 EXPOSE 3000 # 健康检查 HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD wget -qO- http://localhost:3000/health || exit 1 # 启动应用 CMD [npm, start]实现步骤# 1. 进入项目目录 cd node-app # 2. 构建镜像 docker build -t node-app:v1 . # 3. 启动容器后台运行端口映射 3000:3000 docker run -d --name node-container -p 3000:3000 node-app:v1 # 4. 查看运行状态 docker ps # 5. 访问测试 curl http://localhost:3000 curl http://localhost:3000/health # 6. 查看日志 docker logs node-container # 7. 停止并删除容器 docker stop node-container docker rm node-container示例 3Go 应用多阶段构建完整项目结构go-app/ ├── .dockerignore # 参考上文配置 ├── Dockerfile ├── go.mod # Go 模块配置 ├── go.sum # 依赖校验文件 └── main.go # 应用代码go.modmodule docker-go-app go 1.22 require github.com/gin-gonic/gin v1.9.1main.gopackage main import ( net/http github.com/gin-gonic/gin ) func main() { r : gin.Default() r.GET(/, func(c *gin.Context) { c.String(http.StatusOK, Hello Docker Go! ) }) r.GET(/health, func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{status: ok}) }) r.Run(:8080) }Dockerfile多阶段构建# 第一阶段构建阶段使用完整Go镜像 FROM golang:1.22-alpine AS builder # 设置工作目录 WORKDIR /app # 复制Go模块文件利用缓存 COPY go.mod go.sum ./ RUN go mod download # 复制应用代码 COPY . . # 编译Go程序CGO_ENABLED0 禁用CGO生成静态二进制文件 RUN CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o server . # 第二阶段运行阶段使用极简alpine镜像 FROM alpine:3.19 # 安装curl用于健康检查alpine默认无curl RUN apk --no-cache add curl # 从构建阶段复制编译后的二进制文件 COPY --frombuilder /app/server /server # 声明端口 EXPOSE 8080 # 健康检查 HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD curl -f http://localhost:8080/health || exit 1 # 启动应用 CMD [/server]实现步骤# 1. 进入项目目录 cd go-app # 2. 构建镜像多阶段自动完成 docker build -t go-app:v1 . # 3. 启动容器后台运行端口映射 8080:8080 docker run -d --name go-container -p 8080:8080 go-app:v1 # 4. 查看运行状态 docker ps # 5. 访问测试 curl http://localhost:8080 curl http://localhost:8080/health # 6. 查看日志 docker logs go-container # 7. 停止并删除容器 docker stop go-container docker rm go-container多阶段构建优势最终镜像仅包含编译后的二进制文件体积从几百 MB 降至几十 MB减少攻击面提升安全性。构建命令# 基本构建指定镜像名 docker build -t myapp . # 指定 tag 版本 docker build -t myapp:v1.0 . # 指定自定义Dockerfile路径 docker build -f Dockerfile.prod -t myapp:prod . # 不使用缓存构建强制重新构建所有层 docker build --no-cache -t myapp . # 传递构建参数对应Dockerfile中的ARG docker build --build-arg VERSION2.0 -t myapp . # 查看构建的镜像 docker images myapp # 构建后运行容器 docker run -d -p 5000:5000 --name myapp-container myapp # 查看容器日志 docker logs myapp-container # 检查健康状态 docker inspect --format {{.State.Health.Status}} myapp-containerDockerfile 最佳实践1. 使用精简基础镜像# ✗ 完整镜像约 900MB FROM python:3.11 # ✓ slim 镜像约 120MB平衡体积和兼容性 FROM python:3.11-slim # ✓✓ alpine 镜像约 50MB但可能有兼容性问题如缺少系统库 FROM python:3.11-alpine2. 合并 RUN 指令减少层数# ✗ 三层每层都会增加镜像体积 RUN apt-get update RUN apt-get install -y curl RUN rm -rf /var/lib/apt/lists/* # ✓ 一层用\换行串联命令 RUN apt-get update \ apt-get install -y curl \ rm -rf /var/lib/apt/lists/* # 清理apt缓存减少镜像体积3. 使用非 root 用户# 创建无密码的普通用户 RUN adduser --disabled-password --gecos appuser # 切换到普通用户运行容器降低安全风险 USER appuser4. 添加 HEALTHCHECK# 配置健康检查每30秒检查一次超时5秒重试3次失败则标记为不健康 HEALTHCHECK --interval30s --timeout5s --retries3 \ CMD curl -f http://localhost:8080/health || exit 15. 利用 .dockerignore始终创建.dockerignore文件排除不必要的文件详见上文。6. 避免敏感信息硬编码# ✗ 不安全密钥硬编码 ENV DB_PASSWORD123456 # ✓ 推荐通过构建参数或运行时环境变量传入 ARG DB_PASSWORD ENV DB_PASSWORD$DB_PASSWORD # 运行时docker run -e DB_PASSWORD123456 myapp7. 清理构建缓存# Python禁用pip缓存 RUN pip install --no-cache-dir -r requirements.txt # Node.js使用npm ci并清理缓存 RUN npm ci --onlyproduction npm cache clean --force # Ubuntu/Debian清理apt缓存 RUN apt-get update apt-get install -y curl rm -rf /var/lib/apt/lists/*实操练习 Lab 03构建你的第一个镜像练习目标编写完整的 Dockerfile 构建 Python Flask 应用配置 .dockerignore 优化构建上下文构建镜像并运行容器验证健康检查和端口映射步骤 1准备项目目录# 创建项目目录 mkdir flask-docker-lab cd flask-docker-lab # 创建空文件 touch Dockerfile .dockerignore app.py requirements.txt步骤 2编写配置文件1. requirements.txtflask2.3.3 gunicorn21.2.02. app.pyfrom flask import Flask, jsonify app Flask(__name__) # 首页接口 app.route(/) def index(): return h1My First Docker App/h1pBuilt with Dockerfile!/p # 健康检查接口 app.route(/health) def health_check(): return jsonify({ status: healthy, app: flask-docker-lab, version: 1.0 }), 200 if __name__ __main__: app.run(host0.0.0.0, port5000, debugFalse)3. .dockerignore.git __pycache__ *.pyc .env .venv README.md docker-compose*.yml *.log4. Dockerfile# 基础镜像 FROM python:3.11-slim # 设置工作目录 WORKDIR /app # 创建非root用户 RUN adduser --disabled-password --gecos appuser # 复制依赖文件并安装 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用代码 COPY . . # 更改目录权限确保普通用户可访问 RUN chown -R appuser:appuser /app # 切换到普通用户 USER appuser # 声明端口 EXPOSE 5000 # 健康检查 HEALTHCHECK --interval10s --timeout3s --retries3 \ CMD curl -f http://localhost:5000/health || exit 1 # 启动命令 CMD [gunicorn, --bind, 0.0.0.0:5000, app:app]步骤 3构建镜像# 构建镜像命名为flask-lab:v1 docker build -t flask-lab:v1 . # 查看镜像 docker images flask-lab:v1步骤 4运行容器# 后台运行容器映射端口5000:5000 docker run -d -p 5000:5000 --name flask-lab-container flask-lab:v1 # 查看容器状态 docker ps # 查看健康状态等待10秒后检查 docker inspect --format {{.State.Health.Status}} flask-lab-container步骤 5验证应用# 访问首页 curl http://localhost:5000 # 访问健康检查接口 curl http://localhost:5000/health步骤 6清理资源# 停止容器 docker stop flask-lab-container # 删除容器 docker rm flask-lab-container # 删除镜像可选 docker rmi flask-lab:v1练习拓展尝试修改 app.py 代码重新构建镜像观察缓存是否生效尝试添加环境变量如ENV APP_VERSION1.0在 app.py 中读取并返回尝试使用docker build --no-cache强制重新构建对比构建时间尝试将基础镜像换成 alpine 版本解决可能的依赖问题如缺少 curl常见问题排查构建上下文过大检查 .dockerignore 是否配置正确排除 node_modules、.venv 等大目录缓存失效确认指令顺序是否合理变化频繁的指令如 COPY . .放在最后权限问题使用非 root 用户时确保目录 / 文件权限正确chown/chmod健康检查失败确认接口路径正确容器内已安装 curl/wget 等工具端口映射失败确认 EXPOSE 端口与 run -p 端口一致容器内应用绑定 0.0.0.0 而非 127.0.0.1本文为同步搬运内容原创首发于个人独立博客网站https://www.zheng-chang-ren.xyz平台更新优先级说明所有技术笔记、实验教程、踩坑总结均会优先发布、长期维护于个人独立博客CSDN 仅作为辅助分发渠道。若想查阅全部完整文集、获取最新首发内容建议收藏并优先访问我的个人博客网站。