在容器化部署的实践中很多开发者会遇到一个典型场景基于一个基础镜像启动容器后发现里面缺少必要的软件包管理工具如yum或特定的软件仓库导致无法直接安装所需的服务。手动进入容器配置不仅繁琐而且无法固化到镜像中每次创建新容器都需要重复操作。本文将系统性地解决这个问题手把手教你如何从零开始定制一个包含自定义 Yum 仓库的 Docker 镜像并在此镜像基础上完成一个服务以 Nginx 为例的安装与部署。整个过程将覆盖 Dockerfile 编写、镜像构建、仓库配置、服务安装、容器运行等核心环节最终你将得到一个可复用的、开箱即用的服务镜像。1. 核心概念与准备工作在开始动手之前我们需要明确几个核心概念并准备好实验环境。1.1 Docker 镜像与容器Docker 镜像是用于创建 Docker 容器的只读模板。它包含了运行某个应用所需的所有内容代码、运行时环境、库、环境变量和配置文件。你可以把它理解为一个应用程序的“安装包”或“快照”。Docker 容器则是镜像的运行实例。你可以从同一个镜像启动多个容器每个容器都是独立、安全的进程。容器与宿主机共享内核但拥有独立的文件系统、网络和进程空间。我们本次的目标就是创建一个自定义的镜像这个镜像不仅包含基础的操作系统还预配置好了我们需要的软件源Yum 仓库并且预装了特定的服务。1.2 Yum 仓库简介YumYellowdog Updater, Modified是 RHEL、CentOS、Fedora 等 Linux 发行版上默认的包管理器。它通过访问配置好的软件仓库Repository来搜索、下载、安装、更新和删除软件包。默认情况下Docker 官方提供的 CentOS 等基础镜像其内部的 Yum 仓库配置指向的是国外的官方源在国内访问速度可能很慢甚至无法访问。因此为容器内的 Yum 配置一个高速、稳定的国内镜像源如阿里云、清华源是提升构建效率的关键一步。1.3 环境与工具准备为了完成本教程你需要准备以下环境操作系统任何支持 Docker 的 Linux 发行版如 Ubuntu 20.04/22.04, CentOS 7/8、macOS 或 Windows 10/11需启用 WSL2。本文命令以 Linux/macOS 的 Bash 环境为例。Docker 引擎确保已安装并运行 Docker。你可以通过以下命令验证docker --version docker info如果未安装请参考 Docker 官方文档或使用系统包管理器安装。对于生产环境不建议使用一键安装脚本应遵循官方的最佳实践进行安装。文本编辑器用于编写 Dockerfile 和配置文件如 Vim、VS Code、Nano 等。网络连接需要能够访问互联网以下载基础镜像和软件包。2. 定制基础镜像编写第一个 Dockerfile我们将从一个最基础的 CentOS 镜像开始逐步添加我们的定制内容。2.1 创建项目目录首先创建一个清晰的工作目录来管理所有相关文件。mkdir -p ~/docker-custom-image cd ~/docker-custom-image2.2 编写 DockerfileDockerfile 是一个文本文件其中包含了一系列指令用于指导 Docker 如何自动构建镜像。在项目根目录下创建名为Dockerfile的文件vim Dockerfile然后输入以下内容# 使用官方 CentOS 7 作为基础镜像 # 注意CentOS 8 已停止维护CentOS 7 也即将结束生命周期生产环境请考虑 Rocky Linux/AlmaLinux 或 Ubuntu LTS。 # 这里使用 CentOS 7 仅作演示。 FROM centos:7 # 维护者信息非必需但建议保留 LABEL maintaineryour-emailexample.com # 设置工作目录后续的 RUN、COPY、CMD 等指令会以此目录为当前目录 WORKDIR /root # 1. 备份原有的 Yum 仓库配置文件 RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup # 2. 使用阿里云的 CentOS 7 镜像源替换默认源 # 这里我们直接使用 curl 下载仓库配置文件。如果基础镜像没有 curl可以先安装。 RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # 3. 清理旧的 Yum 缓存并生成新缓存 # 这能确保系统使用新的镜像源列表 RUN yum clean all yum makecache # 4. 可选安装一些常用的工具如 vim, wget, net-tools 等 RUN yum install -y vim wget net-tools telnet # 5. 声明容器运行时监听的端口例如 Nginx 的 80 端口 # 这只是一个声明方便使用者知道这个镜像会使用哪些端口。 EXPOSE 80 # 6. 设置容器启动时默认执行的命令 # 这里我们先启动一个 bash shell方便交互式检查。 CMD [/bin/bash]关键指令解释FROM: 指定基础镜像所有构建都基于此镜像。LABEL: 为镜像添加元数据如维护者信息。WORKDIR: 设置工作目录类似cd命令。RUN: 在构建镜像时执行的命令。每一条RUN指令都会创建一个新的镜像层。curl -o ...: 下载文件并保存到指定路径。yum clean all yum makecache: 这是一个经典组合。clean all清除所有缓存makecache根据当前仓库配置生成新的缓存确保后续安装使用新源。EXPOSE: 声明容器打算使用的端口这只是一个文档性质的声明不会自动映射到宿主机。CMD: 指定容器启动时默认运行的命令。一个 Dockerfile 中只能有一个CMD指令如果多个只有最后一个生效。这里我们让容器启动后进入 Bash方便我们后续操作。2.3 构建自定义基础镜像在Dockerfile所在目录执行构建命令docker build -t my-centos:7-with-repo .-t my-centos:7-with-repo: 为构建的镜像打上标签格式为名称:标签。这里我们命名为my-centos标签为7-with-repo。.: 指定构建上下文路径为当前目录Docker 引擎会将该目录下的所有文件发送给 Docker 守护进程。Dockerfile也默认从这个路径寻找。构建过程会依次执行 Dockerfile 中的指令下载基础镜像、执行 RUN 命令等。第一次构建可能会花费一些时间下载centos:7基础镜像。构建成功后可以使用docker images命令查看本地镜像列表应该能看到新构建的my-centos:7-with-repo镜像。docker images | grep my-centos2.4 验证镜像与仓库配置运行一个基于新镜像的容器并检查 Yum 仓库是否已成功替换为阿里云源。# 以交互模式运行容器并分配一个伪终端-it docker run -it --rm --name test-repo my-centos:7-with-repo容器启动后你会进入容器的 Bash Shell。执行以下命令验证# 查看 Yum 仓库列表 yum repolist # 或者查看具体的仓库配置文件内容 cat /etc/yum.repos.d/CentOS-Base.repo | head -20你应该能看到仓库的baseurl指向mirrors.aliyun.com。同时尝试安装一个软件比如之前没安装的tree来测试仓库是否可用yum install -y tree tree --version如果安装成功说明我们的自定义基础镜像包含国内 Yum 源已经制作完成。输入exit退出容器。由于我们使用了--rm参数退出后容器会自动删除但镜像保留。3. 在容器内安装与部署服务以 Nginx 为例现在我们有了一个“好用”的基础镜像。接下来我们要在这个镜像的基础上安装并配置一个具体的服务——Nginx Web 服务器并制作成最终的应用镜像。3.1 创建服务部署的 Dockerfile我们不再修改之前的Dockerfile而是采用更清晰的方式基于我们刚构建的my-centos:7-with-repo镜像创建一个新的、专门用于 Nginx 服务的 Dockerfile。在项目目录下创建一个新的子目录和 Dockerfilemkdir nginx-service cd nginx-service vim Dockerfile输入以下内容# 第二阶段基于我们自定义的、带仓库的基础镜像 FROM my-centos:7-with-repo # 维护者信息 LABEL maintaineryour-emailexample.com # 设置工作目录非必须但好习惯 WORKDIR /usr/share/nginx/html # 1. 安装 Nginx # 由于基础镜像已配置好阿里云源这里安装会很快 RUN yum install -y nginx # 2. 创建一个简单的自定义首页覆盖 Nginx 默认页面 RUN echo h1Hello from Custom Docker Image with Nginx!/h1pThis page is served from a container with custom Yum repo./p /usr/share/nginx/html/index.html # 3. 暴露 Nginx 默认的 HTTP 端口 EXPOSE 80 # 4. 启动 Nginx # 注意直接运行 nginx 命令容器会在前台运行。 # 使用 -g daemon off; 参数让 Nginx 在前台运行这是 Docker 容器的最佳实践。 CMD [nginx, -g, daemon off;]关键点说明镜像继承FROM my-centos:7-with-repo使得新镜像包含了之前所有的修改换源、安装工具。服务安装yum install -y nginx直接从配置好的阿里云源安装 Nginx无需额外配置。前台运行Docker 容器设计为在前台运行一个主进程。如果 Nginx 以守护进程daemon模式启动它会立即启动然后退出导致容器停止。-g daemon off;这个参数强制 Nginx 保持在前台运行。3.2 构建 Nginx 服务镜像在nginx-service目录下执行构建命令docker build -t my-nginx:custom .3.3 运行并测试 Nginx 容器构建完成后运行这个新的镜像。# 运行容器并将容器的 80 端口映射到宿主机的 8080 端口 docker run -d --name my-nginx-app -p 8080:80 my-nginx:custom-d: 以后台detached模式运行容器。--name my-nginx-app: 为容器指定一个名称便于管理。-p 8080:80: 端口映射格式为宿主机端口:容器端口。将宿主机的 8080 端口映射到容器的 80 端口。现在你可以在宿主机上打开浏览器访问http://localhost:8080或者使用curl命令测试curl http://localhost:8080你应该能看到我们自定义的 HTML 内容“Hello from Custom Docker Image with Nginx!”。3.4 管理容器查看容器运行状态和日志# 查看正在运行的容器 docker ps # 查看指定容器的日志 docker logs my-nginx-app # 进入正在运行的容器如果需要调试 # docker exec -it my-nginx-app /bin/bash停止和删除容器# 停止容器 docker stop my-nginx-app # 删除已停止的容器 docker rm my-nginx-app # 如果想强制删除正在运行的容器可以加 -f 参数 # docker rm -f my-nginx-app4. 进阶使用多阶段构建优化镜像上面的例子分成了两个镜像构建步骤清晰但产生了中间镜像。Docker 支持多阶段构建可以在一个Dockerfile中完成多个构建阶段并且只将最终需要的文件复制到最终镜像从而显著减小镜像体积。下面是一个结合了换源和安装 Nginx 的多阶段构建示例实际上本例单阶段即可此处用于演示概念# 第一阶段构建阶段用于准备环境或编译本例中用于换源和安装 FROM centos:7 AS builder # 换源操作 RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup \ curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo \ yum clean all yum makecache # 安装 Nginx 并创建首页 RUN yum install -y nginx \ echo h1Multi-stage Build Nginx/h1 /usr/share/nginx/html/index.html # 第二阶段最终镜像 FROM centos:7 # 从 builder 阶段复制已安装的 Nginx RPM 数据库和文件简化示例实际更复杂 # 注意直接复制二进制文件可能缺少依赖。更常见的多阶段用于编译型语言。 # 对于 yum 安装更简单的方式是直接在最终阶段运行安装命令。 # 这里为了演示我们换一种思路在最终阶段换源并安装。 # 复制 builder 阶段换源后的仓库配置文件 COPY --frombuilder /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/ # 在最终阶段基于换好的源安装 Nginx RUN yum install -y nginx \ yum clean all # 复制 builder 阶段生成的首页 COPY --frombuilder /usr/share/nginx/html/index.html /usr/share/nginx/html/ EXPOSE 80 CMD [nginx, -g, daemon off;]说明对于像 Nginx 这样直接通过包管理器安装的场景多阶段构建的优势不大因为安装过程本身就会在最终镜像中产生层。多阶段构建的真正威力在于编译环境和运行环境的分离。例如用一个包含 GCC 的大镜像编译 Go 程序然后将编译好的、不依赖编译环境的二进制文件复制到一个很小的alpine基础镜像中运行。对于我们的需求一个更简洁、高效的单阶段 Dockerfile 如下FROM centos:7 # 一次性完成所有系统级操作减少镜像层数 RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup \ curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo \ yum clean all yum makecache \ yum install -y nginx vim wget \ yum clean all \ echo h1Optimized Nginx Image/h1 /usr/share/nginx/html/index.html EXPOSE 80 CMD [nginx, -g, daemon off;]最佳实践将相关的RUN指令用连接起来并在最后清理缓存yum clean all这样可以减少镜像的层数并且清理掉安装包缓存让镜像体积更小。5. 常见问题与排查思路在定制镜像和运行容器的过程中你可能会遇到以下问题问题现象可能原因排查与解决思路docker build失败提示Failed to download metadata for repo1. 网络问题无法访问配置的镜像源。2. 镜像源配置文件错误或链接失效。3. 基础镜像版本与仓库配置不匹配如 CentOS 8 用了 CentOS 7 的 repo。1. 在容器内尝试curl -I测试网络连通性。2. 检查 Dockerfile 中curl下载的 repo 文件 URL 是否正确。3. 确保FROM指定的基础镜像版本与使用的.repo文件版本一致。容器启动后立即退出1. 容器内没有前台进程在运行。2.CMD或ENTRYPOINT指定的命令执行失败。1. 使用docker logs 容器名查看启动日志。2. 确保服务启动命令是前台运行模式如 Nginx 用daemon off;。3. 可以先用CMD [/bin/bash]或CMD [sleep, infinity]启动容器然后exec进入手动测试命令。宿主机无法访问容器的服务1. 容器端口没有映射到宿主机缺少-p参数。2. 宿主机防火墙阻止了端口访问。3. 容器内服务没有监听在0.0.0.0上。1. 检查docker run命令是否包含-p 宿主机端口:容器端口。2. 检查宿主机防火墙规则如firewall-cmd或ufw。3. 进入容器检查服务进程和监听地址netstat -tlnp。镜像体积过大1. 每一层RUN指令都产生了缓存或临时文件。2. 安装了不必要的软件包。1. 在RUN指令最后合并清理命令如yum clean all rm -rf /var/cache/yum。2. 使用多阶段构建只复制必要的文件到最终镜像。3. 考虑使用更小的基础镜像如alpine。COPY文件到容器内失败构建上下文中不存在要复制的文件。确保要COPY的文件或目录位于docker build命令指定的上下文路径.下。6. 最佳实践与工程建议遵循以下实践能让你的 Docker 镜像更安全、高效和可维护。使用特定版本的基础镜像 避免使用latest标签因为它会变化导致构建行为不可预测。始终指定稳定版本如centos:7.9.2009、nginx:1.24-alpine。FROM centos:7.9.2009合并 RUN 指令清理缓存 尽可能将多个RUN指令合并为一个用连接并在最后清理包管理器缓存这能减少镜像层数和最终体积。RUN yum update -y \ yum install -y package1 package2 \ yum clean all \ rm -rf /var/cache/yum使用.dockerignore文件 在构建上下文目录创建.dockerignore文件排除不需要的文件如日志、git目录、临时文件可以加速构建过程并避免将敏感文件意外打入镜像。**/.git **/.log **/tmp Dockerfile* docker-compose* README.md以非 root 用户运行容器安全 默认情况下容器内的进程以 root 用户运行。为了降低安全风险应在 Dockerfile 中创建并使用非 root 用户来运行应用。RUN groupadd -r appuser useradd -r -g appuser appuser USER appuser CMD [nginx, -g, daemon off;]注意如果以非 root 用户运行需要确保该用户对必要的目录如日志目录、临时目录有写权限。合理使用COPY和ADDCOPY用于复制本地文件到镜像。ADD功能类似但增加了自动解压和从 URL 下载的功能不推荐从 URL 下载因为构建缓存无效。在大多数情况下优先使用COPY。为镜像打上明确的标签 使用有意义的标签如版本号、构建日期或 Git 提交哈希。docker build -t myapp:1.0.0 -t myapp:latest .扫描镜像安全漏洞 使用docker scan命令或集成到 CI/CD 中对构建的镜像进行安全漏洞扫描。docker scan my-nginx:custom生产环境考虑日志确保应用日志输出到stdout和stderr这样可以通过docker logs查看便于集中日志收集。健康检查在 Dockerfile 中使用HEALTHCHECK指令或在docker run时使用--health-cmd参数让 Docker 引擎能够监控容器内服务的健康状态。资源限制使用-m、--cpus等参数为容器设置内存和 CPU 限制防止单个容器耗尽主机资源。通过本教程你不仅学会了如何定制一个带有自定义 Yum 仓库的 Docker 镜像还掌握了在容器内安装部署服务Nginx的完整流程并了解了多阶段构建、常见问题排查以及一系列生产级的最佳实践。这套方法可以推广到任何基于 Yum/DNF 的 Linux 发行版如 RHEL、Rocky Linux、AlmaLinux和任何需要通过包管理器安装的服务如 MySQL、Redis、Python 等。下次当你需要为一个特定环境准备 Docker 镜像时就可以从容地从编写 Dockerfile 开始了。