一、Dockerfile 基础概念
1.1 什么是 Dockerfile?
Dockerfile 是一个文本文件,包含了一系列用于构建 Docker 镜像的指令。它遵循特定的格式和语法,Docker 引擎通过读取这些指令来自动化构建镜像。以下是其基础示例:
FROM ubuntu:20.04 RUN apt-get update && apt-get install -y nginx COPY index.html /var/www/html/ EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]1.2 Dockerfile 工作原理
Dockerfile 构建镜像的过程是基于分层存储(layer)的概念。每个指令都会创建一个新的镜像层,这些层是只读的,并且会被缓存以加速后续构建。构建过程大致如下:
- 读取Dockerfile:Docker引擎从Dockerfile中读取指令。
- 构建上下文:Docker客户端会将构建上下文(通常是Dockerfile所在目录)的所有文件发送给Docker守护进程。因此,为避免构建缓慢,通常使用.dockerignore文件来排除不必要的文件。
- 逐行执行指令:Docker引擎按照顺序执行Dockerfile中的指令,每一条指令都会生成一个新的中间镜像层。
- 缓存机制:如果指令与之前构建的中间层相同(通过校验和判断),则使用缓存,否则重新执行该指令并生成新的层。
- 最终镜像:所有指令执行完毕后,生成最终的镜像。
二、Dockerfile 指令详解
2.1 Dockerfile 常用指令
| 指令 | 说明 |
|---|---|
| FROM | 设置构建镜像时使用的基础镜像 |
| MAINTAINER | 镜像的创建者 |
| RUN | 构建镜像时用于执行后面跟着的命令行命令(在 docker build 时执行) |
| CMD | 类似于 RUN 指令,启动容器时用于执行后面跟着的命令行命令(在 docker run 时执行) |
| ENTRYPOINT | 启动容器时会将其后面的命令当作参数,结合CMD 指令的命令一起执行 |
| COPY | 构建镜像时复制文件或者目录到容器里指定路径 |
| ADD | 功能与COPY指令类似,不同点在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,ADD 指令会自动复制并解压到 <目标路径>,在不解压的前提下,无法复制 tar 压缩文件(推荐使用 COPY指令) |
| ENV | 设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量 |
| ARG | 构建参数,与 ENV 作用一至。不过作用域不一样。ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量 |
| VOLUME | 定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷 |
| EXPOSE | 声明端口 |
| WORKDIR | 指定工作目录。用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR 指定的工作目录,必须是提前创建好的)。 |
| USER | 用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。 |
| LABEL | 用来给镜像添加一些元数据(metadata),以键值对的形式 |
| ONBUILD | 用于延迟构建命令的执行。简单的说,就是 Dockerfile 里用 ONBUILD 指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build)。当有新的 Dockerfile 使用了之前构建的镜像 FROM test-build ,这是执行新镜像的 Dockerfile 构建时候,会执行 test-build 的 Dockerfile 里的 ONBUILD 指定的命令 |
2.2 FROM:指定基础镜像
# 语法FROM<image>[:<tag>][AS<name>]# 示例FROM ubuntu:22.04 FROM python:3.9-slim AS builder FROM --platform=linux/amd64 node:18-alpine说明:
- 必须是 Dockerfile 的第一个有效指令(除了 ARG)
- 可以多次使用,用于多阶段构建
- AS 为构建阶段命名
2.3 LABEL:添加元数据
# 语法LABEL<key>=<value><key>=<value>...# 示例LABELversion="1.0"LABELmaintainer="john@example.com"LABELdescription="这是一个示例应用"\author="John Doe"\release-date="2024-01-01"2.4 RUN:执行命令
# 两种格式# 1. shell格式(默认使用 /bin/sh -c)RUNapt-getupdate&&apt-getinstall-y\python3\python3-pip\&&rm-rf /var/lib/apt/lists/*# 2. exec格式(推荐,避免shell解析问题)RUN["apt-get","update"]RUN["apt-get","install","-y","python3"]# 最佳实践:合并多个RUN指令减少镜像层RUNapt-getupdate\&&apt-getinstall-y\curl\wget\git\&&apt-getclean\&&rm-rf /var/lib/apt/lists/*\&&mkdir-p /app2.5 CMD:容器启动命令
# 三种格式# 1. exec格式(推荐)CMD["executable","param1","param2"]# 2. shell格式CMDcommandparam1 param2# 3. 作为ENTRYPOINT的默认参数CMD["param1","param2"]# 示例CMD["nginx","-g","daemon off;"]CMD["python","app.py"]重要:
- 一个 Dockerfile 只能有一个 CMD
- 会被 docker run 后面的命令覆盖
- 主要提供容器默认的执行命令
2.6 ENTRYPOINT:入口点
# 两种格式# 1. exec格式(推荐)ENTRYPOINT["executable","param1","param2"]# 2. shell格式ENTRYPOINTcommandparam1 param2# 示例ENTRYPOINT["java","-jar","/app/app.jar"]ENTRYPOINT["/docker-entrypoint.sh"]ENTRYPOINT vs CMD:
# 组合使用示例ENTRYPOINT["java","-jar","/app/app.jar"]CMD["--spring.profiles.active=prod"]# docker run myapp --debug 将执行:# java -jar /app/app.jar --debug2.7 COPY:复制文件
# 语法COPY[--chown=<user>:<group>]<src>...<dest># 示例COPY package.json /app/ COPY requirements.txt /app/ COPY --chown=appuser:appgroup app.py /app/ COPY --from=builder /build/app /app/ COPY src/ /app/src/ COPY *.txt /app/# 模式匹配COPY hom* /mydir/# 复制所有以hom开头的文件COPY hom?.txt /mydir/# ? 匹配单个字符2.8 ADD:增强的复制
# ADD 支持更多功能ADD https://example.com/file.tar.gz /tmp/ ADD file.tar.gz /tmp/# 自动解压tar.gzADD --chown=user:group src dest# 注意事项# 1. 会自动解压tar文件(.tar, .tar.gz, .tar.bz2等)# 2. 支持URL下载# 3. 尽量使用COPY,除非需要ADD的特殊功能2.9 WORKDIR:设置工作目录
# 语法WORKDIR /path/to/workdir# 示例WORKDIR /app RUNpwd# 输出:/appWORKDIR src RUNpwd# 输出:/app/src# 相对于之前的WORKDIRWORKDIR /opt WORKDIR app RUNpwd# 输出:/opt/app2.10 ENV:设置环境变量
# 语法ENV<key><value>ENV<key>=<value>...# 示例ENV NODE_ENV production ENV APP_HOME /app ENVPATH/app/bin:$PATH# 多行定义ENVJAVA_HOME=/usr/lib/jvm/java-11-openjdk\PATH=$PATH:$JAVA_HOME/bin2.11 ARG:构建参数
# 语法ARG<name>[=<default value>]# 示例ARGVERSION=latest ARG USERNAME ARG PASSWORD# 使用ARGFROM ubuntu:${VERSION:-22.04}RUNecho"Building version:$VERSION"# 构建时传递参数# docker build --build-arg VERSION=1.0 --build-arg USERNAME=admin .2.12 EXPOSE:声明端口
# 语法EXPOSE<port>[<port>/<protocol>...]# 示例EXPOSE80EXPOSE443/tcp EXPOSE8080/udp EXPOSE300050008000# EXPOSE 只是声明,不会实际打开端口。实际端口映射在 docker run 时指定。2.13 VOLUME:定义数据卷
# 语法VOLUME["/path/to/dir"]VOLUME /var/log /var/db# 示例VOLUME /data VOLUME["/var/log","/var/db"]VOLUME /var/lib/mysql2.14 USER:指定运行用户
# 语法USER<user>[:<group>]USER<UID>[:<GID>]# 示例USERnobodyUSER1000:1000USERappuser:appgroup# 创建用户示例RUNgroupadd-r appgroup&&useradd-r -g appgroup appuserUSERappuser2.15 HEALTHCHECK:健康检查
# 语法HEALTHCHECK[OPTIONS]CMDcommandHEALTHCHECK NONE# 禁用从基础镜像继承的健康检查# 选项--interval=DURATION# 检查间隔(默认30s)--timeout=DURATION# 超时时间(默认30s)--start-period=DURATION# 启动宽限期(默认0s)--retries=N# 重试次数(默认3次)# 示例HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3\CMDcurl-f http://localhost:8080/health||exit1HEALTHCHECK CMD pg_isready -U postgres||exit1HEALTHCHECK CMDnc-z localhost80||exit12.16 SHELL:指定默认shell
# 语法SHELL["executable","parameters"]# 示例# Windows容器使用PowerShellSHELL["powershell","-Command"]# Linux容器使用bashSHELL["/bin/bash","-c"]2.17 ONBUILD:延迟执行指令
# 语法ONBUILD<INSTRUCTION># 示例ONBUILD COPY./app/src ONBUILD RUNmake/app/src ONBUILD ADD./app# 使用场景:构建基础镜像FROM node:18 AS base ONBUILD COPY package*.json ./ ONBUILD RUNnpmci ONBUILD COPY..三、多阶段构建(Multi-stage Builds)
3.1 基本概念
# 第一阶段:构建阶段FROM golang:1.20 AS builder WORKDIR /app COPY..RUN go build -o myapp.# 第二阶段:运行阶段FROM alpine:latest RUN apk --no-cacheaddca-certificates WORKDIR /root/ COPY --from=builder /app/myapp.CMD["./myapp"]3.2 复杂多阶段示例
# 第一阶段:构建前端FROM node:18 AS frontend-builder WORKDIR /app/frontend COPY frontend/package*.json ./ RUNnpmci COPY frontend/ ./ RUNnpmrun build# 第二阶段:构建后端FROM golang:1.20 AS backend-builder WORKDIR /app/backend COPY backend/ ./ RUNCGO_ENABLED=0GOOS=linux go build -o app.# 第三阶段:生成最终镜像FROM alpine:latest RUN apk --no-cacheaddca-certificates WORKDIR /app# 从前端构建阶段复制构建结果COPY --from=frontend-builder /app/frontend/dist ./public# 从后端构建阶段复制可执行文件COPY --from=backend-builder /app/backend/app.# 从另一个镜像复制文件COPY --from=nginx:alpine /etc/nginx/nginx.conf /etc/nginx/nginx.conf EXPOSE8080CMD["./app"]四、Dockerfile 最佳实践
4.1 优化镜像大小
# 不推荐:创建多个层RUNapt-getupdate RUNapt-getinstall-y python RUNrm-rf /var/lib/apt/lists/*# 推荐:合并指令RUNapt-getupdate\&&apt-getinstall-y\python3\python3-pip\&&apt-getclean\&&rm-rf /var/lib/apt/lists/*4.2 使用 .dockerignore 文件
# 忽略文件示例.git .gitignore node_modules *.log *.tmp Dockerfile README.md .env .vscode *.md *.txt4.3 安全最佳实践
# 1. 使用非root用户RUNgroupadd-r appuser&&useradd-r -g appuser appuserUSERappuser# 2. 使用官方基础镜像FROM alpine:3.18# 3. 定期更新基础镜像# 使用特定版本,而不是latestFROM ubuntu:22.04# 4. 扫描安全漏洞# 构建后运行:docker scan <image-name>4.4 构建缓存优化
# 将不经常变化的部分放在前面# 1. 安装依赖COPY package.json package-lock.json ./ RUNnpmci# 2. 复制源代码(经常变化)COPY..# 3. 构建应用RUNnpmrun build五、完整示例
5.1 项目结构
myapp/ ├── Dockerfile ├── .dockerignore ├── requirements.txt ├── app.py └── config/ └── settings.py5.2 Dockerfile
# 第一阶段:构建阶段FROM python:3.11-slim AS builder WORKDIR /app# 设置环境变量ENVPYTHONDONTWRITEBYTECODE=1\PYTHONUNBUFFERED=1\PIP_NO_CACHE_DIR=1# 安装系统依赖RUNapt-getupdate\&&apt-getinstall-y --no-install-recommends\gcc\g++\libpq-dev\&&apt-getclean\&&rm-rf /var/lib/apt/lists/*# 创建虚拟环境RUN python -m venv /opt/venv ENVPATH="/opt/venv/bin:$PATH"# 安装Python依赖COPY requirements.txt.RUN pipinstall--upgrade pip\&&pipinstall--no-cache-dir -r requirements.txt# 第二阶段:运行阶段FROM python:3.11-slim WORKDIR /app# 创建非root用户RUNgroupadd-r appuser&&useradd-r -g appuser -m -d /app appuser# 从构建阶段复制虚拟环境COPY --from=builder /opt/venv /opt/venv ENVPATH="/opt/venv/bin:$PATH"# 复制应用代码COPY --chown=appuser:appuser..# 切换用户USERappuser# 健康检查HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3\CMD python -c"import requests; requests.get('http://localhost:5000/health', timeout=2)"||exit1# 暴露端口EXPOSE5000# 启动命令ENTRYPOINT["python"]CMD["app.py"]5.3 .dockerignore 文件
# 依赖缓存__pycache__/ *.py[cod]*$py.class# 虚拟环境venv/ env/ .env# IDE.vscode/ .idea/ *.swp *.swo# 日志*.log# 测试tests/ .coverage htmlcov/# 其他.DS_Store .git/ .gitignore README.md docker-compose.yml六、构建和测试
6.1 构建镜像
# 基本构建docker build -t myapp:1.0.# 使用构建参数docker build\--build-argVERSION=2.0\--build-argENVIRONMENT=prod\-t myapp:2.0.# 多阶段构建指定阶段docker build --target builder -t myapp:builder.# 使用不同的Dockerfiledocker build -f Dockerfile.prod -t myapp:prod.6.2 测试镜像
# 运行容器docker run -d -p5000:5000 --name myapp myapp:1.0# 检查容器状态dockerpsdocker logs myapp# 执行健康检查docker inspect --format='{{.State.Health.Status}}'myapp# 进入容器dockerexec-it myappsh# 测试应用curlhttp://localhost:5000/health七、高级技巧
7.1 使用 ARG 和 ENV
# 构建时可以覆盖的变量ARGNODE_ENV=production# 构建时设置的变量,运行时也可用ENVNODE_ENV=${NODE_ENV}# 构建时传递密码(不推荐,会被记录在镜像历史中)ARG DB_PASSWORD RUNecho"Password is:$DB_PASSWORD"7.2 构建缓存控制
# 使用 --no-cache 构建:docker build --no-cache -t myapp .# 或使用特定指令禁用缓存ADD http://example.com/file.tar.gz /tmp/# 上面的ADD会禁用这一层及之后的所有缓存# 使用特定的缓存键RUN --mount=type=cache,target=/var/cache/apt\apt-getupdate&&apt-getinstall-y package7.3 构建上下文优化
# 创建小构建上下文# 1. 使用 .dockerignore 排除不必要文件# 2. 将 Dockerfile 放在项目根目录# 3. 将构建上下文限制在必需文件