Dockerfile编写完全指南

1. 简介

Dockerfile是一个文本文件,包含了一系列指令和参数,用于自动化构建Docker镜像。通过Dockerfile,我们可以定义应用程序的运行环境、依赖项、配置文件等,实现应用程序的容器化部署。

1.1 什么是Dockerfile

Dockerfile是Docker用来构建镜像的构建文件,是由一系列命令和参数构成的脚本。这些命令应用于基础镜像并最终创建一个新的镜像。

1.2 Dockerfile的优势

  • 可重复性: 确保在不同环境中构建相同的镜像
  • 版本控制: 可以像代码一样进行版本管理
  • 自动化: 支持CI/CD流水线自动构建
  • 透明性: 清晰地展示镜像的构建过程
  • 可移植性: 在任何支持Docker的环境中都能运行

2. Dockerfile基本语法

2.1 基本结构

1
2
3
4
5
6
7
8
# 注释
FROM base_image
MAINTAINER author_info
RUN command
COPY source destination
WORKDIR /path
EXPOSE port
CMD ["executable", "param1", "param2"]

2.2 语法规则

  • 每个指令都必须为大写字母且后面要跟随至少一个参数
  • 指令按照从上到下,顺序执行
  • # 表示注释
  • 每个指令都会创建一个新的镜像层,并对镜像进行提交

3. 常用指令详解

3.1 FROM指令

FROM指令用于指定基础镜像,是Dockerfile的第一个指令。

1
2
3
4
5
6
7
8
9
# 使用官方Python镜像
FROM python:3.9-slim

# 使用特定版本的Ubuntu
FROM ubuntu:20.04

# 使用多阶段构建
FROM node:16 AS builder
FROM nginx:alpine AS production

最佳实践:

  • 优先选择官方镜像
  • 使用具体的版本标签而不是latest
  • 选择最小化的基础镜像(如alpine版本)

3.2 RUN指令

