5942 字
30 分钟
Dockerfile 编写语法讲解与实践

在云原生时代,Docker已经成为容器化技术的事实标准。而Dockerfile作为构建Docker镜像的”蓝图”,是每个开发者和运维工程师必须掌握的核心技能。一个编写良好的Dockerfile不仅能让镜像更小、构建更快,还能提高应用的安全性和可维护性。本文将从基础语法到高级技巧,结合实战案例,全面讲解Dockerfile的编写方法与最佳实践。

一、Dockerfile 基础概念#

1.1 什么是 Dockerfile#

Dockerfile是一个文本文件,包含了一系列指令(Instruction),每条指令构建镜像的一层。Docker通过读取Dockerfile中的指令自动构建镜像。简单来说,Dockerfile就是你告诉Docker如何一步步构建你的应用镜像的说明书。

1.2 Dockerfile 工作原理#

Docker采用分层存储的架构,镜像由多个只读层叠加而成。每条Dockerfile指令都会创建一个新的镜像层:

  1. Docker从基础镜像(Base Image)开始
  2. 逐条执行Dockerfile中的指令
  3. 每条指令执行后,提交一个新的镜像层
  4. 所有指令执行完毕,生成最终的镜像
分层架构的优势

这种分层架构带来了巨大的优势:

  • 缓存复用:如果某一层没有变化,Docker会直接使用缓存,大大加快构建速度
  • 空间节省:多个镜像可以共享相同的基础层,节省存储空间
  • 版本控制:每一层都可以看作一个版本,便于回滚和调试

1.3 基本结构#

一个典型的Dockerfile通常包含以下几个部分:

Dockerfile结构示例
# 基础镜像声明
FROM node:20-alpine
# 维护者信息
LABEL maintainer="dev@example.com"
# 环境变量设置
ENV NODE_ENV=production APP_PORT=3000
# 工作目录设置
WORKDIR /app
# 文件复制
COPY package*.json ./
# 依赖安装
RUN npm ci --only=production
# 源代码复制
COPY . .
# 端口暴露
EXPOSE 3000
# 容器启动命令
CMD ["node", "app.js"]

二、核心指令详解#

2.1 FROM:指定基础镜像#

FROM是Dockerfile中必须的第一条指令,用于指定构建过程中使用的基础镜像。

FROM指令语法
# 基本格式
FROM <image>[:<tag>]
# 带平台指定
FROM --platform=<platform> <image>[:<tag>]
# 多阶段构建命名
FROM <image>[:<tag>] AS <name>

常见示例:

Dockerfile
# 使用官方Node.js 20 LTS镜像作为基础
FROM node:20-alpine
# 多阶段构建中命名阶段
FROM node:20-alpine AS builder
# 跨平台构建
FROM --platform=linux/amd64 nginx:alpine
镜像选择最佳实践
  • 优先使用官方镜像,安全性和可靠性更高
  • 尽量使用轻量级镜像(如alpine版本),减小最终镜像体积
  • 明确指定镜像标签,不要使用默认的latest标签,避免不可预期的变化
  • 使用固定版本号而非滚动标签,确保构建的可重现性

2.2 LABEL:添加元数据#

LABEL指令用于为镜像添加元数据,如维护者信息、版本号、描述等。

Dockerfile - 标签定义
# 单行多个标签
LABEL maintainer="your-email@example.com" \
version="1.0.0" \
description="This is a sample Node.js application" \
org.opencontainers.image.source="https://github.com/user/repo"
# 使用反向DNS命名避免冲突
LABEL com.example.project="myapp" \
com.example.team="backend" \
com.example.build-date="2024-11-29"
标签命名规范

推荐使用OCI(Open Container Initiative)标准的镜像标签:

  • org.opencontainers.image.created - 创建时间
  • org.opencontainers.image.authors - 作者信息
  • org.opencontainers.image.url - 项目URL
  • org.opencontainers.image.documentation - 文档链接
  • org.opencontainers.image.source - 源代码仓库
  • org.opencontainers.image.version - 版本号
  • org.opencontainers.image.licenses - 许可证

2.3 ARG:构建参数#

ARG指令用于定义构建时的变量,可以在docker build时通过--build-arg参数传入。

