Docker多阶段构建及适用镜像推荐

发布于:2025-08-10 ⋅ 阅读:(32) ⋅ 点赞:(0)

之前笔者遇到多阶段构建无法COPY文件的问题,特此写此博客重新学习多阶段构建并尝试复现问题。

摘要

Docker 多阶段构建是一种优化镜像体积、提高构建安全性的方法,它允许在一个 Dockerfile 中定义多个 FROM 阶段。每个阶段可以使用不同的基础镜像,并且只将最终阶段需要的文件复制过来。

✅ 优势:

  • 减小最终镜像体积:只保留运行时所需内容,去掉编译器、构建依赖等。
  • 提高构建安全性:不会将源码、密钥等敏感内容带入最终镜像。
  • 构建逻辑更清晰:分阶段构建更易维护、复用和调试。

🔧 基本语法:

# 构建阶段
FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

# 运行阶段
FROM debian:bookworm-slim
COPY --from=builder /app/myapp /usr/local/bin/myapp
CMD ["myapp"]

🚀 Go 和 Python 项目的多阶段构建镜像建议

🟦 Go 项目:

Go 原生支持静态编译,非常适合多阶段构建。

  • 构建阶段

    • golang:1.21-alpinegolang:1.21
  • 运行阶段

    • scratch(完全空白,适用于纯静态编译)
    • alpine(更小巧,但要注意兼容性)
    • debian:bookworm-slim(更通用)

示例:

FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp

FROM scratch
COPY --from=builder /app/myapp /
ENTRYPOINT ["/myapp"]

🟨 Python 项目:

Python 通常需要解释器和依赖包,不像 Go 那么轻量。但也可以通过多阶段减少构建依赖带来的膨胀。

  • 构建阶段

    • python:3.12-slimpython:3.12
  • 运行阶段

    • python:3.12-alpine(注意有些依赖可能不兼容 alpine)
    • python:3.12-slim
    • 或自定义最小化环境

示例:

FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt --target /deps

FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /deps /usr/local/lib/python3.12/site-packages
COPY . .
CMD ["python", "main.py"]

多阶段构建

参考文档:

可以看到dockerfile现在功能更加完善了:

指令 描述
ADD 添加本地或远程的文件和目录。
ARG 使用构建时变量。
CMD 指定默认的执行命令。
COPY 复制文件和目录。
ENTRYPOINT 指定默认的可执行程序。
ENV 设置环境变量。
EXPOSE 描述应用程序监听的端口。
FROM 从基础镜像创建一个新的构建阶段。
HEALTHCHECK 在容器启动后检查其健康状态。
LABEL 向镜像添加元数据。
MAINTAINER 指定镜像的作者。(已弃用,推荐使用 LABEL)
ONBUILD 指定在该镜像被用作构建基础时自动触发的指令。
RUN 执行构建时命令。
SHELL 设置镜像的默认 shell。
STOPSIGNAL 指定容器退出时使用的系统调用信号。
USER 设置用户和用户组 ID。
VOLUME 创建数据卷挂载点。
WORKDIR 更改工作目录。

多阶段构建依赖COPY命令,废话不多说,对多阶段构建做个简单的介绍后迅速进入主题!

多阶段构建是 Dockerfile 提供的一种机制,允许你在一个构建过程中使用多个 FROM 语句定义多个阶段(stage),并通过 COPY --from=某阶段 将前一阶段构建好的文件复制到最终镜像中。

这个机制的核心思想是:

在不同的构建阶段中使用不同的镜像和依赖,仅将最终运行需要的部分提取到最后的镜像中。

⚙️ 原理详解

  1. 定义多个构建阶段

    • 每个 FROM 都可以理解为一个“构建环境”。
    • 可以为阶段命名(如:FROM golang:1.21 AS builder)。
  2. 在某个阶段中编译或构建项目

    • 安装构建依赖、编译源代码、打包资源等。
  3. 从之前的阶段中复制构建产物

    • 使用 COPY --from=builder 将可执行文件、配置等复制到最终镜像中。
  4. 最终镜像精简、安全

    • 只包含运行时所需内容,体积更小,暴露面更少。

