02. Docker 镜像深入与 Dockerfile

02. Docker 镜像深入与 Dockerfile

2.1 Docker 镜像原理

2.1.1 镜像的本质:分层文件系统

Docker 镜像采用 Union File System (联合文件系统) 技术,支持将不同的目录挂载到同一个虚拟文件系统下。

分层结构示例:

复制代码
Ubuntu 镜像的层次结构:
┌─────────────────────────────┐
│  可写容器层 (Container Layer) │  ← 容器启动时添加
├─────────────────────────────┤
│  应用层 (your-app)           │  ← 你的应用
├─────────────────────────────┤
│  依赖层 (dependencies)       │  ← 应用依赖
├─────────────────────────────┤
│  运行时层 (python/node)      │  ← 运行时环境
├─────────────────────────────┤
│  系统层 (ubuntu:22.04)       │  ← 基础操作系统
└─────────────────────────────┘
     ↓ (所有层只读)

分层的优势:

  1. 共享资源:多个镜像可以共享相同的基础层
  2. 快速分发:只需传输差异层
  3. 节省空间:相同的层只存储一份
  4. 快速构建:利用缓存机制

实例说明:

bash 复制代码
# 假设你有两个镜像
镜像A: Ubuntu(200MB) + Python(100MB) + App1(50MB) = 350MB
镜像B: Ubuntu(200MB) + Python(100MB) + App2(50MB) = 350MB

# 实际占用空间
Ubuntu层(200MB) + Python层(100MB) + App1(50MB) + App2(50MB) = 400MB
# 节省了 300MB!

2.1.2 查看镜像分层

bash 复制代码
# 拉取一个镜像
docker pull nginx:alpine

# 查看镜像分层历史
docker history nginx:alpine

# 输出示例:
# IMAGE          CREATED        SIZE      COMMENT
# <missing>      2 weeks ago    7.3MB     CMD ["nginx" "-g" "daemon off;"]
# <missing>      2 weeks ago    0B        EXPOSE 80
# <missing>      2 weeks ago    42MB      RUN /bin/sh -c apk add nginx
# <missing>      3 weeks ago    7.8MB     /bin/sh -c #(nop) ADD file...

查看详细信息:

bash 复制代码
# 查看镜像的详细 JSON 信息
docker inspect nginx:alpine

# 查看镜像的层信息
docker inspect nginx:alpine | grep -A 10 "Layers"

2.1.3 镜像存储驱动

Docker 支持多种存储驱动:

存储驱动 适用系统 特点
overlay2 现代Linux 推荐,性能最好
aufs Ubuntu/Debian 旧版,稳定
devicemapper CentOS/RHEL 较慢
btrfs 支持Btrfs的系统 高级特性
zfs 支持ZFS的系统 企业级

查看当前存储驱动:

bash 复制代码
docker info | grep "Storage Driver"
# Storage Driver: overlay2

2.2 镜像管理

2.2.1 镜像命名规范

Docker 镜像的完整格式:

复制代码
[registry-url/]repository[:tag][@digest]