Dockerfile - 构建参数
# 定义构建参数
ARG NODE_VERSION=20
ARG ALPINE_VERSION=3.19
# 在FROM指令中使用
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}
ARG BUILD_ENV=production
ARG BUILD_DATE
ARG GIT_COMMIT
# 转换为环境变量
ENV BUILD_ENV=${BUILD_ENV} \
BUILD_DATE=${BUILD_DATE} \
GIT_COMMIT=${GIT_COMMIT}
RUN echo "Building for ${BUILD_ENV} environment"

构建时传递参数:

构建命令
docker build \
--build-arg NODE_VERSION=18 \
--build-arg BUILD_ENV=staging \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg GIT_COMMIT=$(git rev-parse HEAD) \
-t myapp:latest .
ARG与ENV的区别
  • ARG只在构建期间可用,容器运行时不存在
  • ENV在构建和运行时都存在
  • ARG可以通过--build-arg覆盖,ENV需要在运行时通过-e覆盖
  • 敏感信息不要使用ARG,因为它会被保存在镜像历史记录中

2.4 ENV:设置环境变量#

ENV指令用于设置环境变量,这些变量在后续的指令和容器运行时都可以使用。

Dockerfile - 环境变量
# 单个变量(传统格式)
ENV NODE_ENV production
# 多个变量(推荐格式)
ENV APP_PORT=3000 \
APP_HOME=/app \
DB_HOST=localhost \
DB_PORT=5432 \
LOG_LEVEL=info
# 引用其他环境变量
ENV PATH="/app/bin:${PATH}"
环境变量安全提示
  • 环境变量会一直存在于镜像中,可能带来安全风险
  • 敏感信息(密码、密钥)不要直接写在Dockerfile中
  • 使用Docker Secrets或外部配置管理工具处理敏感信息
  • 可以在运行容器时使用-e参数覆盖环境变量

2.5 WORKDIR:设置工作目录#

WORKDIR指令用于设置工作目录,相当于cd命令,但比cd更可靠。

Dockerfile - 工作目录
# 设置绝对路径工作目录
WORKDIR /app
# 复制文件到当前工作目录
COPY package.json .
# 使用相对路径(相对于上一个WORKDIR)
WORKDIR src
# 现在工作目录是/app/src
COPY . .
# 再次切换到父目录
WORKDIR ..
# 现在工作目录回到/app
WORKDIR最佳实践
  • 总是使用WORKDIR而不是RUN cd ...来切换目录
  • 优先使用绝对路径,避免路径混乱
  • 可以多次使用WORKDIR,如果目录不存在会自动创建
  • 使用WORKDIR提高Dockerfile可读性和可维护性

2.6 COPY:复制文件#

COPY指令用于将文件或目录从构建上下文(Context)复制到镜像中。

Dockerfile - 文件复制
# 复制单个文件
COPY package.json .
# 复制多个文件
COPY package.json package-lock.json ./
# 复制整个目录
COPY src ./src
# 复制并设置文件所有者
COPY --chown=node:node . .
# 复制时排除特定文件(通过.dockerignore)
COPY . .

.dockerignore文件示例:

