Dockerfile 编写指南

Dockerfile 完整编写教程和最佳实践


📋 目录


Dockerfile 基础

什么是 Dockerfile

Dockerfile 是一个文本文件,包含了一系列指令,用于自动化构建 Docker 镜像。

基本结构

# 注释
FROM base-image:tag
LABEL maintainer="your-email@example.com"
RUN command
COPY source dest
WORKDIR /path
EXPOSE port
CMD ["executable", "param1", "param2"]

常用指令

FROM

指定基础镜像。

# 使用官方镜像
FROM ubuntu:20.04
 
# 使用 Alpine(体积小)
FROM alpine:latest
 
# 多阶段构建
FROM node:16 AS builder

RUN

执行命令并创建新的镜像层。

# 执行单个命令
RUN apt-get update
 
# 执行多个命令(合并减少层数)
RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/*
 
# 使用 shell 形式
RUN echo "Hello" > /tmp/hello.txt

COPY / ADD

复制文件到镜像中。

# COPY(推荐)
COPY package.json /app/
COPY . /app/
 
# ADD(支持 URL 和解压,不推荐)
ADD https://example.com/file.tar.gz /tmp/
ADD file.tar.gz /tmp/  # 自动解压
 
# 复制多个文件
COPY package*.json ./
COPY src/ /app/src/
# 基本语法
COPY <源路径> <目标路径>
 
# 示例
COPY package.json /app/package.json      # 复制并重命名
COPY package.json /app/                  # 复制到目录,保持原名
COPY src/ /app/src/                      # 复制整个目录
COPY . /app/                             # 复制所有
COPY *.js /app/                          # 通配符
COPY ["file1", "file2", "/target/"]      # JSON 格式(包含空格时用)

区别

  • COPY:只复制本地文件,更明确
  • ADD:支持 URL 和自动解压,但行为不够明确

WORKDIR

设置工作目录。

WORKDIR /app
RUN pwd  # 输出 /app

ENV

设置环境变量。

# 设置单个变量
ENV NODE_ENV=production
 
# 设置多个变量
ENV NODE_ENV=production \
    PORT=3000
 
# 使用变量
ENV PATH=$PATH:/usr/local/bin

ARG

定义构建参数。

# 定义构建参数
ARG VERSION=latest
ARG BUILD_DATE
 
# 使用构建参数
RUN echo "Building version $VERSION"

构建时传递参数:

docker build --build-arg VERSION=1.0 -t myapp:1.0 .

EXPOSE

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

EXPOSE 80
EXPOSE 443
EXPOSE 3000/tcp
EXPOSE 8080/udp

注意EXPOSE 只是声明,不会自动映射端口,需要使用 -p 参数。

CMD

指定容器启动时执行的命令(可被覆盖)。

# Shell 形式
CMD echo "Hello World"
 
# Exec 形式(推荐)
CMD ["executable", "param1", "param2"]
 
# 使用 ENTRYPOINT 配合
CMD ["--help"]

ENTRYPOINT

指定容器启动时执行的命令(不可被覆盖)。

# Exec 形式(推荐)
ENTRYPOINT ["executable", "param1", "param2"]
 
# Shell 形式
ENTRYPOINT echo "Hello"

与 CMD 的区别

  • ENTRYPOINT:容器启动时总是执行,参数会追加
  • CMD:可以被 docker run 的参数覆盖

USER

指定运行容器的用户。

# 创建用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
 
# 切换到用户
USER appuser
 
# 切换到用户 ID
USER 1000

VOLUME

创建数据卷挂载点。

VOLUME ["/data"]
VOLUME /var/log

LABEL

添加元数据。

LABEL maintainer="your-email@example.com"
LABEL version="1.0"
LABEL description="My application"

HEALTHCHECK

定义健康检查。

# 检查命令
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:3000/health || exit 1
 
# 禁用健康检查
HEALTHCHECK NONE

多阶段构建

为什么使用多阶段构建

  • 减小最终镜像体积
  • 只包含运行时需要的文件
  • 构建工具不进入最终镜像

示例

# 阶段 1:构建
FROM node:16 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
 
# 阶段 2:运行
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
EXPOSE 3000
CMD ["node", "dist/index.js"]

最佳实践

1. 使用 .dockerignore

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

node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.nyc_output
coverage

2. 合理使用缓存

# 先复制依赖文件,利用缓存
COPY package*.json ./
RUN npm install
 
# 再复制源代码
COPY . .

3. 减少镜像层数

# 不好:多个 RUN 指令
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
 
# 好:合并 RUN 指令
RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/*

4. 使用特定版本标签

# 不好:使用 latest
FROM ubuntu:latest
 
# 好:使用特定版本
FROM ubuntu:20.04

5. 使用多阶段构建

减小最终镜像体积。

6. 非 root 用户运行

RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

7. 最小化镜像

使用 Alpine 等轻量级基础镜像。

FROM alpine:latest
RUN apk add --no-cache nodejs npm

实际案例

Node.js 应用

FROM node:16-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
 
FROM node:16-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs
EXPOSE 3000
CMD ["node", "index.js"]

Python 应用

FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
CMD ["python", "app.py"]

Nginx 静态网站

FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY dist/ /usr/share/nginx/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

📚 参考资源


相关笔记