当前位置: 首页 > news >正文

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

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

目录

  • 摘要
  • 多阶段构建
    • 实现原理
      • 一、核心机制:**多个独立的构建阶段(Build Stage)**
      • 二、阶段之间的通信:`COPY --from=...` 的实现机制
      • 三、镜像层构建原理
      • 四、缓存与重用
      • 五、最终镜像输出机制
    • 快速开始
      • 🧱 使用多阶段构建
      • 🏷️ 给构建阶段命名
      • 🧪 停在指定构建阶段(用于调试)
      • 📦 使用外部镜像作为阶段
      • ♻️ 重用前一阶段作为新的阶段
      • 🆚 传统构建器 与 BuildKit 的区别
        • BuildKit是什么?
  • 实际案例
    • golang静态构建
    • python和golang多阶段构建推荐镜像
    • glibc 和 musl 是什么
      • **glibc(GNU C Library)**
      • **musl(musl libc)**
    • **镜像体积 vs 安全性 二维对照图**
    • 常见镜像标签解释

摘要

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 myappFROM 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 /depsFROM python:3.12-slim
WORKDIR /app
COPY --from=builder /deps /usr/local/lib/python3.12/site-packages
COPY . .
CMD ["python", "main.py"]

多阶段构建

参考文档:

  • dockerfile:https://docs.docker.com/reference/dockerfile/
  • Multi-stage builds:https://docs.docker.com/build/building/multi-stage/

可以看到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 mainFROM 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 mainimport "fmt"func main() {fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.goFROM 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 mainimport "fmt"func main() {fmt.Println("hello, world")
}
EOF
RUN go build -o /bin/hello ./main.goFROM 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:1FROM alpine:latest AS builder
RUN apk --no-cache add build-baseFROM builder AS build1
COPY source1.cpp source.cpp
RUN g++ -o /binary source.cppFROM 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 BuilderBuildKit
并发构建❌ 不支持✅ 支持
缓存粒度
多阶段跳过无关阶段❌ 不行✅ 可以
多平台构建❌ 复杂✅ 内建支持
缓存导入导出❌ 不支持✅ 支持
默认状态一般默认关闭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 mainimport ("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 builderWORKDIR /appCOPY go.mod go.sum ./
RUN go mod downloadCOPY . .# 静态构建
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o myapp# 第二阶段
FROM scratchWORKDIR /appCOPY --from=builder /app/myapp /app/myapp
COPY --from=builder /app/cert /app/certENTRYPOINT ["/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-debian12Google Distroless 静态运行时镜像,无动态库纯静态构建(CGO_ENABLED=0),生产极简镜像无 shell、无调试工具、无动态库,调试需额外方法
gcr.io/distroless/base-debian12Distroless 带最小运行时(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/python3Distroless 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 扩展)。

快速对比表

对比项glibcmusl
体积较大(~2MB)较小(<1MB)
兼容性最好(闭源/老程序可直接跑)有些库不兼容
运行性能中(部分场景更快)
常用场景传统 Linux、企业生产容器、小镜像、静态构建
代表镜像debian, ubuntu, distroless/basealpine, 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 创始人和社区的一种幽默和特色,避免用普通的数字或复杂名字,让版本更有趣味。
  • 也便于记忆和区分不同版本。
http://www.dtcms.com/a/323312.html

相关文章:

  • 在Word和WPS文字中快速拆分、合并表格
  • 物联网之常见网络配置
  • 智能机票助手-接入Ollama本地模型-Spring-AI-Alibaba
  • 【Python 语法糖小火锅 · 第 2 涮】
  • 医院信息系统(HIS)的功能与应用详解
  • MySQL 元数据详细说明
  • RNN——LSTM(deep-learning)学习
  • Python自动化测试断言详细实战代码
  • BroadcastChannel:轻松实现前端跨页面通信
  • JavaWeb03——javascript基础语法
  • 嵌入式 Linux Mender OTA 实战全指南
  • 国家药品监督管理局医疗器械唯一标识管理信息批量导入mysql工具
  • 算法篇----模拟
  • 企业级高性能web服务器
  • 沿街晾晒识别误检率↓76%:陌讯多模态融合算法实战解析
  • VisionPro常用标定方式
  • 本科毕业论文怎么引用github里面数据集
  • Vue3从入门到精通: 2.2 Vue3组件通信与数据传递深度解析
  • AI热点周报(8.3~8.9):OpenAI重返开源,Anthropic放大招,Claude4.1、GPT5相继发布
  • 心灵笔记:正念冥想
  • imx6ull-驱动开发篇16——信号量与互斥体
  • SpringBoot学习日记 Day6:解锁微服务与高效任务处理
  • .NET程序跨平台ARM电脑上发布的程序格式是,so还是DLL?
  • AWT 基本组件深入浅出:Button/Label/TextField/Checkbox/Choice/List 全面实战与性能优化
  • GPT-4 vs GPT-5 深度分析
  • 逻辑回归详解:原理、应用与实践
  • n沟道增强型mos管
  • 支持 UMD 自定义组件与版本控制:从 Schema 到动态渲染
  • Beelzebub靶机通关教程
  • java 中 @NotBlank 和 @NotNull 的区别