✅ 多阶段构建的好处

优点 说明
🚀 镜像更小 构建依赖、源码不会进入最终镜像,减小体积(比如 Go 项目可降到 <20MB)
🔒 更安全 避免将敏感信息(如源码、token、构建工具)暴露到最终镜像中
🧩 结构清晰 构建流程分阶段管理,逻辑更清晰、易读
🔁 复用阶段 多个阶段可以共用构建产物,提高效率
🔄 支持缓存 各阶段可利用 Docker 的缓存机制加速构建过程
🛠️ 构建环境与运行环境分离 避免在运行镜像中安装构建工具,提高性能和可靠性

实现原理

一、核心机制:多个独立的构建阶段(Build Stage)

Docker 在执行多阶段构建时,其本质是顺序执行多个完全独立的 Dockerfile 构建流程,每个阶段就像执行了一个完整的 Dockerfile(带自己的 FROM 基础镜像、上下文、文件系统等)。

  • 每个 FROM 会创建一个新的构建根文件系统(rootfs)
  • 每个阶段之间的环境、依赖、文件系统完全隔离
  • 阶段之间无法直接“访问”,只能通过 COPY --from= 来显式地提取内容。

二、阶段之间的通信:COPY --from=... 的实现机制

COPY --from=stage 并不是把某阶段打包后再解压,而是通过 Docker 引擎的构建图(Build Graph)进行优化提取:

  • 每个阶段构建的中间产物会被保存为一个临时镜像层(intermediate image layer)

  • COPY --from=builder /app/bin /bin 实际上就是:

    • 将名为 builder 的阶段构建出的临时镜像的文件系统挂载到构建上下文中;
    • 从该挂载点中提取 /app/bin 并复制到当前阶段的 /bin
  • 不涉及实际的中间镜像保存或上传,只是层级文件系统(UnionFS)之间的底层拷贝。

💡 可以理解为 COPY --from 是一种跨阶段、受控的文件系统快照复制

三、镜像层构建原理

Dockerfile 中的每条指令(如 RUN, COPY, ADD)都会生成一个新的镜像层(layer)

  • 多阶段构建中,每个阶段依然会生成自己的多层镜像。
  • 最终产出的镜像只保留最后一个阶段的镜像层,前面的都不会出现在最终镜像中。
  • Docker 引擎使用 内容可寻址存储(Content-addressable storage) 来优化这些层,避免重复构建。

四、缓存与重用

每个构建阶段都参与 Docker 的构建缓存机制:

  • 如果某阶段的所有上层指令都没变,则该阶段可以命中缓存、跳过重新构建;
  • 即使某阶段未命中缓存,其后继阶段只要对应 COPY --from 内容不变,也能复用旧产物。

五、最终镜像输出机制

  • Docker 只会将最后一个阶段的文件系统及其层打包成最终镜像;
  • 其他阶段是中间态,不会保留在最终镜像中,除非你显式使用 --target 生成中间镜像。

可以通过 docker build --target builder -t intermediate . 来查看中间阶段的镜像内容。

🧪 举个底层逻辑示例:

FROM golang:1.21 AS builder
WORKDIR /app
COPY . .
RUN go build -o main

FROM scratch
COPY --from=builder /app/main /main
ENTRYPOINT ["/main"]

构建时发生了什么:

  1. Docker 创建构建阶段 1:基于 golang:1.21,执行 COPYRUN,生成中间镜像 builder
  2. Docker 创建阶段 2:基于 scratch(空白镜像)。
  3. COPY --from=builder 触发文件系统层挂载,从 builder/app/main 拷贝到 /main
  4. 最终镜像只包含 scratch + /main,约几 MB 大小,构建层也独立存在缓存中。

快速开始

🧱 使用多阶段构建

