一、Dockerfile 基础概念
1.1 什么是 Dockerfile?
Dockerfile 是一个文本文件,包含了一系列用于构建 Docker 镜像的指令。它遵循特定的格式和语法,Docker 引擎通过读取这些指令来自动化构建镜像。以下是其基础示例:
dockerfile
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:指定基础镜像
bash
# 语法
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:添加元数据
bash
# 语法
LABEL <key>=<value> <key>=<value> ...
# 示例
LABEL version="1.0"
LABEL maintainer="john@example.com"
LABEL description="这是一个示例应用" \
author="John Doe" \
release-date="2024-01-01"
2.4 RUN:执行命令
bash
# 两种格式
# 1. shell格式(默认使用 /bin/sh -c)
RUN apt-get update && apt-get install -y \
python3 \
python3-pip \
&& rm -rf /var/lib/apt/lists/*
# 2. exec格式(推荐,避免shell解析问题)
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "python3"]
# 最佳实践:合并多个RUN指令减少镜像层
RUN apt-get update \
&& apt-get install -y \
curl \
wget \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /app
2.5 CMD:容器启动命令
bash
# 三种格式
# 1. exec格式(推荐)
CMD ["executable", "param1", "param2"]
# 2. shell格式
CMD command param1 param2
# 3. 作为ENTRYPOINT的默认参数
CMD ["param1", "param2"]
# 示例
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
重要:
- 一个 Dockerfile 只能有一个 CMD
- 会被 docker run 后面的命令覆盖
- 主要提供容器默认的执行命令
2.6 ENTRYPOINT:入口点
bash
# 两种格式
# 1. exec格式(推荐)
ENTRYPOINT ["executable", "param1", "param2"]
# 2. shell格式
ENTRYPOINT command param1 param2
# 示例
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
ENTRYPOINT ["/docker-entrypoint.sh"]
ENTRYPOINT vs CMD:
bash
# 组合使用示例
ENTRYPOINT ["java", "-jar", "/app/app.jar"]
CMD ["--spring.profiles.active=prod"]
# docker run myapp --debug 将执行:
# java -jar /app/app.jar --debug
2.7 COPY:复制文件
bash
# 语法
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:增强的复制
bash
# ADD 支持更多功能
ADD https://example.com/file.tar.gz /tmp/
ADD file.tar.gz /tmp/ # 自动解压tar.gz
ADD --chown=user:group src dest
# 注意事项
# 1. 会自动解压tar文件(.tar, .tar.gz, .tar.bz2等)
# 2. 支持URL下载
# 3. 尽量使用COPY,除非需要ADD的特殊功能
2.9 WORKDIR:设置工作目录
bash
# 语法
WORKDIR /path/to/workdir
# 示例
WORKDIR /app
RUN pwd # 输出:/app
WORKDIR src
RUN pwd # 输出:/app/src
# 相对于之前的WORKDIR
WORKDIR /opt
WORKDIR app
RUN pwd # 输出:/opt/app
2.10 ENV:设置环境变量
bash
# 语法
ENV <key> <value>
ENV <key>=<value> ...
# 示例
ENV NODE_ENV production
ENV APP_HOME /app
ENV PATH /app/bin:$PATH
# 多行定义
ENV JAVA_HOME=/usr/lib/jvm/java-11-openjdk \
PATH=$PATH:$JAVA_HOME/bin
2.11 ARG:构建参数
bash
# 语法
ARG <name>[=<default value>]
# 示例
ARG VERSION=latest
ARG USERNAME
ARG PASSWORD
# 使用ARG
FROM ubuntu:${VERSION:-22.04}
RUN echo "Building version: $VERSION"
# 构建时传递参数
# docker build --build-arg VERSION=1.0 --build-arg USERNAME=admin .
2.12 EXPOSE:声明端口
bash
# 语法
EXPOSE <port> [<port>/<protocol>...]
# 示例
EXPOSE 80
EXPOSE 443/tcp
EXPOSE 8080/udp
EXPOSE 3000 5000 8000
# EXPOSE 只是声明,不会实际打开端口。实际端口映射在 docker run 时指定。
2.13 VOLUME:定义数据卷
bash
# 语法
VOLUME ["/path/to/dir"]
VOLUME /var/log /var/db
# 示例
VOLUME /data
VOLUME ["/var/log", "/var/db"]
VOLUME /var/lib/mysql
2.14 USER:指定运行用户
bash
# 语法
USER <user>[:<group>]
USER <UID>[:<GID>]
# 示例
USER nobody
USER 1000:1000
USER appuser:appgroup
# 创建用户示例
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
USER appuser
2.15 HEALTHCHECK:健康检查
bash
# 语法
HEALTHCHECK [OPTIONS] CMD command
HEALTHCHECK NONE # 禁用从基础镜像继承的健康检查
# 选项
--interval=DURATION # 检查间隔(默认30s)
--timeout=DURATION # 超时时间(默认30s)
--start-period=DURATION # 启动宽限期(默认0s)
--retries=N # 重试次数(默认3次)
# 示例
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/health || exit 1
HEALTHCHECK CMD pg_isready -U postgres || exit 1
HEALTHCHECK CMD nc -z localhost 80 || exit 1
2.16 SHELL:指定默认shell
bash
# 语法
SHELL ["executable", "parameters"]
# 示例
# Windows容器使用PowerShell
SHELL ["powershell", "-Command"]
# Linux容器使用bash
SHELL ["/bin/bash", "-c"]
2.17 ONBUILD:延迟执行指令
bash
# 语法
ONBUILD <INSTRUCTION>
# 示例
ONBUILD COPY . /app/src
ONBUILD RUN make /app/src
ONBUILD ADD . /app
# 使用场景:构建基础镜像
FROM node:18 AS base
ONBUILD COPY package*.json ./
ONBUILD RUN npm ci
ONBUILD COPY . .
三、多阶段构建(Multi-stage Builds)
3.1 基本概念
bash
# 第一阶段:构建阶段
FROM golang:1.20 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp .
# 第二阶段:运行阶段
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/myapp .
CMD ["./myapp"]
3.2 复杂多阶段示例
bash
# 第一阶段:构建前端
FROM node:18 AS frontend-builder
WORKDIR /app/frontend
COPY frontend/package*.json ./
RUN npm ci
COPY frontend/ ./
RUN npm run build
# 第二阶段:构建后端
FROM golang:1.20 AS backend-builder
WORKDIR /app/backend
COPY backend/ ./
RUN CGO_ENABLED=0 GOOS=linux go build -o app .
# 第三阶段:生成最终镜像
FROM alpine:latest
RUN apk --no-cache add ca-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
EXPOSE 8080
CMD ["./app"]
四、Dockerfile 最佳实践
4.1 优化镜像大小
bash
# 不推荐:创建多个层
RUN apt-get update
RUN apt-get install -y python
RUN rm -rf /var/lib/apt/lists/*
# 推荐:合并指令
RUN apt-get update \
&& apt-get install -y \
python3 \
python3-pip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
4.2 使用 .dockerignore 文件
bash
# 忽略文件示例
.git
.gitignore
node_modules
*.log
*.tmp
Dockerfile
README.md
.env
.vscode
*.md
*.txt
4.3 安全最佳实践
bash
# 1. 使用非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser
# 2. 使用官方基础镜像
FROM alpine:3.18
# 3. 定期更新基础镜像
# 使用特定版本,而不是latest
FROM ubuntu:22.04
# 4. 扫描安全漏洞
# 构建后运行:docker scan <image-name>
4.4 构建缓存优化
bash
# 将不经常变化的部分放在前面
# 1. 安装依赖
COPY package.json package-lock.json ./
RUN npm ci
# 2. 复制源代码(经常变化)
COPY . .
# 3. 构建应用
RUN npm run build
五、完整示例
5.1 项目结构
bash
myapp/
├── Dockerfile
├── .dockerignore
├── requirements.txt
├── app.py
└── config/
└── settings.py
5.2 Dockerfile
bash
# 第一阶段:构建阶段
FROM python:3.11-slim AS builder
WORKDIR /app
# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1
# 安装系统依赖
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
gcc \
g++ \
libpq-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# 创建虚拟环境
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 安装Python依赖
COPY requirements.txt .
RUN pip install --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt
# 第二阶段:运行阶段
FROM python:3.11-slim
WORKDIR /app
# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser -m -d /app appuser
# 从构建阶段复制虚拟环境
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# 复制应用代码
COPY --chown=appuser:appuser . .
# 切换用户
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:5000/health', timeout=2)" || exit 1
# 暴露端口
EXPOSE 5000
# 启动命令
ENTRYPOINT ["python"]
CMD ["app.py"]
5.3 .dockerignore 文件
bash
# 依赖缓存
__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 构建镜像
bash
# 基本构建
docker build -t myapp:1.0 .
# 使用构建参数
docker build \
--build-arg VERSION=2.0 \
--build-arg ENVIRONMENT=prod \
-t myapp:2.0 .
# 多阶段构建指定阶段
docker build --target builder -t myapp:builder .
# 使用不同的Dockerfile
docker build -f Dockerfile.prod -t myapp:prod .
6.2 测试镜像
bash
# 运行容器
docker run -d -p 5000:5000 --name myapp myapp:1.0
# 检查容器状态
docker ps
docker logs myapp
# 执行健康检查
docker inspect --format='{{.State.Health.Status}}' myapp
# 进入容器
docker exec -it myapp sh
# 测试应用
curl http://localhost:5000/health
七、高级技巧
7.1 使用 ARG 和 ENV
bash
# 构建时可以覆盖的变量
ARG NODE_ENV=production
# 构建时设置的变量,运行时也可用
ENV NODE_ENV=${NODE_ENV}
# 构建时传递密码(不推荐,会被记录在镜像历史中)
ARG DB_PASSWORD
RUN echo "Password is: $DB_PASSWORD"
7.2 构建缓存控制
bash
# 使用 --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-get update && apt-get install -y package
7.3 构建上下文优化
bash
# 创建小构建上下文
# 1. 使用 .dockerignore 排除不必要文件
# 2. 将 Dockerfile 放在项目根目录
# 3. 将构建上下文限制在必需文件