
1. 项目概述与需求拆解1.1 核心需求解析在手把手开始之前先把这个项目的本质说透。所谓用 Docker 构建自己的 CentOS 基础镜像很多人一听就懵Docker Hub 上官方 centos 镜像不是一拉就有的吗为什么还要自己构建这个问题问得很对也确实是我入行时最大的疑惑。当时我接手了一个内部项目要求所有服务必须跑在特定 CentOS 版本内核环境下但官方镜像仓库里的版本和内部安全基线差了几个补丁版本。更麻烦的是生产环境是内网隔离的Docker Hub 根本连不上。这时候唯一的出路就是自己做一个符合公司规范的基础镜像推到内网仓库让所有业务团队直接拉取使用。所以这个项目的核心价值有三个摆脱对公共镜像仓库的依赖满足内网离线部署要求。完全掌控镜像内容按需裁剪或增加软件包做安全加固和合规审计。理解 Docker 镜像的分层结构、构建机制和最佳实践为后续复杂镜像制作打地基。如果你是一个刚开始学 Docker、被各种镜像概念搞晕的初学者或者是一个需要在离线环境交付服务的运维/开发这个项目我强烈建议你从头到尾亲手走一遍。做完之后你会发现那些以前看不懂的 Dockerfile 指令、镜像分层原理、构建缓存机制全都串起来了。1.2 方案选型思考整个项目的技术路线我踩过不少坑后总结出来是这样的基础镜像选择上我推荐直接用 CentOS 官方的 rootfs 压缩包而不是用docker import去导入一个运行中的系统更不是用debootstrap那种为 Debian 系设计的工具。CentOS 项目本身提供了容器用的 rootfs 压缩包这是官方专门为容器场景打包的已经去掉了内核模块、固件等不需要的东西体积比完整系统小很多。构建方式上我会带你先做最原始也是最能帮助你理解镜像原理的docker import方式再做一次基于 Dockerfile 和docker build的标准方式。两条路都走一遍你对镜像的理解会彻底打通。实际上官方仓库里那些镜像本质上就是根据一个 Dockerfile 自动构建出来的而 Dockerfile 的第一行FROM scratch往往就是从一个 rootfs 导入的分层开始的。工具链方面只需要一台能装上 Docker 的 Linux 机器或者 Windows/Mac 上的 Docker Desktop。我自己的测试环境是 CentOS 7.9 宿主机加 Docker 20.10 版本。如果你在 Windows 上使用 Docker Desktop需要注意后面我会讲到的文件解压和路径转义问题。2. 构建前的准备工作2.1 Docker 环境检查与安装这个步骤看似基础但真的很多人栽在这里。我见过同事在没装 Docker 的机器上浪费了半天也见过 Docker 服务没启动就报各种莫名其妙的错误。先做一次体检# 检查 Docker 是否已安装 docker --version # 检查 Docker 服务状态 systemctl status docker # 检查能否正常调用 Docker 引擎 docker info如果在 CentOS 宿主机上还没装过 Docker直接使用官方安装脚本是最快的注意需要在能联网的机器上执行curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo systemctl enable --now docker安装完成后立刻验证docker run hello-world能跑通说明引擎没问题。如果你是在 Windows 上用 Docker Desktop容易踩的坑是 Hyper-V 或 WSL 2 没有开启导致 Docker Desktop 启动后一直转圈报一些含 Virtualization support 之类的错误。处理方案进 BIOS 开启 CPU 虚拟化然后在启用或关闭 Windows 功能里勾选虚拟机平台和适用于 Linux 的 Windows 子系统重启后再试。Mac 用户相对省心装好 Docker Desktop 后注意把内存调大一点即可默认 2GB 在构建偶尔会不够用。2.2 获取 CentOS 官方 rootfs 压缩包这是整个项目最关键的基础材料。CentOS 官方为了便于容器化在 yum 源里发布了一个名为centos-container的软件包里面就包含了构建容器基础镜像的根文件系统压缩包。有两种获取方式我逐个说清楚。方式一直接从 CentOS 官方源下载。如果你是 CentOS 7.9 版本直接去 CentOS 官方的 Vault 归档目录找如果是 CentOS 8 或 Stream 9路径稍有不同。不过最省事的方式是直接通过 yum 下载这个软件包# 在一台能联网且有 yum 的 CentOS 机器上执行 yum install -y centos-container # 装完后rootfs 压缩包在 /var/lib/centos-container/ 目录下 ls -lh /var/lib/centos-container/这个操作会自动拉取指定版本的 container 镜像源得到的文件像CentOS-7-x86_64-rootfs.tar.xz或者类似带容器标识的 tar 包。实测下来这个文件大概几十MB比完整 ISO 几个GB要轻量太多。方式二从 Docker Hub 的官方仓库中把已有镜像导出。如果你的机器能联网 Docker Hub最简单的方式是直接把官方 centos 镜像 pull 下来然后用docker export导出它当前的文件系统docker pull centos:7.9.2009 docker create --name tmp-centos centos:7.9.2009 docker export tmp-centos | gzip centos7.9-rootfs.tar.gz docker rm tmp-centos这种方式导出的 tar 包和官方 rootfs 包在文件内容上基本一致但多了一些镜像运行时生成的临时文件比如/etc/hostname、/etc/resolv.conf之类的占位文件。这并不影响作为基础镜像使用只是我个人更推荐直接用官方 rootfs 包因为它是纯净的、专为容器设计的。提示如果你面向的是内网离线交付场景建议先在有外网的环境把 rootfs 包下载好刻盘或上传到内网文件服务器后续所有操作全部在内网机器的 Docker 里完成不依赖任何公共仓库。3. 用 docker import 构建 God 镜像3.1 解压与导入拿到 rootfs 压缩包以后就进入了真正的构建环节。先用docker import方式走一遍全流程因为这是理解镜像本质的最快路径。# 解压 rootfs 压缩包 mkdir /data/centos-rootfs tar -xf CentOS-7-x86_64-rootfs.tar.xz -C /data/centos-rootfs # 查看目录结构 ls /data/centos-rootfs/正常情况下你应该看到bin/ dev/ etc/ home/ lib/ lib64/ media/ mnt/ opt/ proc/ root/ run/ sbin/ srv/ sys/ tmp/ usr/ var/这些熟悉的系统目录。这其实就是一套完整的根文件系统没有内核没有 Bootloader因为这些在容器里都不需要——容器共享宿主机的内核。接着把它导入 Dockercd /data # 注意这里用 tar 打包而不是直接 import 目录 tar -C centos-rootfs -cf centos71.tar . # 导入为镜像指定仓库名和标签 docker import centos71.tar my-centos:7.1导入完成后用docker images查看就能看到一个my-centos:7.1镜像了。这时候怀着激动的心情跑一个交互式容器docker run -it --rm my-centos:7.1 /bin/bash看到那个[root容器ID /]#提示符的时候你的第一个 CentOS 容器就跑起来了。--rm参数表示容器退出后自动删除适合这种临时验证场景。3.2 容器内初始配置设置时区、yum 源与基础软件进入容器之后第一件事就是检查系统的基本状态cat /etc/os-release df -h这时你会发现根目录大概只有几百 MB 或 1GB 的磁盘空间这很正常因为容器默认的根文件系统磁盘容量上限是继承镜像的 overlay2 存储驱动限制的实际可用空间取决于 Docker 安装时的存储配置。如果要扩容可以在docker run时指定--storage-opt size20G这个我在后面的 FAQ 里细说。接下来是所有操作里最容易踩坑的一步配置 yum 源。CentOS 7 的官方源已经从 mirrors.centos.org 迁移到了 vault。如果你使用官方 rootfs 包里面的 BaseOS、Extras、Updates 源地址可能已经失效所以必须重置。推荐直接改用国内镜像源cd /etc/yum.repos.d/ mkdir backup mv *.repo backup/ cat CentOS-Base.repo EOF [base] nameCentOS-7 - Base baseurlhttps://mirrors.aliyun.com/centos/7/os/x86_64/ gpgcheck0 [extras] nameCentOS-7 - Extras baseurlhttps://mirrors.aliyun.com/centos/7/extras/x86_64/ gpgcheck0 [updates] nameCentOS-7 - Updates baseurlhttps://mirrors.aliyun.com/centos/7/updates/x86_64/ gpgcheck0 EOF yum clean all yum makecache然后安装一些常用基础软件也就是俗称的加一点料yum install -y vim net-tools tree lsof tcpdump wget curl unzip zip这些工具后面排查问题、看日志、抓包都会用到。注意容器里默认没有 systemd 作为 1 号进程所以systemctl是不能直接用的这是一个和传统虚拟机使用体验差异很大的地方。如果你确实需要在容器里跑 systemd 来做多进程服务管理需要给容器额外配置特权模式和挂载特定目录这个后面单独开一节讲。配置完成后退出容器这里有个关键操作docker export的镜像是不包含容器配置的所以你刚才修改的文件系统内容会被保留而启动命令、工作目录、环境变量这类元数据则需要在docker import时通过参数指定。执行exit # 先保存容器为新镜像 docker commit \ -c ENV LANGen_US.UTF-8 \ -c CMD [/bin/bash] \ 现有容器名 my-centos:7.1-config其实更常见的做法是不用docker commit而是重新把容器 export 成 tar 再 import但 commit 方式适合你已经跑了容器、想保留运行状态的场景。不过对基础镜像来说我更推荐下面的 Dockerfile 方案。4. 用 Dockerfile 构建标准化基础镜像4.1 Dockfile 设计思路与指令解析用docker import做完一轮你对镜像的分层和文件系统应该已经有直观感受了。但实际工作中没有人会用 import 来管理镜像版本——因为太不可控了。Dockerfile 之所以成为标准核心在于它把镜像的构建过程代码化了可以提交到 Git 仓库里做版本管理和 CI/CD。直接看我最终用的这个 Dockerfile我会逐步解释每一条# 第一阶段从 scratch 构建 FROM scratch # 将 rootfs 解压到镜像根目录 ADD CentOS-7-x86_64-rootfs.tar.xz / # 设置环境变量 ENV LANGen_US.UTF-8 \ LC_ALLen_US.UTF-8 \ PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # 设置容器默认工作目录 WORKDIR /root # 设置元数据 LABEL maintaineryournameexample.com \ descriptionCustom CentOS 7.9 base image for internal use \ version7.9.2009 # 容器启动后默认执行的命令 CMD [/bin/bash]先解释一个很多人会忽略的点FROM scratch是什么意思scratch是 Docker 保留的一个特殊空白镜像表示从零开始。然后ADD CentOS-7-x86_64-rootfs.tar.xz /这行是精华所在——ADD指令会自动检测到这是一个 tar 压缩包并帮我们解压到镜像的根目录/。这一点和COPY指令有本质区别COPY不会自动解压。ENV设置环境变量时LANG和LC_ALL是控制终端编码的如果你不想在容器里看到中文乱码或者想确保一些 Python/Java 程序按 UTF-8 输出日志这两行必须加。PATH则确保后续docker exec执行命令时能直接找到可执行文件。WORKDIR /root设置了进入容器后的默认目录。LABEL用来做镜像的元数据标记这个在内网镜像仓库里特别有用审计的时候能直接看到是哪个团队、哪个版本、基于什么构建的。最后的CMD [/bin/bash]声明容器默认启动的进程。这里必须用 JSON 数组形式不能用CMD /bin/bash这种 shell 形式否则实际启动时它会包一层/bin/sh -c对进程信号的处理会有细微差异影响优雅退出。4.2 两条构建路线的差异对比看到这里你可能会问docker import出来的镜像和 Dockerfile 构建出来的镜像到底有什么区别我直接说结论分层结构上docker import生成的镜像只有一层任何修改都会在后续 commit 时产生新的完整层镜像体积可能会暴涨Dockerfile 的ADD解压会从 tar 包内容创建一层后续的ENV、LABEL、CMD都是独立的 meta 层结构更清晰。可重建性上Dockerfile 是文本文件可以随时改随时构建版本可回溯import 出来的镜像如果你忘记当时做了什么配置只能一层层登录上去看几乎是不可审计的。可缓存性上Dockerfile 的每一层都有缓存 ID。只要你没改ADD那行哪怕后面反复修改ENV、CMD重新构建时都只会重建受影响层秒级完成。而 import/commit 每次都全部打包非常慢。所以如果你只是临时做个离线环境验证用 import 足够了但只要是正式交付给团队务必用 Dockerfile 把它固化下来。4.3 基于官方 centos 镜像的 Dockerfile 写法还有一种非常务实的写法就是直接以官方 centos 镜像为基础在上面做个性化定制而不是从 scratch 开始。这种方式适合你已经能连上 Docker Hub 或内网镜像仓库的情况FROM centos:7.9.2009 ENV LANGen_US.UTF-8 \ LC_ALLen_US.UTF-8 # 更换 yum 源为国内镜像 RUN rm -rf /etc/yum.repos.d/*.repo \ curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo \ yum clean all \ yum makecache # 安装常见工具 RUN yum install -y vim net-tools tree lsof tcpdump wget curl unzip zip \ yum clean all CMD [/bin/bash]这种写法和 scratch 版本的根本区别在于它复用了官方镜像已经做好的所有底层配置你只需要关注增量部分。yum clean all这一步我强烈建议每一层都执行一次它能把 yum 缓存从镜像层中清除显著减小最终镜像体积。注意Dockerfile 里的每个RUN都会增加一层镜像。所以能合并的指令尽量合并成一个RUN用连接这样可以减少层数既减小体积又加快构建。不过也不要过度合并如果一个 RUN 里内容太长失败后定位问题会很痛苦。执行构建docker build -t my-centos:7.9 .-t指定新的仓库名和标签最后的.表示 Dockerfile 所在目录。构建完再用docker run -it --rm my-centos:7.9 /bin/bash验证。5. 镜像瘦身与最佳实践5.1 为什么你的镜像比别人的大一圈在构建基础镜像时体积是最直观的一个指标。很多人发现自己构建出来的镜像比官方镜像大出几百 MB这里我分享几个主要的体积“吞金兽”。第一是安装源缓存。yum install之后所有下载的 rpm 包都缓存在/var/cache/yum/目录下。如果不清理直接构建成镜像这些缓存就会永久待在镜像层里。解决办法很简单每个RUN最后执行yum clean all。第二是临时文件。比如vim的 swap 文件、man手册页、文档、locate数据库等这些对运行环境来说完全没用。我习惯在安装完后执行一条清理命令yum remove -y vim-minimal bash-completion yum clean all rm -rf /var/cache/yum/* rm -rf /tmp/*第三是/usr/share/doc和/usr/share/man下的说明文档。这些加起来能省 30~50MB对服务器镜像来说完全没必要保留。rm -rf /usr/share/doc/* /usr/share/man/* /usr/share/info/*做完这三步清理镜像体积一般能缩小 20% 35%。我实测一个安装了 vim、curl、wget、lsof、tcpdump 的 CentOS 7.9 基础镜像清理后从 450MB 降到了 280MB 左右。5.2 常用 Dockerfile 瘦身技巧除了上面说的清理还有几个技巧属于用了就回不去的那种。技巧一--no-install-recommends类型的参数在 CentOS 下不适用但 CentOS 可以用--setopttsflagsnodocs来跳过文档安装yum install -y --setopttsflagsnodocs vim curl wget技巧二如果只是要某个命令不要装整个包。比如查端口占用不需要装完整的 net-tools用ss命令就够了它自带在iproute2里。再比如想测试 TCP 连通性不一定非要 telnet 或 nc用 bash 自带的/dev/tcp/特性即可timeout 3 bash -c echo /dev/tcp/127.0.0.1/3306 echo ok || echo fail技巧三把经常变的内容放在 Dockerfile 后面把不常变的内容放前面利用构建缓存。比如yum install那层可以放在 Dockerfile 很靠上的位置除非换基础镜像否则不管后面怎么改构建时都会命中缓存几秒就完成。这块在实际 CI 流水线中特别重要能大幅节省构建时间和流量。技巧四如果内网环境没有 Docker Hub建议在构建完镜像后直接导出成一个离线 tar 包docker save -o my-centos-7.9.tar my-centos:7.9到另一台机器上加载docker load -i my-centos-7.9.tarsave和export的区别我放到后面的 FAQ 里解释。5.3 容器内配置 systemd 的可选方案前面我提到容器里默认不能运行systemd但如果你的服务部署确实依赖 systemd比如需要systemctl start nginx这种管理方式这里给一个可行的替代方案在 Dockerfile 里安装 systemd 相关软件包并且在启动容器时特殊处理。Dockerfile 中安装RUN yum install -y systemd yum clean all启动容器时docker run -itd \ --name test-systemd \ -v /sys/fs/cgroup:/sys/fs/cgroup:ro \ --tmpfs /run \ --tmpfs /tmp \ --privileged \ my-centos:7.9 \ /sbin/init这里解释几个关键点--tmpfs /run和--tmpfs /tmp把这两个目录挂为内存文件系统避免 systemd 因 /run 不是 tmpfs 而启动失败。-v /sys/fs/cgroup:/sys/fs/cgroup:ro把宿主机的 cgroup 控制组挂进容器systemd 需要它来管理系统服务。--privileged提供容器内完整的权限包括挂载、设备访问等。说实话这个方案适合迁移遗留系统到容器时的过渡场景。新项目我还是建议拥抱容器原生方式一个容器一个服务用脚本或者 supervisor 管理。6. CI/CD 流水线中的镜像构建与自动化交付6.1 GitLab CI 中构建 Docker 镜像做完基础镜像自然要考虑怎么把它接入到日常的 CI/CD 流程里。因为开发环境可以手动 build但线上发布必须有自动化的入口。这里我给出 GitLab CI 的经典配置。假设项目结构如下. ├── Dockerfile ├── .gitlab-ci.yml └── rootfs/ └── CentOS-7-x86_64-rootfs.tar.xz.gitlab-ci.yml的核心片段stages: - build - push variables: IMAGE_TAG: $CI_COMMIT_SHORT_SHA build: stage: build image: docker:20.10 services: - docker:20.10-dind script: - docker info - docker build -t $CI_REGISTRY_IMAGE:$IMAGE_TAG . - docker tag $CI_REGISTRY_IMAGE:$IMAGE_TAG $CI_REGISTRY_IMAGE:latest only: - main这段配置里最关键的是services: docker:20.10-dind。它启动了一个docker 中的 docker服务让 CI Runner 里的docker build可以真正执行否则会报找不到 docker daemon。如果你们用的是宿主机共享的 Docker socket也可以在 runner 里配置DOCKER_HOSTunix:///var/run/docker.sock但安全性上要谨慎尽量避免把宿主机的 Docker socket 暴露给任意 CI 任务。实际部署中我还见过不少团队在构建时遇到Get https://registry-1.docker.io...: dial tcp ... i/o timeout这种错误。原因是 CI 机器无法访问外网或者 Docker Hub 被限速。解决办法给 Runner 配置镜像加速器或把基础镜像下载到内网仓库后在 Dockerfile 里把FROM改成内网地址。6.2 镜像推送到私有仓库与版本管理构建出来的镜像不能一直挂在本地机器上不然团队其他人根本无法拉取。生产环境的标准做法是推送到内网镜像仓库按官方说法叫 registry最常见的开源实现是 Harbor 或自建 Registry。推送到 Harbor 的完整命令docker login harbor.example.com -u admin -p yourpass docker tag my-centos:7.9 harbor.example.com/library/my-centos:7.9 docker push harbor.example.com/library/my-centos:7.9推完之后其他机器上拉取docker pull harbor.example.com/library/my-centos:7.9如果 harbor 配置的是 HTTP 协议而不是 HTTPS需要在 Docker daemon 配置里加白名单否则会报 HTTPS 证书错误。编辑/etc/docker/daemon.json{ insecure-registries: [harbor.example.com] }然后重启 Dockersystemctl restart docker。版本管理方面我建议使用项目名-系统版本-构建日期或项目名-系统版本-git sha这种可追溯的命名方式。比如my-centos-7.9-20240615。同时把latest标签指向最新稳定版本方便不了解情况的新同事直接使用。7. 常见问题与排查技巧实录7.1 构建与运行时的高频问题速查这一节直接把我在实操中遇到的、以及身边同事踩过的高频问题整理成速查表方便你遇到问题直接对号入座。问题现象可能原因解决方案docker build时提示failed to fetch metadata网络问题Dockerfile 里 curl/wget 下载源超时使用国内镜像源或提前将需要的 rpm 包下载到本地做离线源docker run -it my-centos:7.9 /bin/bash后没有任何输出镜像中/bin/bash不存在或权限不对用docker run --entrypoint ls my-centos:7.9 /bin/检查是否有 bashyum install报Could not resolve host: mirrors.aliyun.com容器内 DNS 或网络配置异常docker run --dns 223.5.5.5指定阿里 DNS检查宿主机能否访问该域名容器内修改文件后docker restart内容丢失持久化数据未挂载 volume启动时加-v /宿主机路径:/容器路径或使用docker commit保存变更docker import的镜像无法systemctl start服务容器内没有 systemd 作为 init 进程按 5.3 节方案处理或改用 supervisor业务服务直接前台启动镜像推送私有仓库报http: server gave HTTP response to HTTPS client私有仓库是 HTTP 协议在/etc/docker/daemon.json中配置insecure-registries并重启 Docker容器内根目录磁盘空间只有 10GB扩容无效overlay2 存储驱动限制了默认 size启动时加--storage-opt size50G或在 dockerd 配置里设置size默认值7.2 遇到 yum 源失效的处理流程CentOS 7 官方源迁移后很多人一进容器执行yum makecache就报错报的还不是超时而是 404。这里我贴一个标准的处理流程# 1. 备份原始源文件 cd /etc/yum.repos.d mkdir backup mv *.repo backup/ # 2. 写一个最小可用的 aliyun 源 cat CentOS-Base.repo EOF [base] nameCentOS-$releasever - Base baseurlhttps://mirrors.aliyun.com/centos/$releasever/os/x86_64/ gpgcheck0 [extras] nameCentOS-$releasever - Extras baseurlhttps://mirrors.aliyun.com/centos/$releasever/extras/x86_64/ gpgcheck0 [updates] nameCentOS-$releasever - Updates baseurlhttps://mirrors.aliyun.com/centos/$releasever/updates/x86_64/ gpgcheck0 EOF # 3. 如果容器内没有 vim 也没关系用 cat 重定向即可。 # 4. 清理并重建缓存 yum clean all yum makecache如果连 aliyun 也超时说明容器对外访问受限。此时先用宿主机测试curl -I https://mirrors.aliyun.com如果宿主机通而容器不通重点检查 Docker 的 iptables 规则和 /etc/resolv.conf。常见原因是防火墙或公司网络策略拦截了 Docker 网桥流量或者 DNS 配置错误。7.3 构建过程中网络不佳的离线替代方案内网构建镜像最痛苦的就是等下载。这里分享一个我常用的曲线救国思路在能联网的机器上先把所有需要的 rpm 包下载到本地再到内网机器上用本地路径或本地 http 服务做 yum 源。下载 rpm 包mkdir /data/centos7-rpms yum install --downloadonly --downloaddir/data/centos7-rpms vim curl wget net-tools把整个/data/centos7-rpms目录拷贝到内网机器后用 createrepo 构建本地源。如果内网机器有网络资源可以直接用 Python 起一个临时 http 服务cd /data/centos7-rpms python -m SimpleHTTPServer 8000然后 Dockerfile 里的 yum 源就可以指向它RUN yum -y install --disablerepo* --enablerepolocal -x vagrant \ -x rhn-client-tools -x rhnlib -x subscription-manager \ --nogpgcheck \ http://192.168.1.10:8000/不过这个做法是把整个 rpm 目录当成一个源来用需要你提前把所有依赖包都下载完整否则安装时还是会有依赖报错。更稳妥的是在联网机器上用yumdownloader --resolve把依赖一起下载下来再整个目录传过去。7.4 容器根目录扩容实操前面提到容器默认根目录空间可能只有 10GB如果在容器里安装大型软件包或存放数据很快就满了。实测扩容方式如下在启动容器时指定 storage-optdocker run -it --storage-opt size50G my-centos:7.9 /bin/bash或者修改 Docker daemon 全局默认配置编辑/etc/docker/daemon.json加上{ storage-driver: overlay2, storage-opts: [ overlay2.size50G ] }然后重启 Docker之后创建的所有容器默认都有 50GB 的根目录空间。注意overlay2的 size 限制并不是对所有文件系统都生效如果宿主机本身用的就是 xfs 且开启 pquota这个参数才有效。如果你的宿主机文件系统不是 xfs可能即使指定了也无效这需要你在测试环境里先验证。8. 后续扩展与个人踩坑体会走到这一步一个属于自己的 CentOS 基础镜像已经能正常构建、发布和拉取了。最后再补充几个可以继续深入的方向也是我实际项目里做过的事。如果你要做 Java 服务的基础镜像可以在 Dockerfile 里继续叠加 OpenJDK要做 Python 应用可以把 pyenv 或 conda 里需要的 Python 版本整进层里。不过我更建议保持基础镜像尽量干净让业务镜像基于基础镜像去叠加运行时。一旦基础镜像变成大杂烩体积失控不说安全漏洞也难追踪。再提一个很多人容易忽略的点镜像的时区问题。默认 CentOS 的容器时区是 UTC你date看到的比北京时间慢 8 小时。虽然很多服务框架会自己读系统时区但因为测试环境和生产环境的差异常常会踩时间错乱的坑。最省心的做法就是在 Dockerfile 里直接固化时区RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone最后说点我自己的感受。做基础镜像这件事看起来只是一条 Dockerfile 的事但实际上它逼着我去把 Linux 根文件系统的结构、yum 源的原理、Docker 的分层存储机制全都过了一遍。以前我拉官方镜像用得理直气壮直到线上环境被人反馈镜像里怎么还有漏洞我才意识到基础镜像就是整个容器世界的地基地基不干净上面盖什么楼都心里发虚。如果你刚接触 Docker不妨也从构建一个自己的基础镜像开始。别怕踩坑yum 源失效、DNS 不通、systemd 起不来这些问题我全踩过一遍。等你把这些问题都解决完Docker 在你眼里就不再是几个命令行黑魔法而是一套清晰、可控、可扩展的体系。后面不管是要做 CI 流水线、离线交付还是多环境一致性部署你都比别人多一层底气。