使用多阶段构建时,可以在一个 Dockerfile 中使用多个 FROM 指令。每个 FROM 都可以使用不同的基础镜像,并开始一个新的构建阶段。你可以从某个阶段选择性地复制构建产物到另一个阶段,从而避免把你不希望包含在最终镜像中的内容带进去

下面这个 Dockerfile 展示了两个独立的构建阶段:

一个用于构建 Go 二进制,另一个用于构建最终镜像,仅复制第一阶段的二进制文件。

# syntax=docker/dockerfile:1
FROM golang:1.24
WORKDIR /src
COPY <<EOF ./main.go
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go

FROM scratch
COPY --from=0 /bin/hello /bin/hello
CMD ["/bin/hello"]

你只需要这个单一的 Dockerfile,不需要额外的构建脚本,只需运行:

docker build -t hello .

最终构建出的镜像非常小,只包含一个 Go 编译出的二进制文件,不会包含任何构建工具或中间产物。

🛠 它是如何工作的?

第二个 FROM 指令使用了 scratch 作为基础镜像,开启了新的构建阶段。

COPY --from=0 会从第一个阶段中提取已经构建好的二进制文件。Go SDK 和所有中间产物都不会被包含在最终镜像中。

🏷️ 给构建阶段命名

默认情况下,各阶段没有名称,你需要通过阶段编号(第一个 FROM0)引用它们。但你也可以通过在 FROM 中添加 AS <名称> 的方式给阶段命名。这有助于在将来重排指令时不会影响 COPY 行为。

改进后的示例如下:

# syntax=docker/dockerfile:1
FROM golang:1.24 AS build
WORKDIR /src
COPY <<EOF /src/main.go
package main

import "fmt"

func main() {
  fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.go

FROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]

🧪 停在指定构建阶段(用于调试)

你不一定要构建完整的 Dockerfile。你可以通过 --target 参数指定要停止在哪个阶段:

docker build --target build -t hello .

这在以下场景中非常有用:

  • 调试某个具体的构建阶段;
  • 添加一个包含调试工具的调试阶段,但最终只构建精简的生产镜像;
  • 使用一个测试阶段注入测试数据,生产阶段则使用真实数据。

📦 使用外部镜像作为阶段

你也可以用 COPY --from=镜像名 的方式,从另一个镜像中复制文件,而不一定是当前 Dockerfile 中定义的阶段。Docker 会自动拉取该镜像(如果本地没有)。

例如:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

♻️ 重用前一阶段作为新的阶段

你可以通过再次使用 FROM 并引用前一个命名阶段,来接着使用已有的环境。例如:

# syntax=docker/dockerfile:1

FROM alpine:latest AS builder
RUN apk --no-cache add build-base

FROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cpp

FROM builder AS build2
COPY source2.cpp source.cpp
RUN g++ -o /binary source.cpp

🆚 传统构建器 与 BuildKit 的区别

传统构建器(Legacy Builder)会处理 Dockerfile 中 --target 之前的所有构建阶段,即使目标阶段不依赖于它们。

BuildKit 只会构建目标阶段及其依赖阶段

示例 Dockerfile:

# syntax=docker/dockerfile:1
FROM ubuntu AS base
RUN echo "base"

FROM base AS stage1
RUN echo "stage1"

FROM base AS stage2
RUN echo "stage2"

使用 BuildKit 构建:

DOCKER_BUILDKIT=1 docker build --no-cache -f Dockerfile --target stage2 .

🔍 结果:只构建 basestage2,跳过了 stage1(因为无依赖关系)。

使用传统构建器构建:

DOCKER_BUILDKIT=0 docker build --no-cache -f Dockerfile --target stage2 .

🔍 结果:会顺序执行 basestage1stage2,即使 stage2 不依赖 stage1

BuildKit是什么?

BuildKit 是 Docker 背后的 新一代构建引擎,由 Docker 官方开发,用于替代传统的构建器(Legacy Builder)。它最早在 Docker 18.09 中引入,并在后续版本中逐渐成为默认构建方式。

