第4章 FROM指令:基础镜像选型实战

写Dockerfile,第一行就是FROM。它决定了你镜像的“祖宗”。

我个人习惯,选基础镜像永远是第一步。选错了,后面折腾死。我见过一个团队用Ubuntu跑Go应用,最后镜像1.2GB,部署都费劲。换Alpine后,直接降到150MB。

今天我把四种主流基础镜像的情况拆开讲。每种我都踩过坑,希望能帮你省点时间。

FROM指令:镜像的起点

FROM指定了构建层基于哪个父镜像。语法很简单:

FROM <image>[:<tag>] [AS <name>]
  • image:仓库名,比如alpine、ubuntu。
  • tag:版本标签,默认latest。但我建议永远写明确版本,否则哪天latest更新了,你的构建可能就坏了。
  • AS name:给阶段起个名,多阶段构建时用。

小经验:我习惯把FROM和ARG搭配使用,比如ARG BASE_IMAGE=alpine:3.19,然后在CI里按环境换基础镜像,非常灵活。

四种基础镜像,你选对了吗?

下面这张图是我自己总结的选择逻辑,先扫一眼:

基础镜像选择决策图 Dockerfile FROM ? Alpine 5MB / musl libc Ubuntu glibc / 大生态 / 120MB+ Debian 稳定 / 包多 / slim版 Distroless 仅运行时 / 零依赖 关键权衡因素 • 镜像大小 vs 功能完整性 • 安全攻击面 • 调试便利性

接下来,逐一说透。

1. Alpine —— 小,但有坑

Alpine基于musl libc和BusyBox,镜像才5MB,香得很。

优点:小、安全更新快、docker官方推荐。

缺点:musl libc和glibc行为有差异。我遇到过编译出来的C程序在Alpine上core dump,一查是musl的栈大小限制和glibc不一样。

# Dockerfile 示例
FROM alpine:3.19
RUN apk add --no-cache python3 py3-pip
COPY . /app
WORKDIR /app
CMD ["python3", "app.py"]

避坑:如果你用Python的manylinux包,有些wheel依赖glibc,Alpine上装不了。要么换基础镜像,要么用--no-cache从源码编译。

我曾经在CI里直接用了alpine:latest,结果某天pycrypto在musl下编不过,整个发布挂了两小时。从那以后,我定死了tag:alpine:3.19。

2. Ubuntu —— 最省心的选择

Ubuntu基于glibc,生态最全。apt-get里基本什么都有。

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    python3 \
    python3-pip \
    && rm -rf /var/lib/apt/lists/*
COPY . /app
CMD ["python3", "app.py"]

但问题也明显:镜像大。纯Ubuntu 22.04就77MB,装上包轻松上300MB。

什么时候用:你的依赖全是glibc的,或者需要复杂调试环境。我一般只在内部工具镜像用Ubuntu,对外发布的统一走Alpine或Distroless。

3. Debian —— 稳定压到一切

Debian的稳定版更新极慢,包也都经过严格测试。很多基础镜像(比如Python官方镜像)的slim版就是基于Debian。

FROM python:3.12-slim-bookworm
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
CMD ["python", "app.py"]

注意:Debian也有多个版本,bookworm、bullseye。我建议用slim变体,剪掉了许多不必要的包。

我的习惯:Java应用我选Debian slim,因为JVM对musl支持一般,Ubuntu又太大。Debian slim在体积和兼容性之间取了个不错的平衡。

4. Distroless —— 极致安全,但调试痛苦

Distroless镜像由Google维护,里面只有你的应用和运行时,没有shell、没有包管理器、没有ls。攻击面最小,适合生产。

FROM gcr.io/distroless/python3-debian12
COPY app /app
WORKDIR /app
CMD ["app.py"]

但:一旦应用崩了,你想进去看日志?没门。连apt-get都没,装不了任何工具。

我的做法:多阶段构建。第一阶段用Debian全包,编译、测试;第二阶段用Distroless只丢编译产物进去。这样既小又安全,但启动后崩溃就麻烦了——所以我会在代码里打足日志,并且配好健康检查。

镜像对比速查表

特性 Alpine Ubuntu Debian slim Distroless
基础大小 ~5 MB ~77 MB ~45 MB ~20 MB
libc musl glibc glibc glibc
包管理器 apk apt apt 无
安全性 中 低(包多) 中 高
调试友好度 中(有sh) 高 高 低(无shell)
适用场景 轻量应用 / Go / Rust 开发环境 / 内部服务 Java / Python / 通用 生产 / 安全敏感

选择决策流程

我一般在写Dockerfile前,先问自己三个问题:

  1. 应用需要glibc吗? 是→Ubuntu/Debian;否→Alpine。
  2. 生产环境需要最小攻击面吗? 是→Distroless;否→继续考虑。
  3. 后续会频繁调试吗? 是→Debian slim或Ubuntu;否→Alpine。

我个人的黄金组合:开发用Debian slim(调试方便),CI/CD测试用Alpine(速度快),生产用Distroless(安全)。三阶段构建,各取所长。

最后说一句规矩

无论选哪个,FROM必须写明确标签,不要用latest。我曾经一个线上服务因为Ubuntu 20.04自动更新到了22.04,导致ffmpeg依赖版本冲突,回滚了两次。从那以后,我团队里FROM的tag全部锁死,并且加了Dockerfile.dockerignore来防止意外的文件污染。

# 坏习惯
FROM ubuntu:latest

# 好习惯
FROM ubuntu:22.04

嗯,基础镜像选对了,后面的事就顺了。否则,你会在各种libc冲突、镜像膨胀、安全漏洞里反复折腾。希望今天讲的内容能帮你少走弯路。