Dockerfile 从入门到实战:构建自定义镜像
一、什么是 Dockerfile
Dockerfile 是一个文本文件,其中包含了一系列构建 Docker 镜像所需的指令和参数。通过 Dockerfile,我们可以自动化地构建出符合业务需求的自定义镜像,实现应用的标准化交付。
Dockerfile 的核心价值:将应用环境配置、依赖安装、代码部署等过程代码化,实现基础架构即代码(IaC),让环境构建变得可版本控制、可复现、可审计。
二、Dockerfile 常用指令详解
2.1 指令速查表
| 指令 | 用途 | 示例 |
|---|---|---|
FROM |
指定基础镜像 | FROM ubuntu:22.04 |
RUN |
执行命令(构建时) | RUN apt update && apt install -y nginx |
COPY |
复制文件到镜像中 | COPY ./app /app/ |
ADD |
类似 COPY,支持解压和 URL | ADD app.tar.gz /app/ |
WORKDIR |
设置工作目录 | WORKDIR /app |
CMD |
容器启动时的默认命令 | CMD ["nginx", "-g", "daemon off;"] |
ENTRYPOINT |
容器启动时的固定命令入口 | ENTRYPOINT ["java", "-jar"] |
ENV |
设置环境变量 | ENV JAVA_HOME=/usr/lib/jvm/java-11 |
ARG |
构建参数(构建时传入) | ARG VERSION=latest |
EXPOSE |
声明容器监听的端口 | EXPOSE 8080 |
VOLUME |
声明数据卷挂载点 | VOLUME /data |
USER |
切换运行用户 | USER appuser |
LABEL |
添加元数据标签 | LABEL version="1.0" |
HEALTHCHECK |
检查容器健康状态 | HEALTHCHECK CMD curl -f http://localhost/ |
ONBUILD |
触发器指令(子镜像构建时执行) | ONBUILD COPY . /app |
三、核心指令深度解析
3.1 FROM——指定基础镜像
FROM 是 Dockerfile 的第一条指令,指定构建所用的基础镜像。
# 使用官方 Ubuntu 镜像
FROM ubuntu:22.04
# 使用 Alpine(轻量级 Linux,体积小)
FROM alpine:3.18
# 使用特定语言运行环境
FROM python:3.11-slim
FROM node:18-alpine
FROM openjdk:11-jre-slim
FROM golang:1.21-alpine
FROM nginx:alpine
FROM mysql:5.7
💡 选择建议:优先选择官方镜像,生产环境推荐使用
-slim或-alpine版本以减小镜像体积。
3.2 RUN——执行构建命令
RUN 在构建阶段执行命令,每执行一次 RUN 都会生成一个新的镜像层。
# shell 格式(默认 /bin/sh -c)
RUN apt update && apt install -y curl wget vim
# exec 格式(推荐,更明确)
RUN ["apt", "update"]
RUN ["apt", "install", "-y", "curl"]
# 最佳实践:合并 RUN 命令减少层数
RUN apt update && \
apt install -y curl wget && \
apt clean && \
rm -rf /var/lib/apt/lists/*
⚠️ 注意:每一条
RUN都会产生一个新的镜像层,建议将相关命令用&&合并成一行,减少镜像层数。
3.3 COPY vs ADD——复制文件
| 对比项 | COPY | ADD |
|---|---|---|
| 复制本地文件 | ✅ 支持 | ✅ 支持 |
| 自动解压压缩包 | ❌ 不支持 | ✅ 支持(.tar.gz 等) |
| 从 URL 下载 | ❌ 不支持 | ✅ 支持 |
| 推荐程度 | ⭐⭐⭐⭐⭐ 优先使用 | ⭐⭐ 仅在需要解压时使用 |
# COPY 基本用法
COPY ./app.jar /app/app.jar
COPY --chown=appuser:appuser ./config/ /app/config/
# ADD 用法(解压压缩包)
ADD ./app.tar.gz /app/
# ADD 从 URL 下载
ADD https://example.com/app.tar.gz /tmp/
💡 最佳实践:能使用
COPY就使用COPY,ADD在需要自动解压时再考虑。
3.4 WORKDIR——设置工作目录
WORKDIR 切换当前工作目录,后续的 RUN、CMD、ENTRYPOINT 等指令都会在该目录下执行。
WORKDIR /app
# 相当于:cd /app
RUN mkdir logs
COPY . .
推荐:使用绝对路径,让 Dockerfile 更加清晰。
3.5 CMD 与 ENTRYPOINT——容器启动命令
| 对比项 | CMD | ENTRYPOINT |
|---|---|---|
| 作用 | 提供默认启动命令 | 固定启动命令入口 |
| 是否可被覆盖 | ✅ 启动时可被覆盖 | ❌ 启动时不可被覆盖(--entrypoint 可覆盖) |
| 配合使用方式 | 为 ENTRYPOINT 提供默认参数 | 定义固定命令,CMD 传参 |
# CMD 三种写法
CMD echo "Hello Docker" # shell 格式
CMD ["echo", "Hello Docker"] # exec 格式(推荐)
CMD ["nginx", "-g", "daemon off;"] # 使用 JSON 数组
# ENTRYPOINT 两种写法
ENTRYPOINT ["java", "-jar"] # exec 格式(推荐)
ENTRYPOINT java -jar # shell 格式
# 经典组合:ENTRYPOINT + CMD
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]
# 启动时:docker run -it myapp => java -jar app.jar
# 启动时传参:docker run -it myapp other.jar => java -jar other.jar
💡 选择建议:
- 应用入口固定 → 使用
ENTRYPOINT- 提供可替换的默认命令 → 使用
CMD- 两者结合 →
ENTRYPOINT固定,CMD传参
3.6 ENV 与 ARG——变量管理
| 对比项 | ENV | ARG |
|---|---|---|
| 作用范围 | 构建时 + 运行时 | 仅构建时 |
| 是否持久化 | ✅ 在镜像中持久化 | ❌ 构建完成后不可见 |
| 运行时访问 | ✅ 可在容器内访问 | ❌ 容器内不可访问 |
| 传参方式 | 构建时用 --build-arg |
构建时用 --build-arg |
# ARG:构建参数
ARG APP_VERSION=1.0.0
RUN echo "Building version ${APP_VERSION}"
# ENV:环境变量
ENV JAVA_HOME=/usr/lib/jvm/java-11
ENV APP_ENV=production \
APP_PORT=8080
RUN echo $JAVA_HOME
# 构建时传参
# docker build --build-arg APP_VERSION=2.0.0 -t myapp .
3.7 EXPOSE——声明端口
EXPOSE 仅用于文档说明和端口映射提示,实际端口映射仍需要在 docker run 时使用 -p。
# 声明一个端口
EXPOSE 8080
# 声明多个端口
EXPOSE 80 443 8080
# 声明 UDP 端口
EXPOSE 53/udp
3.8 VOLUME——声明数据卷
VOLUME 声明容器中的挂载点,方便数据持久化。
# 声明单个挂载点
VOLUME /data
# 声明多个挂载点
VOLUME ["/data", "/logs", "/config"]
注意:
VOLUME只是声明,实际挂载仍需在docker run时使用-v指定。
3.9 HEALTHCHECK——健康检查
HEALTHCHECK 让 Docker 定期检查容器健康状态,状态显示为 healthy 或 unhealthy。
# 基本格式
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# 参数说明
# --interval=30s 每隔 30 秒检查一次
# --timeout=3s 检查命令超时时间
# --retries=3 连续失败 3 次标记为 unhealthy
3.10 USER——切换用户
为了安全性,不建议使用 root 用户运行应用。
# 创建用户并切换
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser:appgroup
# 后续命令都以 appuser 身份执行
RUN whoami # 输出 appuser
四、实战案例
4.1 案例一:构建 Nginx 静态站点镜像
# 1. 指定基础镜像
FROM nginx:alpine
# 2. 设置维护者标签
LABEL maintainer="dev@example.com"
# 3. 删除默认页面
RUN rm -rf /usr/share/nginx/html/*
# 4. 复制静态网站文件
COPY ./dist /usr/share/nginx/html
# 5. 复制自定义 Nginx 配置
COPY ./nginx.conf /etc/nginx/conf.d/default.conf
# 6. 声明端口
EXPOSE 80
# 7. 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -q -O - http://localhost/ || exit 1
# 8. 启动命令(Nginx 官方镜像已定义,无需再次指定)
构建与运行:
# 构建镜像
docker build -t my-website:v1 .
# 运行容器
docker run -d --name website -p 8080:80 my-website:v1
# 验证
curl http://localhost:8080
4.2 案例二:构建 Java Spring Boot 应用镜像
# 1. 基础镜像(使用 OpenJDK)
FROM openjdk:11-jre-slim
# 2. 设置元数据
LABEL version="1.0" \
description="Spring Boot Application" \
maintainer="dev@example.com"
# 3. 设置工作目录
WORKDIR /app
# 4. 设置时区(可选)
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 5. 创建非 root 用户
RUN groupadd -r appuser && useradd -r -g appuser appuser
# 6. 复制 JAR 包
COPY --chown=appuser:appuser target/app.jar app.jar
# 7. 暴露端口
EXPOSE 8080
# 8. 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
# 9. 切换用户
USER appuser
# 10. 启动命令
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]
构建与运行:
# 构建镜像
docker build -t spring-app:v1 .
# 带环境变量运行
docker run -d --name spring-app \
-p 8080:8080 \
-e SPRING_PROFILES_ACTIVE=prod \
-e DB_HOST=mysql \
spring-app:v1
# 查看日志
docker logs -f spring-app
4.3 案例三:构建 Python Flask 应用镜像
# 1. 基础镜像
FROM python:3.11-slim
# 2. 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# 3. 设置工作目录
WORKDIR /app
# 4. 复制依赖文件(利用 Docker 缓存层)
COPY requirements.txt .
# 5. 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 6. 复制应用代码
COPY . .
# 7. 创建非 root 用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser
# 8. 暴露端口
EXPOSE 5000
# 9. 启动命令
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
构建与运行:
# 构建镜像
docker build -t flask-app:v1 .
# 运行容器(挂载代码目录,便于开发调试)
docker run -d --name flask-app \
-p 5000:5000 \
-v $(pwd):/app \
flask-app:v1
4.4 案例四:多阶段构建(Multi-stage Build)优化镜像大小
多阶段构建允许在 Dockerfile 中定义多个 FROM 阶段,最终只将需要的文件复制到最终镜像中,有效减少镜像体积。
# ============ 第一阶段:构建阶段 ============
FROM golang:1.21-alpine AS builder
WORKDIR /build
# 复制 go.mod 和 go.sum(利用缓存)
COPY go.mod go.sum ./
RUN go mod download
# 复制源代码并编译
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -o app main.go
# ============ 第二阶段:运行阶段 ============
FROM alpine:latest
# 安装 CA 证书(用于 HTTPS 请求)
RUN apk --no-cache add ca-certificates
WORKDIR /app
# 从构建阶段复制二进制文件
COPY --from=builder /build/app .
# 创建非 root 用户
RUN adduser -D -u 1000 appuser && chown appuser:appuser /app/app
USER appuser
EXPOSE 8080
CMD ["./app"]
对比效果:
- 传统方式:Go 编译镜像约 800MB
- 多阶段构建后:最终运行镜像仅 15MB 🎉
构建与运行:
docker build -t go-app:v1 .
docker run -d --name go-app -p 8080:8080 go-app:v1
4.5 案例五:Node.js 应用 + 多阶段构建
# ============ 第一阶段:构建 ============
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# ============ 第二阶段:运行 ============
FROM nginx:alpine
# 复制构建产物到 Nginx 静态目录
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 Nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
五、Dockerfile 最佳实践总结
5.1 指令编写规范
| 序号 | 最佳实践 | 原因 |
|---|---|---|
| 1 | 使用 COPY 而非 ADD,除非需要自动解压 |
ADD 行为复杂,容易引起混淆 |
| 2 | 合并 RUN 命令,减少镜像层数 |
每层都有开销,影响构建和拉取速度 |
| 3 | 利用 Docker 缓存机制,将变动频率低的指令前置 | 提高构建速度 |
| 4 | 使用 .dockerignore 排除无关文件 |
减少构建上下文体积 |
| 5 | 指定具体的基础镜像版本,避免 latest |
保证构建的可复现性 |
| 6 | 尽量使用 alpine 或 slim 版本基础镜像 |
减小镜像体积 |
| 7 | 使用多阶段构建 | 大幅减小最终镜像大小 |
| 8 | 切换非 root 用户运行应用 | 提高安全性 |
| 9 | 添加 HEALTHCHECK 健康检查 |
便于监控容器状态 |
| 10 | 使用 LABEL 添加元数据 |
方便镜像管理和版本追踪 |
5.2 .dockerignore 示例
# Git 相关
.git/
.gitignore
# 依赖目录
node_modules/
target/
*.pyc
__pycache__/
# 构建产物
*.log
*.tmp
*.swp
.DS_Store
# IDE 配置
.idea/
.vscode/
*.iml
# 敏感信息
.env
*.pem
*.key
secrets/
六、常用构建与调试命令
| 指令 | 说明 | 示例 |
|---|---|---|
docker build |
构建镜像 | docker build -t myapp:v1 . |
docker build --no-cache |
不使用缓存构建 | docker build --no-cache -t myapp:v1 . |
docker build --build-arg |
传入构建参数 | docker build --build-arg VERSION=2.0 -t myapp:v2 . |
docker history |
查看镜像构建历史 | docker history myapp:v1 |
docker image inspect |
查看镜像详情 | docker image inspect myapp:v1 |
docker save |
导出镜像为 tar 包 | docker save -o myapp.tar myapp:v1 |
Dockerfile 是实现 “一次构建,处处运行” 的关键,掌握它意味着你可以将任何应用以标准化、可复现的方式打包交付。当你需要部署时,只需一条 docker run 命令,整个环境就能在任意机器上完美复现——这正是现代云原生时代的基础能力。
如果这篇文章对你有帮助,欢迎收藏、分享,也欢迎在评论区交流讨论!
更多推荐
所有评论(0)