7.1 Dockerfile语法详解
7.1.1 什么是Dockerfile
Dockerfile是一个文本文件,包含一系列指令,用于自动化构建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 <image>@<digest> # 示例 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_VERSION=3.9 FROM python:${PYTHON_VERSION}-slim选择基础镜像的原则:
- 官方镜像优先
- 选择合适的变体:
ubuntu:22.04- 完整系统(~77MB)python:3.9-slim- 精简版(~120MB)python:3.9-alpine- Alpine版(~50MB)
- 固定版本标签:使用
python:3.9而非python:latest
7.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 --chown=user:group file.txt /app/ # 设置权限 COPY --chmod=755 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 <key>=<value> ENV <key1>=<value1> <key2>=<value2> # 示例 ENV NODE_ENV=production ENV APP_HOME=/app \ APP_USER=appuser # 在后续指令中使用 ENV APP_HOME=/app WORKDIR ${APP_HOME} COPY . ${APP_HOME} # 运行时覆盖 # docker run -e NODE_ENV=development myappARG vs ENV:
# ARG:构建时参数 ARG PYTHON_VERSION=3.9 FROM python:${PYTHON_VERSION} # docker build --build-arg PYTHON_VERSION=3.10 -t myapp . # ENV:运行时环境变量 ENV APP_ENV=production # docker run -e APP_ENV=development myapp # 组合使用 ARG APP_VERSION=1.0 ENV VERSION=${APP_VERSION}7.2.6 EXPOSE - 暴露端口
# 声明端口(仅文档作用) EXPOSE 80 EXPOSE 443 EXPOSE 8080/tcp EXPOSE 8080/udp # 多个端口 EXPOSE 80 443 8080 # 使用变量 ARG PORT=8080 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 # 覆盖CMD:docker 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# 初始化操作echo"Initializing..."# 数据库迁移、配置生成等# 执行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.2>&1|grep"build context"7.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 --from=builder /app/myapp . CMD ["./myapp"] # 结果:只包含可执行文件和运行时依赖(~15MB)7.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 --from=builder /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 --only=production COPY --from=builder /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_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . # 阶段2:运行 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ COPY --from=builder /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 --from=builder /app/binary . COPY --from=frontend-builder /app/dist ./static COPY --from=backend-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 --from=builder /app/myapp . RUN apk add --no-cache gdb CMD ["gdb", "./myapp"] FROM alpine:latest AS release COPY --from=builder /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 --from=nginx-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_VERSION=3.9 FROM python:${PYTHON_VERSION}-slim ARG APP_VERSION=1.0.0 ENV VERSION=${APP_VERSION} # 标签信息 LABEL version="${APP_VERSION}" \ description="My Application"7.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.txt:
Flask==2.3.0 gunicorn==20.1.0app.py:
fromflaskimportFlask,render_template app=Flask(__name__)@app.route('/')defhello():returnrender_template('index.html')if__name__=='__main__':app.run(host='0.0.0.0',port=5000)Dockerfile:
FROM python:3.9-slim # 设置环境变量 ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 # 创建非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 --interval=30s --timeout=3s --start-period=5s --retries=3 \ 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 --from=builder /app/build /usr/share/nginx/html # 复制Nginx配置 COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]nginx.conf:
server { 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、WORKDIR
- ENV、EXPOSE、VOLUME
- CMD、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/