7.1 Dockerfile语法详解7.1.1 什么是DockerfileDockerfile是一个文本文件包含一系列指令用于自动化构建Docker镜像。基本结构# 注释 指令 参数简单示例# 使用官方Python运行时作为基础镜像 FROM python:3.9-slim # 设置工作目录 WORKDIR /app # 复制应用文件 COPY . . # 安装依赖 RUN pip install -r requirements.txt # 暴露端口 EXPOSE 5000 # 运行应用 CMD [python, app.py]7.1.2 Dockerfile指令顺序指令执行顺序很重要FROM # 必须是第一条指令除注释外 MAINTAINER # 已弃用使用LABEL代替 LABEL # 添加元数据 ARG # 定义构建参数 ENV # 设置环境变量 WORKDIR # 设置工作目录 COPY/ADD # 复制文件 RUN # 执行命令 EXPOSE # 声明端口 VOLUME # 声明数据卷 USER # 切换用户 CMD # 容器启动命令 ENTRYPOINT # 容器入口点7.1.3 构建上下文# 构建镜像dockerbuild -t myapp:1.0.# └─ 构建上下文当前目录# 构建上下文中的文件会被发送给Docker守护进程# 因此要注意# 1. 构建上下文不要太大# 2. 使用.dockerignore排除不需要的文件.dockerignore示例# 排除版本控制 .git .gitignore # 排除临时文件 *.tmp *.log .DS_Store # 排除构建产物 node_modules __pycache__ *.pyc dist/ build/ # 排除文档 README.md docs/ # 排除测试文件 tests/ *.test.js7.2 常用指令详解7.2.1 FROM - 基础镜像# 基本格式 FROM image FROM image:tag FROM imagedigest # 示例 FROM ubuntu:22.04 FROM python:3.9-slim FROM nginx:1.25-alpine # 多阶段构建 FROM golang:1.20 AS builder # ... 构建步骤 FROM alpine:latest # ... 复制构建产物 # 使用ARG指定基础镜像版本 ARG PYTHON_VERSION3.9 FROM python:${PYTHON_VERSION}-slim选择基础镜像的原则官方镜像优先选择合适的变体ubuntu:22.04- 完整系统~77MBpython:3.9-slim- 精简版~120MBpython:3.9-alpine- Alpine版~50MB固定版本标签使用python:3.9而非python:latest7.2.2 RUN - 执行命令# Shell形式推荐 RUN apt-get update apt-get install -y \ curl \ vim \ rm -rf /var/lib/apt/lists/* # Exec形式 RUN [/bin/bash, -c, echo hello] # 多个RUN会创建多层 RUN apt-get update # Layer 1 RUN apt-get install -y curl # Layer 2 RUN apt-get install -y vim # Layer 3 # 合并为一层推荐 RUN apt-get update apt-get install -y \ curl \ vim \ rm -rf /var/lib/apt/lists/* # Layer 1最佳实践# 安装软件包 RUN apt-get update apt-get install -y \ package1 \ package2 \ package3 \ rm -rf /var/lib/apt/lists/* # 清理缓存 # 创建用户 RUN groupadd -r appuser useradd -r -g appuser appuser # 编译安装 RUN wget https://example.com/source.tar.gz \ tar -xzf source.tar.gz \ cd source \ ./configure \ make \ make install \ cd .. \ rm -rf source source.tar.gz # 清理临时文件7.2.3 COPY - 复制文件# 基本格式 COPY src... dest COPY [src,... dest] # 路径包含空格时使用 # 示例 COPY app.py /app/ COPY requirements.txt /app/ COPY . /app/ # 复制多个文件 COPY file1.txt file2.txt /app/ # 使用通配符 COPY *.py /app/ COPY src/*.js /app/src/ # 保留文件属性 COPY --chownuser:group file.txt /app/ # 设置权限 COPY --chmod755 script.sh /app/COPY vs ADD# COPY简单复制 COPY requirements.txt /app/ # ADD支持URL和自动解压不推荐常用 ADD http://example.com/file.tar.gz /tmp/ ADD archive.tar.gz /app/ # 自动解压 # 推荐明确使用COPY需要解压时手动RUN tar COPY archive.tar.gz /tmp/ RUN tar -xzf /tmp/archive.tar.gz -C /app/ \ rm /tmp/archive.tar.gz7.2.4 WORKDIR - 工作目录# 设置工作目录 WORKDIR /app # 如果目录不存在会自动创建 WORKDIR /path/to/workdir # 可以多次使用 WORKDIR /a WORKDIR b WORKDIR c # 最终在 /a/b/c # 使用环境变量 ENV DIRPATH/app WORKDIR ${DIRPATH}最佳实践# 不推荐 RUN cd /app COPY . . # 不在/app目录 # 推荐 WORKDIR /app COPY . . # 在/app目录7.2.5 ENV - 环境变量# 基本格式 ENV keyvalue ENV key1value1 key2value2 # 示例 ENV NODE_ENVproduction ENV APP_HOME/app \ APP_USERappuser # 在后续指令中使用 ENV APP_HOME/app WORKDIR ${APP_HOME} COPY . ${APP_HOME} # 运行时覆盖 # docker run -e NODE_ENVdevelopment myappARG vs ENV# ARG构建时参数 ARG PYTHON_VERSION3.9 FROM python:${PYTHON_VERSION} # docker build --build-arg PYTHON_VERSION3.10 -t myapp . # ENV运行时环境变量 ENV APP_ENVproduction # docker run -e APP_ENVdevelopment myapp # 组合使用 ARG APP_VERSION1.0 ENV VERSION${APP_VERSION}7.2.6 EXPOSE - 暴露端口# 声明端口仅文档作用 EXPOSE 80 EXPOSE 443 EXPOSE 8080/tcp EXPOSE 8080/udp # 多个端口 EXPOSE 80 443 8080 # 使用变量 ARG PORT8080 EXPOSE ${PORT}注意EXPOSE只是声明实际端口映射需要在运行时指定dockerrun -p8080:80 myapp7.2.7 VOLUME - 数据卷# 声明数据卷挂载点 VOLUME /data VOLUME [/var/log, /var/db] # 示例 FROM postgres:13 VOLUME /var/lib/postgresql/data7.2.8 USER - 切换用户# 切换到非root用户安全最佳实践 RUN groupadd -r appuser useradd -r -g appuser appuser USER appuser # 切换回root USER root # 使用UID USER 1000完整示例FROM python:3.9-slim # 创建用户 RUN groupadd -r appuser useradd -r -g appuser appuser WORKDIR /app # 以root身份安装系统依赖 RUN apt-get update apt-get install -y \ gcc \ rm -rf /var/lib/apt/lists/* # 复制文件 COPY requirements.txt . RUN pip install -r requirements.txt COPY . . # 修改文件所有者 RUN chown -R appuser:appuser /app # 切换到非root用户 USER appuser CMD [python, app.py]7.2.9 CMD - 容器启动命令# Shell形式 CMD python app.py # Exec形式推荐 CMD [python, app.py] # 作为ENTRYPOINT的参数 ENTRYPOINT [python] CMD [app.py]注意一个Dockerfile只能有一个CMD多个CMD只有最后一个生效运行时可以覆盖CMD# Dockerfile中的CMDCMD[python,app.py]# 运行时覆盖dockerrun myapp python other.py7.2.10 ENTRYPOINT - 入口点# Exec形式推荐 ENTRYPOINT [python, app.py] # Shell形式 ENTRYPOINT python app.py # 与CMD组合 ENTRYPOINT [python] CMD [app.py] # 等效于python app.py # 运行时可以只覆盖CMD部分ENTRYPOINT vs CMD# 示例1只用CMD CMD [nginx, -g, daemon off;] # 运行docker run myapp # 覆盖docker run myapp echo hello # 示例2只用ENTRYPOINT ENTRYPOINT [nginx, -g, daemon off;] # 运行docker run myapp # 不易覆盖需要--entrypoint # 示例3组合使用推荐 ENTRYPOINT [python] CMD [app.py] # 运行docker run myapp # 执行python app.py # 覆盖CMDdocker run myapp test.py # 执行python test.py实用示例# 启动脚本作为ENTRYPOINT COPY docker-entrypoint.sh / ENTRYPOINT [/docker-entrypoint.sh] CMD [nginx, -g, daemon off;]# docker-entrypoint.sh#!/bin/bashset-e# 初始化操作echoInitializing...# 数据库迁移、配置生成等# 执行CMDexec$7.3 构建上下文(Build Context)7.3.1 理解构建上下文# 构建命令dockerbuild -t myapp:1.0.# └─ 构建上下文路径# Docker会将该路径下的所有文件打包发送给守护进程# 输出Sending build context to Docker daemon 15.36MB项目结构示例myproject/ ├── Dockerfile ├── .dockerignore ├── app/ │ ├── __init__.py │ ├── main.py │ └── utils.py ├── requirements.txt ├── tests/ │ └── test_main.py ├── docs/ │ └── README.md └── .git/7.3.2 优化构建上下文.dockerignore文件# 版本控制 .git .gitignore .gitattributes # IDE配置 .vscode/ .idea/ *.swp *.swo # Python __pycache__/ *.py[cod] *$py.class .pytest_cache/ .coverage htmlcov/ # Node.js node_modules/ npm-debug.log yarn-error.log # 构建产物 dist/ build/ *.egg-info/ # 临时文件 *.tmp *.log .DS_Store Thumbs.db # 文档和测试 docs/ tests/ *.md LICENSE # 环境文件 .env .env.local7.3.3 使用远程上下文# 从Git仓库构建dockerbuild https://github.com/user/repo.git#branch# 从Git仓库的子目录构建dockerbuild https://github.com/user/repo.git#branch:subdir# 从tar文件构建dockerbuild http://example.com/context.tar.gz7.3.4 构建上下文最佳实践# 1. 在项目根目录创建Dockerfilemyproject/ ├── Dockerfile ├── src/ └──...# 2. 使用.dockerignore排除不需要的文件# 3. 避免COPY整个上下文# 不推荐COPY./app# 推荐只复制必要文件COPY requirements.txt /app/ COPY src/ /app/src/# 4. 查看构建上下文大小dockerbuild --no-cache -ttest.21|grepbuild context7.4 多阶段构建(Multi-stage Build)7.4.1 为什么需要多阶段构建问题# 单阶段构建 FROM golang:1.20 WORKDIR /app COPY . . RUN go build -o myapp # 结果镜像包含Go编译器等工具体积巨大~800MB解决# 多阶段构建 # 阶段1构建 FROM golang:1.20 AS builder WORKDIR /app COPY . . RUN go build -o myapp # 阶段2运行 FROM alpine:latest WORKDIR /app COPY --frombuilder /app/myapp . CMD [./myapp] # 结果只包含可执行文件和运行时依赖~15MB7.4.2 多阶段构建示例Python应用# 阶段1构建依赖 FROM python:3.9 AS builder WORKDIR /app COPY requirements.txt . # 安装依赖到指定目录 RUN pip install --user --no-cache-dir -r requirements.txt # 阶段2运行环境 FROM python:3.9-slim WORKDIR /app # 复制安装的依赖 COPY --frombuilder /root/.local /root/.local COPY . . # 确保PATH包含用户安装的包 ENV PATH/root/.local/bin:$PATH CMD [python, app.py]Node.js应用# 阶段1构建 FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 阶段2运行 FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --onlyproduction COPY --frombuilder /app/dist ./dist EXPOSE 3000 CMD [node, dist/index.js]Go应用# 阶段1构建 FROM golang:1.20-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build -a -installsuffix cgo -o main . # 阶段2运行 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --frombuilder /app/main . EXPOSE 8080 CMD [./main]7.4.3 命名构建阶段# 命名阶段 FROM golang:1.20 AS builder FROM node:18 AS frontend-builder FROM python:3.9 AS backend-builder # 从指定阶段复制 COPY --frombuilder /app/binary . COPY --fromfrontend-builder /app/dist ./static COPY --frombackend-builder /app/app.py .7.4.4 停止在特定阶段# 只构建到builder阶段用于调试dockerbuild --target builder -t myapp:builder.# 完整构建dockerbuild -t myapp:latest.调试用例FROM golang:1.20 AS builder WORKDIR /app COPY . . RUN go build -o myapp FROM alpine:latest AS debug COPY --frombuilder /app/myapp . RUN apk add --no-cache gdb CMD [gdb, ./myapp] FROM alpine:latest AS release COPY --frombuilder /app/myapp . CMD [./myapp]# 构建debug版本dockerbuild --target debug -t myapp:debug.# 构建release版本dockerbuild --target release -t myapp:release.# 或dockerbuild -t myapp:release.7.4.5 外部镜像作为阶段# 从外部镜像复制文件 FROM nginx:1.25 AS nginx-base FROM alpine:latest COPY --fromnginx-base /etc/nginx/nginx.conf /config/7.5 构建缓存优化技巧7.5.1 理解构建缓存Docker在构建时会缓存每一层FROM python:3.9-slim # Layer 1: 缓存 WORKDIR /app # Layer 2: 缓存 COPY requirements.txt . # Layer 3: 如果文件未改变使用缓存 RUN pip install -r requirements.txt # Layer 4: 如果Layer 3缓存使用缓存 COPY . . # Layer 5: 如果文件改变重新构建 CMD [python, app.py] # Layer 6: 重新构建7.5.2 优化层的顺序不好的顺序FROM python:3.9-slim WORKDIR /app COPY . . # 代码改变此层失效 RUN pip install -r requirements.txt # 每次都要重新安装 CMD [python, app.py]好的顺序FROM python:3.9-slim WORKDIR /app COPY requirements.txt . # 只有依赖改变时失效 RUN pip install -r requirements.txt # 大部分时间使用缓存 COPY . . # 代码改变只影响这一层 CMD [python, app.py]7.5.3 分离变化频繁的层# Node.js应用优化 FROM node:18-alpine WORKDIR /app # 1. 先复制package.json不常改变 COPY package*.json ./ RUN npm ci # 2. 再复制源代码经常改变 COPY src/ ./src/ COPY public/ ./public/ # 3. 最后复制配置文件 COPY config/ ./config/ CMD [npm, start]7.5.4 使用.dockerignore# .dockerignore node_modules .git .env *.log # 减少构建上下文避免不必要的缓存失效7.5.5 构建参数管理# 使用ARG避免硬编码 ARG PYTHON_VERSION3.9 FROM python:${PYTHON_VERSION}-slim ARG APP_VERSION1.0.0 ENV VERSION${APP_VERSION} # 标签信息 LABEL version${APP_VERSION} \ descriptionMy Application7.5.6 禁用缓存# 完全禁用缓存dockerbuild --no-cache -t myapp:1.0.# 从特定层开始禁用缓存dockerbuild --cache-from myapp:latest -t myapp:1.0.# 拉取缓存dockerpull myapp:latestdockerbuild --cache-from myapp:latest -t myapp:1.1.7.6 实战构建完整应用镜像7.6.1 Python Flask应用项目结构flask-app/ ├── Dockerfile ├── .dockerignore ├── requirements.txt ├── app.py └── templates/ └── index.htmlrequirements.txtFlask2.3.0 gunicorn20.1.0app.pyfromflaskimportFlask,render_template appFlask(__name__)app.route(/)defhello():returnrender_template(index.html)if__name____main__:app.run(host0.0.0.0,port5000)DockerfileFROM python:3.9-slim # 设置环境变量 ENV PYTHONUNBUFFERED1 \ PYTHONDONTWRITEBYTECODE1 # 创建非root用户 RUN groupadd -r appuser useradd -r -g appuser appuser WORKDIR /app # 安装系统依赖 RUN apt-get update apt-get install -y --no-install-recommends \ gcc \ rm -rf /var/lib/apt/lists/* # 安装Python依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制应用文件 COPY . . # 修改文件所有者 RUN chown -R appuser:appuser /app # 切换用户 USER appuser # 暴露端口 EXPOSE 5000 # 健康检查 HEALTHCHECK --interval30s --timeout3s --start-period5s --retries3 \ CMD curl -f http://localhost:5000/ || exit 1 # 启动应用 CMD [gunicorn, -b, 0.0.0.0:5000, -w, 4, app:app].dockerignore__pycache__ *.pyc .pytest_cache .coverage .env .git README.md构建和运行# 构建dockerbuild -t flask-app:1.0.# 运行dockerrun -d -p5000:5000 --name myflask flask-app:1.0# 测试curlhttp://localhost:50007.6.2 React前端应用Dockerfile# 多阶段构建 # 阶段1构建 FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # 阶段2生产环境 FROM nginx:1.25-alpine # 复制构建产物 COPY --frombuilder /app/build /usr/share/nginx/html # 复制Nginx配置 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]nginx.confserver { listen 80; server_name localhost; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } location /api { proxy_pass http://backend:8080; } }7.7 小结通过本章学习我们掌握了构建自定义镜像的核心技能✅Dockerfile语法基本结构和指令顺序构建上下文理解✅常用指令FROM、RUN、COPY、WORKDIRENV、EXPOSE、VOLUMECMD、ENTRYPOINT的区别✅构建上下文.dockerignore使用优化构建上下文大小✅多阶段构建减小镜像体积分离构建和运行环境✅缓存优化层的顺序优化分离变化频繁的内容✅实战应用Python应用镜像Node.js应用镜像最佳实践下一步在第8章中我们将学习镜像最佳实践选择合适的基础镜像减小镜像体积的技巧分层原理与优化安全扫描与漏洞修复这些是生产环境中必须掌握的技能。本章思考题CMD和ENTRYPOINT的区别是什么各适用于什么场景多阶段构建如何减小镜像体积如何优化Docker镜像的构建缓存为什么要使用非root用户运行容器相关资源Dockerfile参考https://docs.docker.com/engine/reference/builder/最佳实践https://docs.docker.com/develop/develop-images/dockerfile_best-practices/多阶段构建https://docs.docker.com/build/building/multi-stage/