跳到主要内容

Dockerfile 编写

Dockerfile 是一个文本文件,包含构建 Docker 镜像的所有指令。本章将详细介绍 Dockerfile 的语法和最佳实践。

Dockerfile 基础

什么是 Dockerfile

Dockerfile 是一个用于构建镜像的脚本文件,每条指令都会在镜像中创建一个新的层。Docker 读取 Dockerfile 中的指令,按顺序执行构建过程。

基本结构

# 注释
INSTRUCTION arguments
  • 指令不区分大小写,但约定使用大写
  • 指令按顺序执行
  • Dockerfile 必须以 FROM 指令开头

解析器指令(Parser Directives)

解析器指令是可选的,影响 Dockerfile 后续行的处理方式。它们不添加构建层,也不会显示为构建步骤。解析器指令必须写在 Dockerfile 最顶部。

syntax 指令

声明 Dockerfile 使用的语法版本:

# syntax=docker/dockerfile:1
FROM alpine

使用语法版本的好处:

  • 自动使用最新的 Dockerfile 语法特性,无需升级 Docker
  • 启用高级构建特性,如 heredoc、构建缓存挂载等
  • 保持向前兼容

推荐的语法版本:

版本说明
docker/dockerfile:1最新稳定版,推荐日常使用
docker/dockerfile:1-labs实验性功能版本
docker/dockerfile-upstream:master最新开发版

escape 指令

设置转义字符,默认为反斜杠 \

# escape=`
FROM microsoft/nanoserver
COPY testfile.txt c:\
RUN dir c:\

