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 的区别:
| 特性 | ARG | ENV |
|---|---|---|
| 作用范围 | 构建阶段 | 构建和运行阶段 |
| 可被覆盖 | 构建时 --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:
| 特性 | COPY | ADD |
|---|---|---|
| 复制本地文件 | 支持 | 支持 |
| 下载远程文件 | 不支持 | 支持 |
| 自动解压 | 不支持 | 支持 |
| 推荐使用 | 是 | 特定场景 |
优先使用 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
最佳实践:
- 合并多个 RUN 命令,减少镜像层数
- 在同一层清理缓存文件
- 使用
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 | 共享模式:shared、private、locked |
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 挂载选项:
| 选项 | 说明 |
|---|---|
id | secret 标识符,默认为 target 路径的文件名 |
target | 挂载路径,默认为 /run/secrets/ + id |
env | 挂载为环境变量(Dockerfile v1.10.0+) |
required | secret 不可用时是否报错,默认 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:
| 特性 | ENTRYPOINT | CMD |
|---|---|---|
| 是否可被覆盖 | 需要 --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
信号说明:
| 信号 | 编号 | 说明 |
|---|---|---|
| SIGTERM | 15 | 优雅终止(默认) |
| SIGKILL | 9 | 强制终止 |
| SIGQUIT | 3 | 退出并生成 core dump |
| SIGINT | 2 | 中断(Ctrl+C) |
| SIGHUP | 1 | 挂起/重载配置 |
应用场景:
# 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=复制产物,显著减小镜像体积
练习
- 为一个简单的 Node.js 应用编写 Dockerfile
- 使用多阶段构建优化镜像大小
- 添加健康检查指令
- 配置非 root 用户运行容器
- 对比优化前后的镜像大小差异