第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里按环境换基础镜像,非常灵活。
四种基础镜像,你选对了吗?
下面这张图是我自己总结的选择逻辑,先扫一眼:
接下来,逐一说透。
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前,先问自己三个问题:
- 应用需要glibc吗? 是→Ubuntu/Debian;否→Alpine。
- 生产环境需要最小攻击面吗? 是→Distroless;否→继续考虑。
- 后续会频繁调试吗? 是→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冲突、镜像膨胀、安全漏洞里反复折腾。希望今天讲的内容能帮你少走弯路。