这在 Windows 上特别有用,因为反斜杠是 Windows 路径分隔符。使用反引号 ` 作为转义字符可以避免路径冲突。

check 指令

配置构建检查的执行方式:

# check=skip=JSONArgsRecommended,StageNameCasing
# check=error=true

参数说明:

参数说明
skip=<checks>跳过指定的检查项,多个用逗号分隔
skip=all跳过所有检查
error=true将检查失败视为错误而非警告

常用检查项:

  • JSONArgsRecommended:JSON 参数格式建议
  • StageNameCasing:阶段命名大小写规范
  • MaintainerDeprecated:MAINTAINER 指令已弃用警告

构建镜像

# 基本构建
docker build -t my-image:v1 .

# 指定 Dockerfile 路径
docker build -t my-image:v1 -f Dockerfile.dev .

# 构建时传递参数
docker build -t my-image:v1 --build-arg VERSION=1.0 .

# 不使用缓存构建
docker build -t my-image:v1 --no-cache .

基础指令

FROM - 基础镜像

指定构建镜像的基础镜像,必须是 Dockerfile 的第一条指令:

# 使用最新版本
FROM ubuntu

# 指定版本
FROM ubuntu:22.04

# 使用特定平台
FROM --platform=linux/arm64 ubuntu:22.04

# 多阶段构建命名
FROM node:18 AS builder

LABEL - 元数据标签

添加镜像的元数据信息:

LABEL maintainer="[email protected]"
LABEL version="1.0.0"
LABEL description="这是一个示例镜像"

# 合并多个标签
LABEL maintainer="[email protected]" \
version="1.0.0" \
description="示例镜像"

ARG - 构建参数

定义构建时的变量,只在构建过程中有效:

# 定义构建参数
ARG VERSION=1.0
ARG NODE_VERSION=18

# 使用参数
FROM node:${NODE_VERSION}

# 构建时覆盖
# docker build --build-arg VERSION=2.0 -t my-image .

ENV - 环境变量

设置环境变量,在容器运行时仍然有效:

# 设置单个环境变量
ENV APP_HOME=/app

# 设置多个环境变量
ENV APP_HOME=/app \
NODE_ENV=production \
PORT=3000

# 使用环境变量
WORKDIR ${APP_HOME}

ENV 与 ARG 的区别

特性ARGENV
作用范围构建阶段构建和运行阶段
可被覆盖构建时 --build-arg运行时 -e
可见性不在镜像中在镜像中

工作目录和用户

WORKDIR - 工作目录

设置工作目录,类似于 cd 命令:

# 设置工作目录
WORKDIR /app

# 如果目录不存在,会自动创建
WORKDIR /app/src

# 可以使用环境变量
ENV APP_HOME=/app
WORKDIR ${APP_HOME}

# 相对路径(相对于上一个 WORKDIR)
WORKDIR subdirectory

USER - 运行用户

指定运行容器时的用户:

# 创建用户
RUN useradd -m -s /bin/bash appuser

# 切换用户
USER appuser

# 切换用户和组
USER appuser:appgroup

# 使用 UID
USER 1000

文件操作

COPY - 复制文件

从构建上下文复制文件到镜像:

# 复制单个文件
COPY package.json /app/

# 复制目录
COPY src/ /app/src/

# 复制多个文件
COPY package.json package-lock.json /app/

# 使用通配符
COPY *.json /app/

# 保持文件属性
COPY --chown=appuser:appgroup app/ /app/

# 从其他阶段复制(多阶段构建)
COPY --from=builder /app/dist /app/dist

ADD - 添加文件

功能更强大的文件添加指令:

# 添加本地文件
ADD app.tar.gz /app/

# 添加远程文件(自动下载)
ADD https://example.com/file.txt /app/

# 自动解压 tar 文件
ADD archive.tar.gz /app/

COPY vs ADD

特性COPYADD
复制本地文件支持支持
下载远程文件不支持支持
自动解压不支持支持
推荐使用特定场景
最佳实践

优先使用 COPY,ADD 仅在需要自动解压或下载远程文件时使用。

执行命令

RUN - 运行命令

在构建过程中执行命令:

# shell 格式
RUN apt-get update

# exec 格式(推荐)
RUN ["apt-get", "update"]

# 多行命令使用 && 连接
RUN apt-get update && apt-get install -y \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*

# 使用 heredoc(Dockerfile 1.4+)
RUN <<EOF
apt-get update
apt-get install -y curl vim
rm -rf /var/lib/apt/lists/*
EOF

最佳实践

  1. 合并多个 RUN 命令,减少镜像层数
  2. 在同一层清理缓存文件
  3. 使用 apt-get install -y --no-install-recommends 减少安装包
# 好的做法
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*

# 不好的做法(产生两个层)
RUN apt-get update
RUN apt-get install -y curl

RUN --mount 高级用法

RUN 指令支持 --mount 选项,可以挂载文件系统到构建过程中。这是 BuildKit 提供的高级特性。

类型一:bind 挂载

从构建上下文或其他阶段挂载文件:

# syntax=docker/dockerfile:1

FROM alpine
# 从构建上下文挂载配置文件
RUN --mount=type=bind,source=config.json,target=/app/config.json \
cat /app/config.json

# 从其他构建阶段挂载
FROM node:18 AS builder
WORKDIR /app
RUN npm build

FROM alpine
# 挂载 builder 阶段的产物
RUN --mount=type=bind,from=builder,source=/app/dist,target=/dist \
ls /dist

类型二:cache 挂载

缓存包管理器的依赖,加速后续构建:

# syntax=docker/dockerfile:1

# Go 模块缓存
FROM golang:1.21
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download

# npm 缓存
FROM node:18
RUN --mount=type=cache,target=/root/.npm \
npm install

# pip 缓存
FROM python:3.11
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt

# apt 缓存
FROM ubuntu:22.04
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gcc

cache 挂载的常用选项:

选项说明
target挂载路径
id缓存标识符,区分不同缓存
sharing共享模式:sharedprivatelocked
ro只读模式

类型三:tmpfs 挂载

在构建过程中挂载临时内存文件系统。tmpfs 挂载的数据存储在内存中,构建结束后自动销毁,适合存储临时数据。

# syntax=docker/dockerfile:1

FROM alpine
# 挂载 tmpfs 用于临时文件
RUN --mount=type=tmpfs,target=/tmp \
# 在 /tmp 中处理临时文件,不会写入镜像层
dd if=/dev/urandom of=/tmp/random bs=1M count=10 && \
sha256sum /tmp/random

# 指定大小限制
RUN --mount=type=tmpfs,target=/cache,size=100m \
# 限制 tmpfs 大小为 100MB
./build-process.sh

tmpfs 挂载选项

选项说明
target挂载路径
size文件系统大小上限

适用场景

  • 需要临时存储但不希望写入镜像层
  • 敏感数据处理后立即销毁
  • 加速临时文件的读写(内存速度)
  • 避免磁盘 IO 开销

类型四:secret 挂载

安全地访问敏感信息,不会写入镜像层。secret 挂载有两种方式:挂载为文件或挂载为环境变量。

挂载为文件(默认方式)

# syntax=docker/dockerfile:1

FROM alpine
# 挂载密钥文件
RUN --mount=type=secret,id=aws_credentials \
aws configure set credentials.file /run/secrets/aws_credentials

挂载为环境变量

从 Dockerfile v1.10.0 开始,可以使用 env 选项将 secret 挂载为环境变量:

# syntax=docker/dockerfile:1

FROM alpine
# 将 API_KEY 挂载为环境变量
RUN --mount=type=secret,id=api_key,env=API_KEY \
curl -H "Authorization: Bearer $API_KEY" https://api.example.com

# 同时挂载为文件和环境变量
RUN --mount=type=secret,id=db_password,target=/run/secrets/db_password,env=DB_PASSWORD \
./setup-database.sh

secret 挂载选项

选项说明
idsecret 标识符,默认为 target 路径的文件名
target挂载路径,默认为 /run/secrets/ + id
env挂载为环境变量(Dockerfile v1.10.0+)
requiredsecret 不可用时是否报错,默认 false
mode文件权限,默认 0400
uid文件用户 ID,默认 0
gid文件组 ID,默认 0

构建时传递密钥:

# 从文件传递 secret
docker buildx build --secret id=aws_credentials,src=$HOME/.aws/credentials .

# 从环境变量传递 secret(需要先设置环境变量)
export API_KEY=your_api_key
docker buildx build --secret id=api_key,env=API_KEY .

# 从环境变量传递(显式指定源)
docker buildx build --secret id=api_key,src=/dev/stdin <<< "$API_KEY"

安全注意事项

  • secret 不会写入镜像层,也不会出现在构建缓存中
  • 只有声明了该 secret 的 RUN 指令才能访问
  • 构建完成后 secret 会自动清理
  • 敏感信息不应硬编码在 Dockerfile 中

类型四:ssh 挂载

在构建过程中使用 SSH 密钥:

# syntax=docker/dockerfile:1

FROM alpine
RUN apk add --no-cache openssh-client git

# 使用 SSH 访问私有仓库
RUN --mount=type=ssh \
ssh-keyscan github.com >> ~/.ssh/known_hosts && \
git clone [email protected]:private/repo.git

构建时传递 SSH 代理:

eval $(ssh-agent)
ssh-add ~/.ssh/id_rsa
docker buildx build --ssh default=$SSH_AUTH_SOCK .

RUN --network 网络控制

RUN 指令支持 --network 选项,控制命令执行时的网络环境:

# syntax=docker/dockerfile:1

FROM alpine

# 默认网络(与宿主机网络隔离)
RUN --network=default apk add curl

# 无网络访问(完全隔离)
RUN --network=none pip install --no-deps package

# 使用宿主机网络
RUN --network=host curl http://localhost:8080/api

使用场景

模式适用场景
default默认行为,适合大多数情况
none离线构建、确保构建可重复性、安全隔离
host访问本地服务、网络调试

离线构建示例

# syntax=docker/dockerfile:1

FROM python:3.11

# 先在有网络时下载依赖
COPY requirements.txt .
RUN pip download -r requirements.txt -d /tmp/packages

# 然后在无网络模式下安装(确保使用本地包)
RUN --network=none \
pip install --no-index --find-links=/tmp/packages -r requirements.txt

Here-Documents(多行文本)

BuildKit 支持在 Dockerfile 中使用 here-documents,这是一种将多行文本传递给命令的方式。这个特性让复杂的多行脚本更加清晰易读。

基本语法

# syntax=docker/dockerfile:1

FROM alpine

# 使用 here-document 编写多行脚本
RUN <<EOF
echo "Hello from Docker"
echo "This is a multi-line script"
apk add --no-cache curl
EOF

带缩进的 here-document

# syntax=docker/dockerfile:1

FROM python:3.11-slim

WORKDIR /app

# 使用 <<- 可以保留缩进(忽略前导制表符)
RUN <<-EOF
pip install --no-cache-dir \
flask \
gunicorn \
requests
echo "Dependencies installed"
EOF

传递脚本文件

# syntax=docker/dockerfile:1

FROM alpine

# 创建并执行复杂脚本
RUN <<SETUP
#!/bin/sh
set -e

# 安装依赖
apk add --no-cache \
curl \
git \
bash

# 创建目录结构
mkdir -p /app/{bin,config,logs}

# 设置权限
chmod 755 /app/bin

echo "Setup completed"
SETUP

here-document 与变量

# syntax=docker/dockerfile:1

FROM alpine

ARG APP_NAME=myapp
ARG APP_VERSION=1.0

# 变量会在 here-document 中展开
RUN <<EOF
echo "Building ${APP_NAME} version ${APP_VERSION}"
mkdir -p /app/${APP_NAME}
echo "Version: ${APP_VERSION}" > /app/${APP_NAME}/version.txt
EOF

多个 here-document

# syntax=docker/dockerfile:1

FROM alpine

# 一个 RUN 指令中使用多个 here-document
RUN <<EOF
echo "First script"
EOF
RUN <<EOF
echo "Second script"
EOF

适用场景

场景说明
复杂安装脚本多行命令更易读和维护
配置文件生成直接在 Dockerfile 中生成配置
条件逻辑包含 if/else 等控制结构
减少层数将多个命令合并为一个 RUN

CMD - 容器启动命令

指定容器启动时默认执行的命令:

# exec 格式(推荐)
CMD ["nginx", "-g", "daemon off;"]

# shell 格式
CMD nginx -g "daemon off;"

# 作为 ENTRYPOINT 的默认参数
CMD ["--port", "3000"]

注意

  • 一个 Dockerfile 中只能有一个 CMD
  • 如果运行容器时指定了命令,CMD 会被覆盖
  • exec 格式会被解析为 JSON 数组,必须使用双引号

ENTRYPOINT - 入口点

配置容器为可执行程序:

# exec 格式
ENTRYPOINT ["docker-entrypoint.sh"]

# 结合 CMD 使用
ENTRYPOINT ["node"]
CMD ["app.js"]

# 运行时可以覆盖 CMD
# docker run my-image server.js

ENTRYPOINT vs CMD

特性ENTRYPOINTCMD
是否可被覆盖需要 --entrypoint直接覆盖
用途固定命令默认参数
组合使用主命令默认参数
# 组合使用示例
ENTRYPOINT ["node"]
CMD ["app.js"]

# 运行 node app.js
docker run my-image

# 运行 node server.js
docker run my-image server.js

端口和卷

EXPOSE - 声明端口

声明容器运行时监听的端口:

# 声明单个端口
EXPOSE 80

# 声明多个端口
EXPOSE 80 443

# 声明 UDP 端口
EXPOSE 53/udp

注意

  • EXPOSE 只是声明,不会实际发布端口
  • 实际发布端口需要使用 -p-P 选项
  • 主要用于文档说明和自动端口映射

VOLUME - 声明数据卷

声明容器应该使用的持久化存储:

# 声明单个卷
VOLUME /data

# 声明多个卷
VOLUME ["/data", "/logs"]

使用场景

  • 数据库数据目录
  • 日志目录
  • 用户上传文件目录
# MySQL 示例
VOLUME /var/lib/mysql

# Redis 示例
VOLUME /data

健康检查

HEALTHCHECK - 健康检查

配置容器的健康检查命令:

# 基本语法
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1

# 参数说明
# --interval: 检查间隔(默认 30s)
# --timeout: 超时时间(默认 30s)
# --start-period: 启动等待时间(默认 0s)
# --retries: 重试次数(默认 3)

健康状态

  • starting:启动中
  • healthy:健康
  • unhealthy:不健康
# Web 应用健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:3000/health || exit 1

# 数据库健康检查
HEALTHCHECK --interval=10s --timeout=3s \
CMD pg_isready -U postgres || exit 1

ONBUILD - 触发器指令

ONBUILD 指令创建一个触发器,当镜像作为其他镜像的基础镜像时执行。这对于构建可重用的基础镜像非常有用。

# 基础镜像 Dockerfile
FROM node:18-alpine
WORKDIR /app
# 当其他镜像基于此镜像构建时,自动执行 COPY 和 RUN
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
CMD ["npm", "start"]

使用这个基础镜像时:

# 应用镜像 Dockerfile
FROM my-node-base
# 自动执行:COPY package*.json ./
# 自动执行:RUN npm install
# 自动执行:COPY . .

执行时机

ONBUILD 指令不会在当前构建中执行,而是在子镜像构建时触发:

基础镜像构建:记录 ONBUILD 指令,不执行

子镜像构建:FROM 基础镜像时,执行记录的 ONBUILD 指令

生成的镜像:不包含 ONBUILD 指令(不会传递到孙镜像)

注意事项

  • ONBUILD 不会递归触发(子镜像的 ONBUILD 不会传递给孙镜像)
  • ONBUILD 不支持链接其他 ONBUILD 指令
  • ONBUILD 不会触发 FROM、MAINTAINER 指令

典型应用场景

# 创建语言运行时基础镜像
FROM python:3.11-slim

WORKDIR /app

# 子镜像构建时自动安装依赖
ONBUILD COPY requirements.txt .
ONBUILD RUN pip install --no-cache-dir -r requirements.txt
ONBUILD COPY . .

# 子镜像只需指定 CMD

SHELL - 设置默认 Shell

SHELL 指令设置后续指令使用的默认 Shell。在 Windows 上特别有用,因为 Windows 默认使用 cmd.exe。

# Windows 上切换到 PowerShell
FROM microsoft/nanoserver
SHELL ["powershell", "-Command"]

# 后续 RUN 指令使用 PowerShell
RUN Write-Host "Hello from PowerShell"
RUN Get-Process

# 切换回 cmd
SHELL ["cmd", "/S", "/C"]
RUN echo "Hello from CMD"

Linux 上的使用

FROM alpine
# 默认使用 /bin/sh -c
RUN echo $HOME

# 切换到 bash
SHELL ["/bin/bash", "-c"]
RUN echo $HOME

# 使用 zsh
SHELL ["/bin/zsh", "-c"]
RUN echo $HOME

SHELL 指令影响范围

SHELL 指令影响后续的 RUN、CMD、ENTRYPOINT 指令的 shell 格式执行方式。

# 默认 shell: /bin/sh -c
RUN echo "default shell"

SHELL ["/bin/bash", "-c"]

# 现在 shell: /bin/bash -c
RUN echo "bash shell"

# CMD 和 ENTRYPOINT 的 shell 格式也受影响
CMD echo "using bash"

STOPSIGNAL - 停止信号

STOPSIGNAL 指令设置发送给容器以使其退出的系统调用信号。这影响 docker stop 命令的行为。

# 使用 SIGTERM(默认)
STOPSIGNAL SIGTERM

# 使用 SIGQUIT
STOPSIGNAL SIGQUIT

# 使用信号编号
STOPSIGNAL 15

信号说明

信号编号说明
SIGTERM15优雅终止(默认)
SIGKILL9强制终止
SIGQUIT3退出并生成 core dump
SIGINT2中断(Ctrl+C)
SIGHUP1挂起/重载配置

应用场景

# Nginx 需要使用 SIGQUIT 优雅关闭
STOPSIGNAL SIGQUIT

# 应用需要自定义信号处理
STOPSIGNAL SIGTERM

# Java 应用可能需要 SIGINT
STOPSIGNAL SIGINT

当执行 docker stop 时,Docker 会发送指定的信号,等待容器优雅退出(默认 10 秒超时),超时后发送 SIGKILL 强制终止。

MAINTAINER - 维护者信息(已弃用)

MAINTAINER 指令用于设置镜像作者信息,但已被弃用,推荐使用 LABEL 代替:

# 已弃用
MAINTAINER John Doe <[email protected]>

# 推荐使用 LABEL
LABEL maintainer="John Doe <[email protected]>"

MAINTAINER 指令仅为向后兼容而保留,新项目应使用 LABEL 指令。

多阶段构建

多阶段构建可以显著减小最终镜像大小:

# 构建阶段
FROM node:18 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 运行阶段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

优点

  • 最终镜像不包含构建工具
  • 减小镜像体积
  • 提高安全性
# Go 应用示例
FROM golang:1.21 AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

FROM alpine:3.18
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]

实战示例

Node.js 应用

# 使用官方 Node.js 镜像
FROM node:18-alpine AS builder

# 设置工作目录
WORKDIR /app

# 复制依赖文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建应用
RUN npm run build

# 生产阶段
FROM node:18-alpine

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
adduser -S nextjs -u 1001

WORKDIR /app

# 复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./

# 切换用户
USER nextjs

# 环境变量
ENV NODE_ENV=production

# 暴露端口
EXPOSE 3000

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

# 启动命令
CMD ["node", "dist/main.js"]

Python 应用

FROM python:3.11-slim

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1

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 . .

# 创建非 root 用户
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=3s \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"

CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

Java 应用

# 构建阶段
FROM maven:3.9-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar

# 非 root 用户
RUN addgroup -S java && adduser -S javauser -G java
USER javauser

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar"]

最佳实践

1. 使用 .dockerignore

创建 .dockerignore 文件排除不需要的文件:

.git
.gitignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.env
*.md
tests/

2. 减少镜像层数

# 不好:多个层
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim

# 好:合并为一个层
RUN apt-get update && apt-get install -y \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*

3. 合理使用缓存

将不常变化的指令放在前面:

# 先复制依赖文件
COPY package*.json ./
RUN npm install

# 再复制源代码(经常变化)
COPY . .

4. 使用特定版本标签

# 不好:使用 latest
FROM node:latest

# 好:指定版本
FROM node:18.19.0-alpine

5. 最小化镜像大小

# 使用 Alpine 镜像
FROM node:18-alpine

# 清理缓存
RUN npm install && npm cache clean --force

# 使用多阶段构建
# ...(见上文示例)

小结

Dockerfile 编写的核心知识:

  • 基本结构:以 FROM 开头,指令按顺序执行,每条指令创建一个层
  • 解析器指令# syntax= 指定语法版本、# escape= 设置转义字符、# check= 配置检查
  • 常用指令FROM 基础镜像、RUN 执行命令、CMD/ENTRYPOINT 启动命令、COPY/ADD 文件操作
  • 环境配置ENV 环境变量、ARG 构建参数、WORKDIR 工作目录、USER 运行用户
  • 端口和卷EXPOSE 声明端口、VOLUME 声明数据卷
  • 健康检查HEALTHCHECK 配置容器健康检测
  • 高级指令ONBUILD 触发器、SHELL 默认 shell、STOPSIGNAL 停止信号
  • RUN 高级选项--mount 挂载文件系统、--network 网络控制
  • 多阶段构建:使用 AS 命名阶段,COPY --from= 复制产物,显著减小镜像体积

练习

  1. 为一个简单的 Node.js 应用编写 Dockerfile
  2. 使用多阶段构建优化镜像大小
  3. 添加健康检查指令
  4. 配置非 root 用户运行容器
  5. 对比优化前后的镜像大小差异