BuildKit 是一个模块化、并行化、高性能的容器镜像构建引擎,它改进了传统 Docker 构建过程中的效率低、缓存粒度粗、功能单一等问题。

可以把 BuildKit 看成是:

Dockerfile 的“编译器升级版”,让镜像构建更快、更智能、更可控。

🧩 BuildKit 的核心特性

特性 说明
🧠 依赖分析与按需构建 只构建目标阶段及其依赖,跳过无关阶段(支持多阶段构建优化)
🧱 并行构建步骤 多个 RUNCOPY 可以并发执行,显著提升构建速度
💾 更细粒度的缓存 比传统构建器支持更智能的缓存策略,跨项目缓存也更容易
📦 缓存导入/导出 支持将构建缓存导入/导出(如推送到 CI/CD 缓存服务器)
🧰 支持前端构建器插件 支持多个 Dockerfile 前端解析器,如 Dockerfile V1、V2
🔐 更好的安全性 支持 rootless 模式、构建过程隔离更彻底
🧹 自动清理中间层 避免磁盘堆积,镜像空间更干净

🛠 如何启用 BuildKit?

  1. 临时启用:

    DOCKER_BUILDKIT=1 docker build -t myapp .
    
  2. 永久启用:在 Docker 配置文件中设置(Linux/Mac):

    # /etc/docker/daemon.json
    {
      "features": {
        "buildkit": true
      }
    }
    

然后重启 Docker:

```bash
systemctl restart docker
```

🧪 BuildKit 的 CLI 工具:buildx

Docker 官方提供了 docker buildx 子命令,作为 BuildKit 的前端工具,支持:

  • 构建多平台镜像(如同时构建 amd64 和 arm64)
  • 使用本地或远程缓存
  • 控制构建上下文(甚至跨主机构建)
  • 自定义构建器实例

示例:

docker buildx build --platform linux/amd64,linux/arm64 -t myapp:multiarch --push .

📌 总结:BuildKit 与传统构建器对比

对比项 Legacy Builder BuildKit
并发构建 ❌ 不支持 ✅ 支持
缓存粒度
多阶段跳过无关阶段 ❌ 不行 ✅ 可以
多平台构建 ❌ 复杂 ✅ 内建支持
缓存导入导出 ❌ 不支持 ✅ 支持
默认状态 一般默认关闭 Docker Desktop 默认启用
启用方式 自动使用 DOCKER_BUILDKIT=1 或配置文件启用

实际案例

给出一个golang http开启https的简单代码,这里证书我们用openssl签发:

# 1. 生成私钥
openssl genrsa -out server.key 2048

# 2. 生成证书签署请求(CSR)
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Beijing/L=Beijing/O=Example/OU=IT/CN=localhost"

# 3. 使用自签名方式生成 X.509 证书,有效期 365 天
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt

生成文件:

  • server.key: 私钥
  • server.crt: 自签证书

你也可以用交互式生成第二步的证书:openssl req -new -key server.key -out server.csr

在这里插入图片描述
Go 启动 HTTPS(支持 HTTP/2):

package main

import (
	"fmt"
	"log"
	"net/http"
)

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "✅ Hello, HTTPS with HTTP/2! 👋\n")
	log.Printf("📥 Received %s request from %s via %s", r.Method, r.RemoteAddr, r.Proto)
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", helloHandler)

	server := &http.Server{
		Addr:    ":8443",
		Handler: mux,
	}

	log.Println("🚀 Starting HTTPS server on https://localhost:8443 ...")
	err := server.ListenAndServeTLS("cert/server.crt", "cert/server.key")
	if err != nil {
		log.Fatalf("❌ Failed to start server: %v", err)
	}
}

我们建立一个cert文件夹把证书copy进去:
在这里插入图片描述
编译后访问:https://localhost:8443/
在这里插入图片描述
现在我们编写一个dockerfile:

# 第一阶段:构建阶段
FROM golang:1.24 AS builder

WORKDIR /app

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

COPY . .