组成部分:

  • registry-url:仓库地址(默认 docker.io
  • repository:镜像名称(可包含组织/用户名)
  • tag:版本标签(默认 latest)
  • digest:镜像摘要(SHA256哈希)

示例:

bash 复制代码
nginx                          # 完整:docker.io/library/nginx:latest
nginx:1.24                     # 完整:docker.io/library/nginx:1.24
myregistry.com/myapp:v1.0     # 私有仓库镜像
ubuntu@sha256:abc123...       # 使用摘要引用(不可变)

2.2.2 拉取镜像

bash 复制代码
# 拉取最新版本
docker pull nginx

# 拉取指定版本
docker pull nginx:1.24-alpine

# 从私有仓库拉取
docker pull myregistry.com:5000/myapp:v1.0

# 拉取所有标签
docker pull -a nginx

# 查看拉取进度(详细模式)
docker pull --quiet=false nginx

拉取时发生了什么?

复制代码
1. 连接到 Registry
2. 检查镜像的 manifest (清单)
3. 下载各个层 (layer)
4. 验证每个层的校验和
5. 解压并存储到本地

2.2.3 查看本地镜像

bash 复制代码
# 列出所有本地镜像
docker images

# 或使用
docker image ls

# 查看所有镜像(包括中间层)
docker images -a

# 只显示镜像 ID
docker images -q

# 格式化输出
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

# 过滤镜像
docker images nginx              # 查看所有 nginx 镜像
docker images --filter "dangling=true"  # 查看悬挂镜像

输出说明:

复制代码
REPOSITORY    TAG       IMAGE ID       CREATED       SIZE
nginx         latest    a6bd71f48f68   2 weeks ago   187MB
ubuntu        22.04     27941809078c   3 weeks ago   77.8MB
  • REPOSITORY: 镜像仓库名
  • TAG: 镜像标签
  • IMAGE ID: 镜像唯一标识
  • CREATED: 镜像创建时间
  • SIZE: 镜像大小

2.2.4 搜索镜像

bash 复制代码
# 在 Docker Hub 搜索镜像
docker search nginx

# 限制搜索结果数量
docker search --limit 5 nginx

# 只显示官方镜像
docker search --filter "is-official=true" nginx

# 只显示星级超过 100 的镜像
docker search --filter "stars=100" nginx

2.2.5 删除镜像

bash 复制代码
# 通过镜像名删除
docker rmi nginx:latest

# 通过镜像 ID 删除
docker rmi a6bd71f48f68

# 删除多个镜像
docker rmi nginx:1.24 nginx:1.23

# 强制删除(即使有容器在使用)
docker rmi -f nginx

# 删除所有悬挂镜像(dangling images)
docker image prune

# 删除所有未使用的镜像
docker image prune -a

# 删除所有镜像
docker rmi $(docker images -q)

什么是悬挂镜像?

  • 没有标签的镜像(<none>:<none>
  • 通常是构建新版本后的旧镜像
  • 不再被任何容器使用

2.2.6 镜像标签管理

bash 复制代码
# 给镜像打标签(创建副本引用)
docker tag nginx:latest mynginx:v1.0

# 为推送到私有仓库准备
docker tag myapp:latest myregistry.com:5000/myapp:v1.0

# 标记为多个版本
docker tag myapp:latest myapp:v1.0
docker tag myapp:latest myapp:stable

2.2.7 镜像的导入导出

导出镜像(保存为 tar 文件):

bash 复制代码
# 导出单个镜像
docker save -o nginx.tar nginx:latest

# 导出多个镜像
docker save -o images.tar nginx:latest ubuntu:22.04

# 使用管道压缩
docker save nginx:latest | gzip > nginx.tar.gz

导入镜像:

bash 复制代码
# 从 tar 文件加载镜像
docker load -i nginx.tar

# 从压缩文件加载
docker load < nginx.tar.gz

使用场景:

  • 离线环境部署
  • 镜像备份
  • 跨主机传输镜像

2.2.8 镜像详细信息

bash 复制代码
# 查看镜像详细信息(JSON 格式)
docker inspect nginx:latest

# 查看特定字段
docker inspect --format='{{.Architecture}}' nginx
docker inspect --format='{{.Os}}' nginx
docker inspect --format='{{.Size}}' nginx

2.3 Dockerfile 基础

2.3.1 什么是 Dockerfile?

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

优势:

  • 版本控制:可以放入 Git 管理
  • 可重复:任何人都能重现相同的镜像
  • 透明:清楚地知道镜像包含什么
  • 自动化:集成到 CI/CD 流程

2.3.2 基本结构

dockerfile 复制代码
# 注释以 # 开头
FROM ubuntu:22.04                # 基础镜像
LABEL maintainer="you@example.com"  # 元数据

RUN apt-get update && \          # 执行命令
    apt-get install -y python3

COPY app.py /app/                # 复制文件
WORKDIR /app                     # 设置工作目录

ENV APP_ENV=production           # 环境变量
EXPOSE 8080                      # 暴露端口

CMD ["python3", "app.py"]        # 容器启动命令

2.3.3 核心指令详解

FROM - 指定基础镜像
dockerfile 复制代码
# 从官方镜像开始
FROM ubuntu:22.04

# 从特定版本
FROM python:3.11-slim

# 从 scratch(空白镜像)
FROM scratch

# 多阶段构建
FROM node:18 AS builder
FROM nginx:alpine AS production

最佳实践:

  • 使用官方镜像
  • 使用具体版本标签(不要用 latest
  • 优先使用 Alpine/Slim 版本(更小)
RUN - 执行命令
dockerfile 复制代码
# Shell 形式(推荐用于复杂命令)
RUN apt-get update && apt-get install -y curl

# Exec 形式(不会启动 shell)
RUN ["apt-get", "update"]

# 合并多个命令以减少层数
RUN apt-get update && \
    apt-get install -y \
        python3 \
        python3-pip \
        git && \
    rm -rf /var/lib/apt/lists/*

# 使用 && 链接命令,使用 \ 换行

最佳实践:

  • 合并 RUN 指令减少层数
  • 清理缓存文件(如 apt lists)
  • 使用 && 确保前一个命令成功
COPY vs ADD

COPY(推荐):

dockerfile 复制代码
# 复制单个文件
COPY app.py /app/

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

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

# 使用通配符
COPY *.py /app/

# 保留文件属性
COPY --chown=user:group files* /app/

ADD(特殊用途):

dockerfile 复制代码
# ADD 支持 URL
ADD https://example.com/file.tar.gz /tmp/

# ADD 自动解压 tar 文件
ADD archive.tar.gz /app/

何时使用哪个?

  • 一般情况使用 COPY(更透明)
  • 需要下载 URL 或自动解压时使用 ADD
WORKDIR - 设置工作目录
dockerfile 复制代码
# 设置工作目录(自动创建)
WORKDIR /app

# 相对路径(相对于上一个 WORKDIR)
WORKDIR /app
WORKDIR src         # 实际是 /app/src

# 使用环境变量
ENV APP_HOME=/app
WORKDIR ${APP_HOME}

最佳实践:

  • 使用绝对路径
  • 不要使用 RUN cd /app(不持久)
ENV - 设置环境变量
dockerfile 复制代码
# 设置单个变量
ENV NODE_ENV=production

# 设置多个变量
ENV APP_HOME=/app \
    APP_PORT=8080 \
    APP_VERSION=1.0

# 在后续指令中使用
ENV APP_DIR=/app
WORKDIR ${APP_DIR}
COPY . ${APP_DIR}

常用环境变量:

dockerfile 复制代码
ENV PYTHONUNBUFFERED=1          # Python 不缓冲输出
ENV DEBIAN_FRONTEND=noninteractive  # apt-get 非交互模式
ENV PATH=/app/bin:$PATH         # 添加到 PATH
EXPOSE - 声明端口
dockerfile 复制代码
# 声明容器监听的端口
EXPOSE 80

# 声明多个端口
EXPOSE 8080 8443

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

注意:

  • EXPOSE 只是声明,不会真正发布端口
  • 需要 docker run -p 来实际映射端口
CMD vs ENTRYPOINT

CMD - 容器默认命令:

dockerfile 复制代码
# Shell 形式
CMD python app.py

# Exec 形式(推荐)
CMD ["python", "app.py"]

# 作为 ENTRYPOINT 的参数
CMD ["--port", "8080"]

ENTRYPOINT - 容器入口点:

dockerfile 复制代码
# 定义可执行容器
ENTRYPOINT ["python", "app.py"]

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

区别对比:

dockerfile 复制代码
# 只有 CMD
CMD ["echo", "hello"]
# 运行:docker run myimage         → 输出 "hello"
# 运行:docker run myimage ls      → 执行 ls(覆盖 CMD)

# 只有 ENTRYPOINT
ENTRYPOINT ["echo", "hello"]
# 运行:docker run myimage         → 输出 "hello"
# 运行:docker run myimage world   → 输出 "hello world"

# ENTRYPOINT + CMD
ENTRYPOINT ["echo"]
CMD ["hello"]
# 运行:docker run myimage         → 输出 "hello"
# 运行:docker run myimage world   → 输出 "world"

最佳实践:

  • 需要灵活运行不同命令:使用 CMD
  • 容器始终运行同一程序:使用 ENTRYPOINT
  • 组合使用:ENTRYPOINT 定义程序,CMD 定义默认参数
USER - 切换用户
dockerfile 复制代码
# 创建用户并切换
RUN useradd -m -u 1000 appuser
USER appuser

# 切换到特定用户和组
USER appuser:appgroup

# 使用 UID
USER 1000

安全最佳实践:

dockerfile 复制代码
FROM python:3.11-slim

# 创建非 root 用户
RUN useradd -m -u 1000 appuser && \
    mkdir -p /app && \
    chown -R appuser:appuser /app

# 切换到非 root 用户
USER appuser

WORKDIR /app
COPY --chown=appuser:appuser . .

CMD ["python", "app.py"]
VOLUME - 声明挂载点
dockerfile 复制代码
# 声明数据卷挂载点
VOLUME /data

# 声明多个挂载点
VOLUME ["/data", "/logs"]

用途:

  • 持久化数据
  • 共享数据
  • 提高 I/O 性能
ARG - 构建参数
dockerfile 复制代码
# 定义构建参数
ARG VERSION=1.0
ARG PYTHON_VERSION=3.11

# 使用构建参数
FROM python:${PYTHON_VERSION}-slim

RUN echo "Building version ${VERSION}"

# 设置默认值
ARG APP_PORT=8080
EXPOSE ${APP_PORT}

构建时传递参数:

bash 复制代码
docker build --build-arg VERSION=2.0 --build-arg PYTHON_VERSION=3.12 .

ARG vs ENV:

  • ARG:只在构建时可用
  • ENV:构建和运行时都可用
LABEL - 添加元数据
dockerfile 复制代码
# 添加元数据
LABEL version="1.0"
LABEL description="My awesome app"
LABEL maintainer="you@example.com"

# 多个标签
LABEL version="1.0" \
      description="My app" \
      maintainer="you@example.com"

2.4 构建镜像

2.4.1 基本构建

创建 Dockerfile:

dockerfile 复制代码
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

构建镜像:

bash 复制代码
# 基本构建(自动生成 ID)
docker build .

# 指定标签
docker build -t myapp:v1.0 .

# 指定 Dockerfile 路径
docker build -f Dockerfile.prod -t myapp:prod .

# 指定构建上下文
docker build -t myapp /path/to/context

# 不使用缓存
docker build --no-cache -t myapp .

# 查看构建过程
docker build --progress=plain -t myapp .

2.4.2 构建上下文

什么是构建上下文?

  • docker build 命令最后的路径(如 .
  • 该目录下的所有文件都会发送给 Docker daemon
  • COPYADD 指令使用的相对路径基准

优化构建上下文:

.dockerignore 文件:

dockerignore 复制代码
# 忽略文件和目录
node_modules
npm-debug.log
.git
.gitignore
*.md
.env
.vscode
__pycache__
*.pyc
*.pyo
*.pyd
.pytest_cache
.coverage

# 使用通配符
**/*.log
**/.DS_Store

# 例外规则
!important.log

2.4.3 构建缓存机制

Docker 构建使用缓存来加速:

dockerfile 复制代码
FROM python:3.11-slim

# ✓ 这层会被缓存
WORKDIR /app

# ✓ 依赖很少变化,使用缓存
COPY requirements.txt .
RUN pip install -r requirements.txt

# ✗ 代码经常变化,这层之后的都会重新构建
COPY . .

CMD ["python", "app.py"]

缓存失效规则:

  1. Dockerfile 指令改变
  2. 复制的文件内容改变
  3. 父层缓存失效

优化技巧:

  • 把不常变化的层放在前面
  • 依赖安装和代码复制分开
  • 合理使用 .dockerignore

2.4.4 多阶段构建

问题场景:

  • 编译型语言需要编译工具
  • 构建后的镜像包含不必要的工具
  • 镜像体积过大

多阶段构建解决方案:

dockerfile 复制代码
# 阶段 1:构建阶段
FROM node:18 AS builder

WORKDIR /app
COPY package*.json ./
RUN npm install

COPY . .
RUN npm run build

# 阶段 2:运行阶段
FROM nginx:alpine

# 只复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

另一个示例(Go 应用):

dockerfile 复制代码
# 构建阶段
FROM golang:1.21 AS builder

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

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

# 运行阶段(使用最小镜像)
FROM alpine:latest

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

# 从构建阶段复制二进制文件
COPY --from=builder /app/myapp .

CMD ["./myapp"]

优势:

  • 构建镜像:1.2GB → 运行镜像:15MB
  • 只包含运行时需要的文件
  • 更安全(没有构建工具)

2.5 实战案例

案例 1:Flask Web 应用

项目结构:

复制代码
my-flask-app/
├── app.py
├── requirements.txt
└── Dockerfile

app.py

python 复制代码
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello from Docker!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

requirements.txt:

复制代码
Flask==3.0.0

Dockerfile:

dockerfile 复制代码
FROM python:3.11-slim

WORKDIR /app

# 先复制依赖文件(利用缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 再复制应用代码
COPY app.py .

EXPOSE 5000

CMD ["python", "app.py"]

构建和运行:

bash 复制代码
# 构建镜像
docker build -t flask-app:v1.0 .

# 运行容器
docker run -d -p 5000:5000 --name my-flask-app flask-app:v1.0

# 测试
curl http://localhost:5000

案例 2:Node.js 应用(多阶段构建)

Dockerfile:

dockerfile 复制代码
# 构建阶段
FROM node:18-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

# 运行阶段
FROM node:18-alpine

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001

WORKDIR /app

# 从构建阶段复制依赖
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules

# 复制应用代码
COPY --chown=nodejs:nodejs . .

USER nodejs

EXPOSE 3000

CMD ["node", "server.js"]

案例 3:优化的 Nginx 静态站点

Dockerfile:

dockerfile 复制代码
FROM nginx:alpine

# 删除默认配置
RUN rm /etc/nginx/conf.d/default.conf

# 复制自定义配置
COPY nginx.conf /etc/nginx/conf.d/

# 复制静态文件
COPY dist/ /usr/share/nginx/html/

# 使用非 root 用户运行(需要自定义配置)
USER nginx

EXPOSE 8080

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --quiet --tries=1 --spider http://localhost:8080/ || exit 1

CMD ["nginx", "-g", "daemon off;"]

2.6 最佳实践总结

镜像体积优化

dockerfile 复制代码
# 不推荐的做法
FROM ubuntu:22.04
RUN apt-get update
RUN apt-get install -y python3
RUN apt-get install -y python3-pip
COPY . /app
# 镜像大小:~500MB

# 推荐的做法
FROM python:3.11-alpine
RUN apk add --no-cache python3
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
# 镜像大小:~50MB

安全性

dockerfile 复制代码
# ✓ 使用非 root 用户
RUN useradd -m appuser
USER appuser

# ✓ 使用具体版本
FROM python:3.11-slim  # 而不是 python:latest

# ✓ 不要在镜像中存储机密信息
# 使用 docker secrets 或环境变量

构建效率

dockerfile 复制代码
# ✓ 把不常变化的层放前面
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .  # 代码经常变,放后面

# ✓ 合并 RUN 命令
RUN apt-get update && \
    apt-get install -y package1 package2 && \
    rm -rf /var/lib/apt/lists/*

# ✓ 使用 .dockerignore

相关推荐
DreamLife☼2 个月前
Docker-Dockerfile 完全指南:编写最佳实践的镜像
docker·镜像构建·多阶段构建·docker 安全·最小化镜像·非 root 用户·构建缓存