.dockerignore
# 版本控制
.git
.gitignore
.github
# 依赖目录
node_modules
bower_components
# 构建输出
dist
build
*.log
# IDE配置
.vscode
.idea
*.swp
# 测试文件
test
tests
**/*.test.js
coverage
# 文档和说明
README.md
CHANGELOG.md
docs
# 环境配置
.env
.env.local
docker-compose.yml
Dockerfile
COPY指令注意事项
  • src路径必须是构建上下文内的路径,不能使用绝对路径或..访问上级目录
  • 如果src是目录,只会复制目录的内容,而不是目录本身
  • 使用.dockerignore文件排除不需要复制的文件,减小镜像体积和加快构建速度
  • 复制大量小文件会创建多个层,考虑先打包再复制

2.7 ADD:高级文件复制#

ADD指令与COPY类似,但具有一些额外功能:

Dockerfile - ADD指令
# 复制并自动解压tar文件
ADD app.tar.gz /app
# 复制远程文件(不推荐)
ADD https://example.com/file.txt /app/
# 普通文件复制(推荐用COPY)
ADD config.json /app/
ADD与COPY的选择

推荐规则:

  • 优先使用COPY,因为它更透明和可预测
  • 只有在需要自动解压tar文件时才使用ADD
  • 不要使用ADD下载远程文件,应该使用RUN wget或RUN curl

为什么不用ADD下载远程文件:

不推荐做法
ADD https://example.com/large-file.zip /app/
# 问题:文件会永久保存在镜像层中
推荐做法
RUN wget -O /tmp/file.zip https://example.com/large-file.zip && \
unzip /tmp/file.zip -d /app && \
rm /tmp/file.zip
# 优势:下载、解压、删除在同一层,不增加镜像体积

2.8 RUN:执行命令#

RUN指令用于在镜像构建过程中执行命令,是最常用的指令之一。

Dockerfile - RUN指令优化
# shell形式(推荐用于复杂命令)
RUN apt-get update && apt-get install -y \
git \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# exec形式(推荐用于简单命令)
RUN ["npm", "install", "--production"]
RUN ["/bin/bash", "-c", "echo hello"]
# 多行命令优化示例
RUN set -ex && \
apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
wget && \
wget -O /usr/local/bin/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.5/dumb-init_1.2.5_amd64 && \
chmod +x /usr/local/bin/dumb-init && \
apt-get purge -y --auto-remove wget && \
rm -rf /var/lib/apt/lists/*
RUN指令优化技巧
  1. 合并相关命令:将多个相关命令合并为一个RUN指令,减少镜像层数
  2. 使用&&连接:确保前一个命令成功后才执行下一个
  3. 立即清理缓存:在安装完依赖后立即清理包管理器缓存
  4. 使用反斜杠换行:对于长命令,提高可读性
  5. 使用set -ex:-e遇错即停,-x打印执行的命令
  6. 安装后卸载构建工具:减小最终镜像体积

2.9 VOLUME:定义挂载点#

VOLUME指令用于创建挂载点,用于持久化数据或共享数据。

Dockerfile - 数据卷
# 定义单个挂载点
VOLUME /data
# 定义多个挂载点
VOLUME ["/var/log", "/var/db"]
# 使用JSON数组格式
VOLUME ["/app/uploads", "/app/cache"]

使用示例:

运行容器时挂载
# 使用匿名卷
docker run -v /data myapp
# 使用命名卷
docker run -v mydata:/data myapp
# 挂载主机目录
docker run -v /host/path:/data myapp
VOLUME使用场景
  • 数据库数据存储(MySQL、PostgreSQL、MongoDB等)
  • 应用日志目录
  • 用户上传的文件
  • 临时缓存数据
  • 需要在容器间共享的数据

2.10 EXPOSE:暴露端口#

EXPOSE指令用于声明容器运行时监听的端口。

Dockerfile - 端口暴露
# 暴露TCP端口(默认)
EXPOSE 3000
# 暴露UDP端口
EXPOSE 53/udp
# 同时暴露TCP和UDP端口
EXPOSE 80/tcp 80/udp
# 暴露多个端口
EXPOSE 8080 8443 9090
端口暴露说明
  • EXPOSE只是声明端口,并不会实际发布端口
  • 运行容器时需要使用-p参数来发布端口:
    Terminal window
    docker run -p 3000:3000 myapp
  • 这是一个文档化的指令,帮助使用者了解容器需要暴露哪些端口
  • 使用-P参数可以自动映射所有EXPOSE声明的端口

2.11 CMD:容器启动命令#

CMD指令用于指定容器启动时默认执行的命令。

Dockerfile - CMD指令
# exec形式(推荐)
CMD ["npm", "start"]
# shell形式
CMD npm start
# 作为ENTRYPOINT的默认参数
CMD ["--port", "3000"]
CMD注意事项
  • 一个Dockerfile中只能有一个CMD指令,如果有多个,只有最后一个生效
  • CMD会被docker run命令后面的参数完全覆盖
  • 推荐使用exec形式,因为它不会创建额外的shell进程
  • exec形式不会进行shell变量替换,如需使用变量需用shell形式

2.12 ENTRYPOINT:容器入口点#

ENTRYPOINT指令用于配置容器启动时执行的可执行文件。

Dockerfile - ENTRYPOINT指令
# exec形式(推荐)
ENTRYPOINT ["node", "app.js"]
# shell形式
ENTRYPOINT node app.js
# 与CMD组合使用
ENTRYPOINT ["node"]
CMD ["app.js"]

ENTRYPOINT与CMD的组合使用:

Dockerfile - 组合使用示例
# 定义入口点和默认参数
ENTRYPOINT ["node"]
CMD ["app.js"]
# 运行时可以覆盖CMD
# docker run myapp server.js -> 实际执行:node server.js
# docker run myapp -> 实际执行:node app.js
ENTRYPOINT实际应用场景

场景1:创建可执行容器

Dockerfile
FROM alpine
RUN apk add --no-cache curl
ENTRYPOINT ["curl"]
CMD ["--help"]

使用:docker run mycurl https://example.com

场景2:初始化脚本

Dockerfile
FROM node:20-alpine
COPY entrypoint.sh /
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "app.js"]
entrypoint.sh
#!/bin/sh
# 初始化数据库
npm run migrate
# 执行CMD传入的命令
exec "$@"

三、高级技巧与最佳实践#

3.1 多阶段构建#

多阶段构建是Docker 17.05引入的特性,允许你在一个Dockerfile中使用多个FROM指令,每个FROM指令开始一个新的构建阶段。

Node.js应用多阶段构建示例:

Dockerfile - 多阶段构建
# ============================================
# 阶段1:依赖安装
# ============================================
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ============================================
# 阶段2:构建阶段
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# 阶段3:最终运行镜像
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
# 从deps阶段复制生产依赖
COPY --from=deps /app/node_modules ./node_modules
# 从builder阶段复制构建产物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
USER nextjs
EXPOSE 3000
CMD ["node", "dist/main.js"]

Go应用极限优化示例:

Dockerfile - Go多阶段构建
# ============================================
# 构建阶段
# ============================================
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo \
-ldflags="-s -w" -o main .
# ============================================
# 最终阶段:使用scratch镜像
# ============================================
FROM scratch
# 从builder阶段复制二进制文件
COPY --from=builder /app/main /main
# 复制ca证书(用于HTTPS请求)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
USER 1000:1000
ENTRYPOINT ["/main"]
多阶段构建优势
  • 大幅减小镜像体积:最终镜像只包含运行时需要的文件
  • 提高安全性:不包含构建工具和源代码,减少攻击面
  • 加快部署速度:更小的镜像意味着更快的下载和启动
  • 简化构建流程:在一个Dockerfile中完成构建和打包

3.2 优化镜像体积#

镜像体积直接影响部署效率和安全性,以下是经过实战验证的优化技巧:

对比示例:

优化前.Dockerfile
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y nodejs npm
COPY . .
RUN npm install
CMD ["node", "app.js"]
# 镜像大小:约600MB
优化后.Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 nodejs
USER nodejs
EXPOSE 3000
CMD ["node", "app.js"]
# 镜像大小:约150MB,减少75%

Alpine系统优化技巧:

Dockerfile - Alpine优化
FROM alpine:3.19
# 合并安装命令,使用虚拟包管理构建依赖
RUN apk add --no-cache --virtual .build-deps \
gcc musl-dev python3-dev && \
apk add --no-cache python3 py3-pip && \
apk del .build-deps
# 清理包管理器缓存
RUN rm -rf /var/cache/apk/*
镜像体积优化清单
  1. 选择最小基础镜像:alpine < slim < 标准版
  2. 合并RUN指令:减少镜像层数
  3. 及时清理缓存:
    • apt: rm -rf /var/lib/apt/lists/*
    • apk: rm -rf /var/cache/apk/*
    • npm: npm cache clean --force
    • yarn: yarn cache clean
  4. 使用.dockerignore:排除不需要的文件
  5. 多阶段构建:只保留运行时文件
  6. 删除构建工具:安装后卸载编译器等
  7. 压缩二进制文件:使用UPX等工具

3.3 利用构建缓存#

Docker的构建缓存是提升构建速度的关键,理解缓存机制能显著优化开发体验。

缓存失效示例:

错误示例.Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
RUN npm install
# 问题:每次源代码变化都会导致npm install重新执行
正确示例.Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# 优势:只有依赖变化时才重新安装

进阶缓存优化:

Dockerfile - 缓存优化
FROM node:20-alpine
# 第一层:复制依赖定义文件
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 缓存点1:依赖安装层
# 第二层:复制配置文件
COPY tsconfig.json ./
COPY .eslintrc.json ./
# 缓存点2:配置文件层
# 第三层:复制源代码
COPY src ./src
# 缓存点3:源代码层
RUN npm run build
缓存最佳实践
  1. 按变化频率排序指令:

    • 基础镜像选择(几乎不变)
    • 系统依赖安装(很少变化)
    • 应用依赖安装(偶尔变化)
    • 配置文件复制(较少变化)
    • 源代码复制(经常变化)
  2. 使用BuildKit缓存挂载:

    Dockerfile - BuildKit缓存
    # syntax=docker/dockerfile:1
    FROM node:20-alpine
    WORKDIR /app
    RUN --mount=type=cache,target=/root/.npm \
    npm install -g pnpm
    COPY package*.json ./
    RUN --mount=type=cache,target=/root/.pnpm-store \
    pnpm install --frozen-lockfile
  3. 查看缓存使用情况:

    Terminal window
    # 使用--progress=plain查看详细构建过程
    docker build --progress=plain .
    # 强制不使用缓存
    docker build --no-cache .
    # 从指定阶段开始不使用缓存
    docker build --target=builder --no-cache .

3.4 安全最佳实践#

容器安全是生产环境部署的重中之重,以下是经过生产验证的安全实践。

创建非root用户:

Dockerfile - 安全配置
FROM node:20-alpine
# 方案1:使用镜像自带的非root用户
RUN mkdir -p /app && chown -R node:node /app
WORKDIR /app
USER node
# 方案2:创建自定义用户
RUN addgroup -g 1001 -S appgroup && \
adduser -S -u 1001 -G appgroup appuser
WORKDIR /app
RUN chown -R appuser:appgroup /app
USER appuser
# 方案3:使用数字UID(推荐用于Kubernetes)
RUN addgroup -g 1000 appgroup && \
adduser -u 1000 -G appgroup -s /bin/sh -D appuser
USER 1000:1000

敏感信息处理:

Dockerfile - 敏感信息处理
# ❌ 错误:直接写入敏感信息
ENV DATABASE_PASSWORD=supersecret
# ✅ 正确:使用ARG但不转ENV
ARG GITHUB_TOKEN
RUN git clone https://${GITHUB_TOKEN}@github.com/user/repo.git && \
rm -rf .git
# ARG不会保留在最终镜像中
# ✅ 正确:使用BuildKit secrets
# docker build --secret id=npmrc,src=$HOME/.npmrc .
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
npm install private-package

安全扫描集成:

Dockerfile - 漏洞扫描
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# 使用npm audit检查依赖漏洞
RUN npm audit --audit-level=moderate || \
(npm audit fix && npm audit --audit-level=moderate)
COPY . .
USER node
CMD ["node", "app.js"]

完整安全示例:

Dockerfile - 生产级安全配置
# 使用特定版本镜像
FROM node:20.11.0-alpine3.19
# 安装安全更新
RUN apk update && \
apk upgrade --no-cache
WORKDIR /app
# 最小权限原则
RUN addgroup -g 1001 -S nodejs && \
adduser -S -u 1001 nextjs -G nodejs
COPY --chown=nextjs:nodejs package*.json ./
# 安装时检查漏洞
RUN npm ci --only=production && \
npm audit --audit-level=high
COPY --chown=nextjs:nodejs . .
# 删除不必要的文件
RUN rm -rf .git .github docs tests *.md
# 设置只读权限
RUN chmod -R 555 /app && \
chmod -R 777 /app/tmp
# 切换到非root用户
USER nextjs
EXPOSE 3000
# 使用dumb-init处理信号
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["node", "server.js"]
安全检查清单
  • 使用官方或可信镜像
  • 固定镜像版本标签
  • 使用非root用户运行
  • 不在镜像中存储敏感信息
  • 定期更新基础镜像
  • 扫描镜像漏洞(Trivy、Snyk)
  • 最小化镜像内容
  • 设置只读文件系统
  • 使用健康检查
  • 限制容器资源

3.5 调试与故障排查#

开发过程中难免遇到构建失败或运行问题,掌握调试技巧能大大提高效率。

构建过程调试:

调试命令
# 详细输出构建过程
docker build --progress=plain --no-cache .
# 查看每一层的大小
docker history myapp:latest
# 在特定阶段停止构建
docker build --target=builder -t myapp:debug .
# 进入调试阶段
docker run -it myapp:debug sh
# 查看构建缓存使用情况
docker builder inspect --bootstrap
# 清理构建缓存
docker builder prune -a

运行时调试:

运行时调试命令
# 覆盖CMD进入容器
docker run -it --entrypoint sh myapp:latest
# 查看容器日志
docker logs -f container_name
# 进入运行中的容器
docker exec -it container_name sh
# 查看容器进程
docker top container_name
# 查看容器资源使用
docker stats container_name
# 检查容器配置
docker inspect container_name
# 导出容器文件系统
docker export container_name > container.tar

常见问题排查:

Dockerfile - 添加调试工具
FROM node:20-alpine
# 开发环境添加调试工具
RUN if [ "$NODE_ENV" = "development" ]; then \
apk add --no-cache \
curl wget vim strace \
&& npm install -g nodemon; \
fi
WORKDIR /app
COPY . .
# 开发环境使用nodemon热重载
CMD if [ "$NODE_ENV" = "development" ]; then \
nodemon app.js; \
else \
node app.js; \
fi
调试技巧总结
  1. 逐层构建测试:

    Dockerfile
    FROM node:20-alpine AS deps
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    # 先测试这一阶段:docker build --target=deps .
    FROM deps AS builder
    COPY . .
    RUN npm run build
    # 再测试这一阶段:docker build --target=builder .
  2. 使用RUN echo输出调试信息:

    Dockerfile
    RUN echo "DEBUG: Current directory: $(pwd)" && \
    echo "DEBUG: Files: $(ls -la)" && \
    echo "DEBUG: Environment: $(env)"
  3. 保存中间镜像:

    Terminal window
    # 在失败的层之前提交镜像
    docker commit container_id debug:latest
    docker run -it debug:latest sh

3.6 CI/CD集成实践#

将Dockerfile集成到CI/CD流程是现代DevOps的标准实践。

GitLab CI配置:

.gitlab-ci.yml
variables:
DOCKER_DRIVER: overlay2
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
stages:
- build
- test
- deploy
build:
stage: build
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
--build-arg VCS_REF=$CI_COMMIT_SHA
--cache-from $CI_REGISTRY_IMAGE:latest
-t $IMAGE_TAG
-t $CI_REGISTRY_IMAGE:latest
.
- docker push $IMAGE_TAG
- docker push $CI_REGISTRY_IMAGE:latest
test:
stage: test
image: $IMAGE_TAG
script:
- npm test
security_scan:
stage: test
image: aquasec/trivy:latest
script:
- trivy image --severity HIGH,CRITICAL $IMAGE_TAG

GitHub Actions配置:

.github/workflows/docker.yml
name: Docker Build and Push
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=sha,prefix={{branch}}-
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
build-args: |
BUILD_DATE=${{ github.event.head_commit.timestamp }}
VCS_REF=${{ github.sha }}

四、实战案例#

4.1 Node.js 全栈应用#

生产级Node.js应用Dockerfile,包含所有最佳实践:

Dockerfile
# syntax=docker/dockerfile:1
# ============================================
# 阶段1:依赖安装
# ============================================
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# 利用pnpm提升安装速度
RUN npm install -g pnpm
COPY package.json pnpm-lock.yaml* ./
RUN --mount=type=cache,target=/root/.pnpm-store \
pnpm install --frozen-lockfile --prod
# ============================================
# 阶段2:构建阶段
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# 安装开发依赖并构建
RUN npm install -g pnpm && \
pnpm install --frozen-lockfile && \
pnpm build && \
pnpm prune --prod
# ============================================
# 阶段3:最终运行镜像
# ============================================
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production \
PORT=3000 \
HOSTNAME="0.0.0.0"
# 创建非root用户
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs && \
mkdir -p /app/.next && \
chown -R nextjs:nodejs /app
# 从构建阶段复制文件
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
USER nextjs
EXPOSE 3000
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
CMD ["node", "dist/main.js"]

4.2 Python FastAPI应用#

Dockerfile
# ============================================
# 多阶段构建:Python FastAPI应用
# ============================================
FROM python:3.11-slim as builder
WORKDIR /app
# 安装构建依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
# 安装Python依赖到自定义目录
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# ============================================
# 最终运行镜像
# ============================================
FROM python:3.11-slim
WORKDIR /app
# 设置Python环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH=/root/.local/bin:$PATH
# 从builder复制已安装的包
COPY --from=builder /root/.local /root/.local
# 创建非root用户
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
# 复制应用代码
COPY --chown=appuser:appuser . .
USER appuser
EXPOSE 8000
# 使用uvicorn运行
HEALTHCHECK CMD curl --fail http://localhost:8000/health || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

4.3 Go微服务应用#

Dockerfile
# ============================================
# 构建阶段
# ============================================
FROM golang:1.22-alpine AS builder
# 安装构建依赖
RUN apk add --no-cache \
ca-certificates \
git \
make \
tzdata
WORKDIR /app
# 利用go mod缓存
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download && \
go mod verify
COPY . .
# 编译二进制文件
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -a -installsuffix cgo \
-ldflags="-s -w -X main.version=$(git describe --tags --always) -X main.buildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o /app/server ./cmd/server
# ============================================
# 最终运行镜像
# ============================================
FROM scratch
# 从builder复制文件
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /app/server /server
# 使用非root用户
USER 65534:65534
EXPOSE 8080
ENTRYPOINT ["/server"]

4.4 Nginx静态网站#

Dockerfile
# ============================================
# 构建阶段:构建前端资源
# ============================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ============================================
# 最终运行镜像
# ============================================
FROM nginx:alpine
# 删除默认nginx配置
RUN rm /etc/nginx/conf.d/default.conf
# 复制自定义配置
COPY nginx.conf /etc/nginx/conf.d/
# 从builder复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 创建非root用户运行nginx
RUN addgroup -g 101 -S nginx && \
adduser -S -D -H -u 101 -h /var/cache/nginx -s /sbin/nologin -G nginx -g nginx nginx && \
chown -R nginx:nginx /usr/share/nginx/html && \
chown -R nginx:nginx /var/cache/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chmod -R 755 /usr/share/nginx/html
USER nginx
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
nginx.conf
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# 开启gzip压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# SPA路由支持
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}

五、总结与展望#

Dockerfile是构建Docker镜像的核心,编写高质量的Dockerfile需要深入理解Docker的工作原理,掌握各种指令的使用方法,并遵循最佳实践。

核心要点回顾:

  1. 基础原则:

    • 使用官方轻量级镜像,明确指定版本标签
    • 采用分层架构思维,合理组织指令顺序
    • 利用构建缓存,加快构建速度
  2. 安全优先:

    • 使用非root用户运行容器
    • 不在镜像中存储敏感信息
    • 定期扫描和更新基础镜像
  3. 性能优化:

    • 使用多阶段构建减小镜像体积
    • 合并RUN指令减少镜像层数
    • 及时清理缓存和临时文件
  4. 可维护性:

    • 添加注释说明复杂逻辑
    • 使用环境变量和构建参数
    • 遵循一致的编码风格

未来趋势:

  • BuildKit增强:Docker正在大力推进BuildKit,提供更强大的缓存管理、并行构建、秘密管理等功能
  • 无发行版镜像:Distroless镜像趋势越来越明显,进一步减小镜像体积和攻击面
  • OCI规范统一:容器镜像规范逐步统一,Dockerfile语法将更加标准化
  • AI辅助优化:AI工具将帮助自动优化Dockerfile,发现潜在问题

掌握Dockerfile编写不是终点而是起点,持续关注Docker生态的发展,结合实际项目不断实践和优化,才能构建出真正适合生产环境的容器镜像。

Dockerfile 编写语法讲解与实践
https://www.6ixblog.site/posts/linux-cloud-6/
作者
Licwic
发布于
2026-05-30
许可协议
CC BY-NC-SA 4.0