# 静态构建
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp

# 第二阶段
FROM scratch

WORKDIR /app

COPY --from=builder /app/myapp /app/myapp
COPY --from=builder /app/cert /app/cert

ENTRYPOINT ["/app/myapp"]

然后我们构建并运行:

docker build -t gohttp:1.0.0 .
docker run --name gohttp -p 8443:8443 gohttp:1.0.0

然后检查一下docker启动的镜像,发现服务正常启动:

在这里插入图片描述
看样子笔者之前遇到的无法copy目录的BUG是某个特定版本的docker导致的,最新版docker目前没有复现。

当我们尝试进入容器时:

docker exec -it gohttp bash
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found in $PATH: unknown

这个错误跟 Docker 版本 没关系,主要是因为最终的镜像(scratchdistroless/static)里根本没有 bash 这个命令,所以执行:

docker exec -it gohttp bash

就会报:

exec: "bash": executable file not found in $PATH
  • FROM scratch → 完全空,连 /bin/sh 都没有,更别说 bash
  • FROM gcr.io/distroless/static → 也是极简,只包含运行你的应用需要的动态/静态库,不包含 shell。
  • 所以 exec 进去的时候,找不到你指定的 bash 可执行文件。

解决办法

  1. 开发调试阶段(建议用大一点的基础镜像)

    比如:

    FROM debian:bookworm-slim
    

    或者:

    FROM golang:1.22-bullseye
    

    这样镜像里会有 /bin/bash/bin/sh,方便你 docker exec 进去调试。

  2. 生产环境(scratch/distroless)

    如果你为了减小体积用 scratch / distroless,那么就不能 exec 进去跑 bash 了,可以直接运行你的程序或用 sh(如果基础镜像有)。
    例如:

    docker exec -it gohttp /app/myapp
    

    或者用 busybox 作为调试工具镜像进入:

    docker run -it --rm --network container:gohttp busybox sh
    

    这样是用另一个带 shell 的容器共享目标容器的网络/文件系统环境来调试。

  3. 特殊情况

    如果你就是想在 distroless 镜像中能 exec 进去调试,可以临时在构建时加工具:

    FROM gcr.io/distroless/base-debian12
    # 生产环境请删掉
    RUN apt-get update && apt-get install -y bash
    

    但这会让 distroless 变“大”,失去它的轻量优势。

golang静态构建

当你用一些非常小的基础镜像(例如 scratchdistroless/staticbusybox:glibc 这种),它们几乎没有运行时依赖库,所以 Go 程序必须静态构建,否则运行时会找不到需要的动态库。

什么是 Go 的静态构建?

  • 静态构建:把程序依赖的所有运行时库(比如 libc、加密库等)都打包到一个单独的可执行文件里。

  • 构建出来的二进制文件可以 直接在任何兼容架构的 Linux 上运行,不依赖系统预装的动态库。

  • 在 Go 里,静态构建常见的配置就是:

    CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp
    

为什么小基础镜像需要静态构建?

  • scratchdistroless/static 这类镜像,本质是空的

    • 没有 glibc / musl
    • 没有任何动态链接库(.so 文件)
  • 如果你的 Go 程序编译出来是 动态链接的,在这些镜像里就会运行失败:

    ./myapp: No such file or directory
    

    其实它找不到的是动态库,而不是程序本身。

  • 静态构建的程序则不依赖外部库,所以能在空镜像里正常运行。

如何开启 Go 静态构建?

在大多数场景下,只要:

CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp
  • CGO_ENABLED=0
    禁用 CGO,Go 就会用纯 Go 实现的运行时替代调用 C 代码,这样能彻底去掉对 libc 的依赖。
  • GOOS=linux
    指定目标系统为 Linux(即使你在 Mac/Windows 上交叉编译)。
  • -ldflags="-s -w"
    -s 去掉符号表,-w 去掉调试信息,减小二进制体积。

