Dockerfile 详解

一、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)的概念。每个指令都会创建一个新的镜像层,这些层是只读的,并且会被缓存以加速后续构建。构建过程大致如下:

  1. 读取Dockerfile:Docker引擎从Dockerfile中读取指令。
  2. 构建上下文:Docker客户端会将构建上下文(通常是Dockerfile所在目录)的所有文件发送给Docker守护进程。因此,为避免构建缓慢,通常使用.dockerignore文件来排除不必要的文件。
  3. 逐行执行指令:Docker引擎按照顺序执行Dockerfile中的指令,每一条指令都会生成一个新的中间镜像层。
  4. 缓存机制:如果指令与之前构建的中间层相同(通过校验和判断),则使用缓存,否则重新执行该指令并生成新的层。
  5. 最终镜像:所有指令执行完毕后,生成最终的镜像。

二、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. 将构建上下文限制在必需文件
相关推荐
专家大圣3 小时前
摆脱局域网!Logseq 搭配cpolar公网访问让笔记管理更自由
linux·网络·docker·内网穿透·cpolar
小黑要上天3 小时前
8-docker run --rm选项说明
运维·docker·容器
小翰子_3 小时前
Docker 常用笔记(速查版)
笔记·docker·容器
2401_831501733 小时前
Devops之Docker安装和使用
运维·docker·devops
Lethehong3 小时前
RAG-全链路问答系统:从零到容器化部署的终极指南
docker·云原生·cnb
.柒宇.3 小时前
shell脚本之Docker安装
运维·docker·容器
LingRannn4 小时前
Ubuntu 24.04 安装 Docker Engine
linux·ubuntu·docker
凯子坚持 c4 小时前
Docker存储卷深度解析:机制、管理与数据持久化实战
运维·docker·容器
蟑螂恶霸4 小时前
使用docker安装windows 11
运维·docker·容器