RUN指令用于在镜像构建过程中执行命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Shell形式
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git \
    && rm -rf /var/lib/apt/lists/*

# Exec形式
RUN ["apt-get", "update"]
RUN ["apt-get", "install", "-y", "curl"]

# 多个命令合并
RUN apt-get update \
    && apt-get install -y python3 python3-pip \
    && pip3 install --upgrade pip \
    && apt-get clean

最佳实践:

  • 合并多个RUN指令减少镜像层数
  • 清理包管理器缓存
  • 使用\进行换行提高可读性

3.3 COPY和ADD指令

COPY指令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 复制文件
COPY app.py /app/

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

# 复制多个文件
COPY requirements.txt package.json /app/

# 设置文件权限
COPY --chown=user:group files* /app/

ADD指令

1
2
3
4
5
# ADD具有额外功能:自动解压缩
ADD archive.tar.gz /app/

# 从URL下载文件
ADD https://example.com/file.txt /app/

最佳实践:

  • 优先使用COPY而不是ADD
  • 只有需要自动解压缩或从URL下载时才使用ADD
  • 使用.dockerignore文件排除不需要的文件

3.4 WORKDIR指令

1
2
3
4
5
6
7
8
# 设置工作目录
WORKDIR /app

# 相对路径(基于当前WORKDIR)
WORKDIR src

# 绝对路径
WORKDIR /usr/local/app

最佳实践:

  • 使用绝对路径
  • 避免使用RUN cd命令

3.5 EXPOSE指令

1
2
3
4
5
6
7
8
9
# 暴露单个端口
EXPOSE 80

# 暴露多个端口
EXPOSE 80 443

# 指定协议
EXPOSE 53/udp
EXPOSE 80/tcp

**注意:**EXPOSE指令只是声明端口,实际运行时需要使用-p参数映射端口。

3.6 ENV指令

1
2
3
4
5
6
7
8
# 设置环境变量
ENV NODE_ENV=production
ENV PATH=/app/bin:$PATH

# 一次设置多个变量
ENV NODE_ENV=production \
    PORT=3000 \
    DEBUG=false

3.7 ARG指令

1
2
3
4
5
6
7
# 定义构建参数
ARG VERSION=latest
ARG BUILD_DATE

# 使用构建参数
FROM node:${VERSION}
LABEL build_date=${BUILD_DATE}

构建时传递参数:

1
docker build --build-arg VERSION=16 --build-arg BUILD_DATE=$(date) .

3.8 CMD和ENTRYPOINT指令

CMD指令

1
2
3
4
5
6
7
8
# Exec形式(推荐)
CMD ["python", "app.py"]

# Shell形式
CMD python app.py

# 作为ENTRYPOINT的默认参数
CMD ["--help"]

ENTRYPOINT指令

1
2
3
4
5
6
# 设置容器启动命令
ENTRYPOINT ["python", "app.py"]

# 结合CMD使用
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]

区别:

  • CMD可以被docker run命令覆盖
  • ENTRYPOINT不会被覆盖,docker run的参数会作为参数传递给ENTRYPOINT

3.9 VOLUME指令

1
2
3
4
5
6
# 创建挂载点
VOLUME ["/data"]
VOLUME ["/var/log", "/var/db"]

# 单个目录
VOLUME /app/uploads

3.10 USER指令

1
2
3
4
5
6
7
8
# 创建用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 切换用户
USER appuser

# 使用UID
USER 1000

3.11 HEALTHCHECK指令

1
2
3
4
5
6
# 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 禁用健康检查
HEALTHCHECK NONE

3.12 LABEL指令

1
2
3
4
5
6
7
8
9
# 添加元数据
LABEL version="1.0"
LABEL description="This is a web application"
LABEL maintainer="developer@example.com"

# 多个标签
LABEL version="1.0" \
      description="Web application" \
      maintainer="developer@example.com"

4. 实际应用示例

4.1 Python Web应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 使用官方Python运行时作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

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

# 安装系统依赖
RUN apt-get update \
    && apt-get install -y --no-install-recommends \
        gcc \
        libc6-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制requirements文件
COPY requirements.txt .

# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

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

# 暴露端口
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "app:app"]

4.2 Node.js应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 使用官方Node.js镜像
FROM node:16-alpine

# 设置工作目录
WORKDIR /usr/src/app

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

# 安装依赖
RUN npm ci --only=production && npm cache clean --force

# 复制应用代码
COPY . .

# 创建用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# 更改文件所有权
RUN chown -R nextjs:nodejs /usr/src/app
USER nextjs

# 暴露端口
EXPOSE 3000

# 启动应用
CMD ["node", "server.js"]

4.3 Java Spring Boot应用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 多阶段构建
FROM maven:3.8.4-openjdk-11 AS builder

WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline

COPY src ./src
RUN mvn clean package -DskipTests

# 运行阶段
FROM openjdk:11-jre-slim

WORKDIR /app

# 创建用户
RUN groupadd -r spring && useradd -r -g spring spring

# 复制jar文件
COPY --from=builder /app/target/*.jar app.jar

# 更改所有权
RUN chown spring:spring app.jar
USER spring

# 暴露端口
EXPOSE 8080

# JVM参数优化
ENV JAVA_OPTS="-Xmx512m -Xms256m"

# 启动应用
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar app.jar"]

4.4 静态网站(Nginx)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 构建阶段
FROM node:16-alpine 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

# 复制nginx配置
COPY nginx.conf /etc/nginx/nginx.conf

# 暴露端口
EXPOSE 80

# 启动nginx
CMD ["nginx", "-g", "daemon off;"]

5. 多阶段构建

5.1 基本概念

多阶段构建允许在一个Dockerfile中使用多个FROM指令,每个FROM指令可以使用不同的基础镜像。

5.2 优势

  • 减小最终镜像大小
  • 分离构建环境和运行环境
  • 提高安全性
  • 简化构建流程

5.3 实际示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# 第一阶段:构建
FROM golang:1.19-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o main .

# 第二阶段:运行
FROM alpine:latest

RUN apk --no-cache add ca-certificates
WORKDIR /root/

# 只复制编译后的二进制文件
COPY --from=builder /app/main .

EXPOSE 8080
CMD ["./main"]

5.4 命名构建阶段

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 命名构建阶段
FROM node:16 AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm install

FROM node:16 AS builder
WORKDIR /app
COPY --from=dependencies /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM nginx:alpine AS production
COPY --from=builder /app/dist /usr/share/nginx/html

6. 最佳实践

6.1 镜像大小优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 使用alpine镜像
FROM node:16-alpine

# 合并RUN指令
RUN apk add --no-cache \
    python3 \
    make \
    g++ \
    && npm install \
    && apk del make g++

# 清理缓存
RUN apt-get update \
    && apt-get install -y package \
    && rm -rf /var/lib/apt/lists/*

# 使用.dockerignore
# .dockerignore文件内容:
# node_modules
# .git
# *.log
# .DS_Store

6.2 安全最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 使用非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
USER appuser

# 最小权限原则
COPY --chown=appuser:appuser . /app

# 使用具体版本
FROM node:16.14.2-alpine

# 扫描漏洞
# 使用工具如Trivy、Clair等

6.3 构建缓存优化

1
2
3
4
5
6
7
# 先复制依赖文件
COPY package*.json ./
RUN npm install

# 后复制源代码
COPY . .
RUN npm run build

6.4 环境变量管理

1
2
3
4
5
6
7
8
9
# 使用ARG进行构建时配置
ARG NODE_ENV=production
ENV NODE_ENV=$NODE_ENV

# 敏感信息使用secrets
# docker build --secret id=mypassword,src=./password.txt .
RUN --mount=type=secret,id=mypassword \
    PASSWORD=$(cat /run/secrets/mypassword) && \
    echo "Password: $PASSWORD"

6.5 健康检查

1
2
3
4
5
6
7
# Web应用健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/health || exit 1

# 数据库健康检查
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
    CMD mysqladmin ping -h localhost || exit 1

7. 常见问题和解决方案

7.1 镜像层过多

**问题:**每个RUN指令都会创建新的镜像层

解决方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 错误做法
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN rm -rf /var/lib/apt/lists/*

# 正确做法
RUN apt-get update && \
    apt-get install -y curl vim && \
    rm -rf /var/lib/apt/lists/*

7.2 缓存失效

**问题:**代码变更导致依赖重新安装

解决方案:

1
2
3
4
5
6
# 先复制依赖文件
COPY requirements.txt .
RUN pip install -r requirements.txt

# 后复制源代码
COPY . .

7.3 时区问题

1
2
3
# 设置时区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

7.4 权限问题

1
2
3
# 确保文件权限正确
COPY --chown=appuser:appuser . /app
RUN chmod +x /app/start.sh

8. 调试和测试

8.1 构建调试

1
2
3
4
5
6
7
8
# 查看构建过程
docker build --progress=plain --no-cache .

# 构建到特定阶段
docker build --target builder .

# 查看镜像历史
docker history image_name

8.2 运行时调试

1
2
3
4
5
6
7
8
# 交互式运行
docker run -it image_name /bin/bash

# 覆盖入口点
docker run -it --entrypoint /bin/bash image_name

# 查看容器日志
docker logs container_name

8.3 镜像分析

1
2
3
4
5
# 使用dive分析镜像层
dive image_name

# 查看镜像大小
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

9. CI/CD集成

9.1 GitHub Actions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
name: Build and Push Docker Image

on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    
    - name: Build Docker image
      run: docker build -t myapp:${{ github.sha }} .
    
    - name: Push to registry
      run: |
        echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
        docker push myapp:${{ github.sha }}

9.2 GitLab CI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

10. 高级技巧

10.1 BuildKit特性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# syntax=docker/dockerfile:1
FROM alpine

# 使用缓存挂载
RUN --mount=type=cache,target=/var/cache/apk \
    apk add --update git

# 使用secrets
RUN --mount=type=secret,id=mypassword \
    cat /run/secrets/mypassword

10.2 条件构建

1
2
3
4
5
6
ARG ENVIRONMENT=production

# 开发环境安装调试工具
RUN if [ "$ENVIRONMENT" = "development" ]; then \
        apt-get update && apt-get install -y vim curl; \
    fi

10.3 动态标签

1
2
3
ARG VERSION
LABEL version=$VERSION
LABEL build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')

11. 性能优化

11.1 并行构建

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 使用BuildKit并行构建
# DOCKER_BUILDKIT=1 docker build .

FROM alpine AS stage1
RUN sleep 10

FROM alpine AS stage2
RUN sleep 10

FROM alpine
COPY --from=stage1 /etc/os-release /stage1-info
COPY --from=stage2 /etc/os-release /stage2-info

11.2 镜像压缩

1
2
3
4
5
# 使用squash减少层数
docker build --squash -t myapp .

# 使用multi-stage构建
# 只保留最终运行时需要的文件

12. 监控和日志

12.1 日志配置

1
2
3
# 确保日志输出到stdout/stderr
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log

12.2 监控集成

1
2
3
# 添加监控端点
EXPOSE 9090
HEALTHCHECK --interval=30s CMD curl -f http://localhost:9090/metrics

总结

Dockerfile是容器化应用的核心,掌握其编写技巧对于现代应用开发至关重要。

关键要点:

  1. 基础指令掌握:熟练使用FROM、RUN、COPY、WORKDIR等基本指令
  2. 多阶段构建:有效减小镜像大小,分离构建和运行环境
  3. 安全实践:使用非root用户,最小权限原则
  4. 性能优化:合理利用缓存,减少镜像层数
  5. 最佳实践:遵循Docker官方推荐的编写规范

实际应用建议:

  1. 从简单开始:先编写基本的Dockerfile,逐步优化
  2. 版本控制:将Dockerfile纳入版本控制系统
  3. 自动化测试:在CI/CD流水线中集成镜像构建和测试
  4. 安全扫描:定期扫描镜像漏洞
  5. 监控优化:持续监控镜像大小和构建时间