什么时候不能完全静态构建

  • 如果你依赖某些 C 扩展库(比如使用 SQLite C 版本、调用系统 C 库函数等),禁用 CGO 会导致功能缺失或构建失败。

  • 这时要么:

    1. 换成纯 Go 的库实现
    2. 选一个带动态库的基础镜像(如 distroless/basealpine

python和golang多阶段构建推荐镜像

这里给出推荐的 Python / Go 多阶段构建推荐镜像清单

Go 多阶段构建推荐镜像

镜像名称 解释 适用场景 使用限制
golang:<version> 官方完整 Go 构建镜像,基于 Debian,包含编译工具链和常见库 开发调试、多阶段构建的第一阶段 镜像较大(>1GB),不适合直接用作生产镜像
golang:<version>-alpine 基于 Alpine 的轻量版 Go 构建镜像(musl libc) 需要更小的构建阶段镜像,且无 glibc 依赖 musl 与 glibc 存在兼容性差异,某些依赖可能编译失败
gcr.io/distroless/static-debian12 Google Distroless 静态运行时镜像,无动态库 纯静态构建(CGO_ENABLED=0),生产极简镜像 无 shell、无调试工具、无动态库,调试需额外方法
gcr.io/distroless/base-debian12 Distroless 带最小运行时(glibc、证书等) 需要 glibc 支持的 Go 程序(CGO 开启) 无包管理器,仍不可直接 apt 安装
scratch 空镜像,什么都没有 纯静态 Go 二进制(极限最小体积) 无证书、无时区、无动态库,HTTPS 可能需手动内嵌 CA
busybox:glibc 含基础命令和 glibc 的极小镜像 需要极小镜像 + 基本调试命令 功能有限,不适合复杂依赖
debian:bookworm-slim 官方 Debian 精简版 需要 apt 安装额外依赖、体积适中 不是最小,但兼容性最好
ubuntu:22.04 官方 Ubuntu 基础镜像 需要 Ubuntu 生态支持的依赖 体积较大,启动速度慢

Python 多阶段构建推荐镜像

镜像名称 解释 适用场景 使用限制
python:<version> 官方完整 Python 镜像(基于 Debian) 开发调试、多阶段构建的第一阶段 镜像大(>900MB),不适合直接生产
python:<version>-slim 基于 Debian 的精简版 Python 生产环境常用,依赖安装速度快 缺少编译工具,安装需要编译的包可能失败
python:<version>-alpine 基于 Alpine 的轻量 Python 对镜像体积要求极高的场景 Alpine + Python 有兼容性坑(musl 与某些 PyPI 包不兼容)
gcr.io/distroless/python3 Distroless Python 运行时 纯生产环境,安全加固(无 shell) 不能直接安装 pip 包(需提前打包)
debian:bookworm-slim + pyenv 自定义 Python 安装 需要完全控制 Python 版本和构建方式 构建复杂、体积稍大
ubuntu:22.04 + pyenv 基于 Ubuntu 的自定义环境 企业内部有 Ubuntu 标准的场景 镜像大、构建时间长
conda/miniconda3 最小化 Conda 环境 科学计算、数据分析依赖多的场景 镜像大(>400MB),启动稍慢

额外建议(Go / Python 通用)

  1. 多阶段构建思路

    • 第一阶段(builder)用完整镜像(含编译工具链)
    • 第二阶段(runtime)用极简镜像(scratch / distroless / slim)
  2. 调试与生产分离

    • 调试用带 shell 的镜像(Debian/Ubuntu/Alpine)
    • 生产用无 shell 的镜像(scratch / distroless)
  3. 安全性

    • distroless 无包管理器,不容易被攻击扩展恶意程序
    • scratch 更极端,但失去灵活性
  4. 证书问题(Go HTTPS / Python requests)

    • scratch 和 distroless/static 没有 CA,需要提前内置
    • distroless/base 和 slim 镜像自带 CA

glibc 和 musl 是什么

上文提到了glibc 和 musl 那么它们究竟是什么呢?

它们都是 C 标准库(libc) 的实现,几乎所有 Linux 应用程序在运行时都会依赖它。

即使你的程序是用 Go、Python、Java 写的,只要涉及到底层系统调用(比如网络、文件操作),最终都可能通过 libc 完成。

glibc(GNU C Library)

  • 地位:Linux 世界里最广泛使用的 libc 实现,GNU/Linux 系统的默认选择。

  • 特点

    • 功能齐全,兼容性强。
    • 拥有大量扩展功能(不只是标准 C)。
    • 性能不错,但代码基复杂、体积较大。
  • 代表系统

    • Debian / Ubuntu / CentOS / Fedora / RedHat 都用 glibc。
  • 优缺点

    • ✅ 稳定、兼容性最好,企业生产首选。
    • ❌ 占用空间大(>2MB),启动速度略慢,构建出来的镜像更大。

musl(musl libc)

  • 地位:轻量级 libc 实现,Alpine Linux 默认使用。

  • 特点

    • 代码精简,专注小体积和简单实现。
    • 静态链接更容易,适合容器场景。
    • 体积小(<1MB)。
  • 代表系统

    • Alpine Linux。
  • 优缺点

    • ✅ 镜像体积小、加载快、静态构建简单。
    • ❌ 某些 glibc 特有的功能不兼容(尤其是老旧闭源程序)。
    • ❌ 某些 Python/Rust/Java 库在 musl 下可能编译或运行出错(比如复杂的 C 扩展)。

快速对比表

对比项 glibc musl
体积 较大(~2MB) 较小(<1MB)
兼容性 最好(闭源/老程序可直接跑) 有些库不兼容
运行性能 中(部分场景更快)
常用场景 传统 Linux、企业生产 容器、小镜像、静态构建
代表镜像 debian, ubuntu, distroless/base alpine, distroless/static

镜像体积 vs 安全性 二维对照图

  • X 轴:镜像大小(越右越大)
  • Y 轴:安全性(越高越安全)
  • scratch:极小、极安全(但功能极少)
  • distroless/static:接近 scratch 的体积,安全性很高
  • distroless/base:功能更多(带 glibc),体积略大,安全性仍高
  • alpine:体积小但 musl 兼容性有坑,安全性中等
  • slim 系列:体积适中,安全性中等
  • debian / ubuntu:功能全,兼容性最好,但体积大、安全面大
安全性 ↑
       |
       |   scratch
       |       distroless/static
       |         distroless/base
       |
       |             alpine
       |
       |                   debian-slim / python-slim
       |
       |                         debian / ubuntu
       +----------------------------------------------→ 镜像体积
  • 左上角(小体积,高安全):scratch, distroless/static
  • 中上(中体积,高安全):distroless/base
  • 中间(中体积,中安全):alpine
  • 右下角(大体积,低安全):ubuntu / debian full

常见镜像标签解释

基本概念:Docker 镜像标签(TAG)是什么?

  • 标签(Tag)就是镜像的名字后面跟的版本号或描述,比如 python:3.13-alpine3.21,代表Python 3.13版本,基于 Alpine Linux 3.21 的镜像。
  • 一个镜像标签其实对应多架构镜像清单(manifest list),包含了针对不同CPU架构的不同镜像,比如 linux/amd64linux/arm/v6windows/amd64 等。
  • 镜像体积大小和安全漏洞报告是针对具体架构的镜像层统计的。

Python 镜像标签解析

  • alpine3.213.13.6-alpine3.213.13-alpine3.21

    • 这是基于 Alpine Linux 3.21 版本的镜像,非常小巧(15-16MB),适合体积敏感和轻量级场景。
    • 3.13.6 是 Python 版本,alpine3.21 是基础操作系统版本。
    • 这种镜像大小小,没发现漏洞(None found),很安全。
  • 3-alpine3.223-alpine3.213-alpine

    • 3 代表 Python 3 的最新次版本,alpine3.22 是操作系统版本,3-alpine 则可能指最新 alpine 版本的 Python 3。
  • slim-bullseyeslim-bookwormslim

    • bullseyebookworm 是 Debian Linux 的发行版代号,slim 代表“瘦身”版基础镜像,体积较小但比 Alpine 大(40MB左右),且基于 Debian 系统,兼容性更好。
    • 漏洞数量显示存在(小部分),比 Alpine 镜像多,因为 Debian 镜像组件多,攻击面更大。
  • latestbullseyebookworm

    • latest 是默认最新稳定版本,基于 Debian 的完整版本,体积较大(300MB 以上),漏洞数量较多。
    • bullseyebookworm 直接指定 Debian 发行版,适合对兼容性要求高的项目。
  • 不同架构:每个标签下会有多个架构的镜像,比如:

    • linux/amd64:常见的 x86_64 架构,桌面和服务器主流。
    • linux/arm/v6linux/arm/v7:用于较低功耗设备和嵌入式。
    • windows/amd64:Windows 平台镜像。

Go 镜像标签解析

  • tip-alpine3.22tip-alpine3.21tip-alpine

    • tip 通常表示 Go 的“最新开发版本”或“最新源码版本”,适合追踪 Go 语言最新变化。
    • 基于 Alpine Linux 3.21/3.22,体积小(约80MB),无漏洞报告。
  • 带日期的标签,如 tip-20250801-alpine3.22

    • 这是按具体日期构建的快照版本,便于回滚或重现某一天的镜像环境。
  • windowsservercore-ltsc2025windowsservercore-ltsc2022nanoserver-ltsc2025

    • 这些是 Windows 平台的镜像,分别基于不同 Windows Server 版本的核心镜像。
    • 体积非常大(几GB),因为 Windows 镜像本身就大。
    • nanoserver 是更小更精简的 Windows 容器基础镜像。
  • bullseyebookworm

    • 这是基于 Debian Linux 的不同发行版本,体积大约 270-300MB,漏洞较多。
  • latest

    • 最新稳定版本的镜像,基于 Debian 或 Alpine,体积和漏洞数量依据基础镜像不同。

为什么会有这么多标签?

  • 兼容性和适用场景不同

    • Alpine 版适合极简场景和体积敏感环境。
    • Debian bullseye/bookworm 适合稳定且依赖更多系统库的软件。
    • Windows 镜像适合 Windows 容器环境。
  • 版本控制

    • 通过精确版本号(如 3.13.6)固定 Python/Go 版本,保证构建可复现。
    • 通过 tip 跟踪最新开发版本。
  • 多架构支持

    • 镜像自动支持多平台,方便在不同硬件上运行同样的镜像名。
  • 安全性差异

    • Alpine 由于基础库少,漏洞少。
    • Debian 基础镜像更大,组件多,漏洞相对更多。

镜像大小的差异

  • Alpine 体积小,通常15-80MB。
  • Debian slim 版中等,40-300MB不等。
  • Debian完整版和 Windows 镜像体积很大,几百MB到几GB。
  • 镜像中包含的系统工具、库越多,体积越大。

另外你看到的奇奇怪怪的镜像其实是因为:

Debian 代号是用《玩具总动员》(Toy Story)里角色的名字来给版本命名的,这也是 Debian 的一个经典传统。

  • 所有 Debian 稳定版的代号都是《玩具总动员》动画片中的角色名字。
  • 这个传统从 Debian 1.1 “buzz”开始,后面都是以玩具总动员里的人物名字作为发行代号。

比如:

  • Debian 1.1: buzz(巴斯光年)
  • Debian 1.2: rex(霸王龙)
  • Debian 3.0: woody(胡迪)
  • Debian 11: bullseye(牛眼,一个玩具士兵)
  • Debian 12: bookworm(书虫,动画中一个角色)

为什么用《玩具总动员》?

  • 这是 Debian 创始人和社区的一种幽默和特色,避免用普通的数字或复杂名字,让版本更有趣味。
  • 也便于记忆和区分不同版本。

网站公告

今日签到

点亮在社区的每一天
去签到