Docker镜像定制:从配置Yum仓库到部署Nginx服务的完整实践

在容器化部署的实践中,很多开发者会遇到一个典型场景:基于一个基础镜像启动容器后,发现里面缺少必要的软件包管理工具(如yum)或特定的软件仓库,导致无法直接安装所需的服务。手动进入容器配置不仅繁琐,而且无法固化到镜像中,每次创建新容器都需要重复操作。本文将系统性地解决这个问题,手把手教你如何从零开始定制一个包含自定义 Yum 仓库的 Docker 镜像,并在此镜像基础上,完成一个服务(以 Nginx 为例)的安装与部署。整个过程将覆盖 Dockerfile 编写、镜像构建、仓库配置、服务安装、容器运行等核心环节,最终你将得到一个可复用的、开箱即用的服务镜像。

1. 核心概念与准备工作

在开始动手之前,我们需要明确几个核心概念,并准备好实验环境。

1.1 Docker 镜像与容器

Docker 镜像是用于创建 Docker 容器的只读模板。它包含了运行某个应用所需的所有内容:代码、运行时环境、库、环境变量和配置文件。你可以把它理解为一个应用程序的“安装包”或“快照”。

Docker 容器则是镜像的运行实例。你可以从同一个镜像启动多个容器,每个容器都是独立、安全的进程。容器与宿主机共享内核,但拥有独立的文件系统、网络和进程空间。

我们本次的目标,就是创建一个自定义的镜像,这个镜像不仅包含基础的操作系统,还预配置好了我们需要的软件源(Yum 仓库),并且预装了特定的服务。

1.2 Yum 仓库简介

Yum(Yellowdog Updater, Modified)是 RHEL、CentOS、Fedora 等 Linux 发行版上默认的包管理器。它通过访问配置好的软件仓库(Repository)来搜索、下载、安装、更新和删除软件包。

默认情况下,Docker 官方提供的 CentOS 等基础镜像,其内部的 Yum 仓库配置指向的是国外的官方源,在国内访问速度可能很慢甚至无法访问。因此,为容器内的 Yum 配置一个高速、稳定的国内镜像源(如阿里云、清华源)是提升构建效率的关键一步。

1.3 环境与工具准备

为了完成本教程,你需要准备以下环境:

  1. 操作系统:任何支持 Docker 的 Linux 发行版(如 Ubuntu 20.04/22.04, CentOS 7/8)、macOS 或 Windows 10/11(需启用 WSL2)。本文命令以 Linux/macOS 的 Bash 环境为例。
  2. Docker 引擎:确保已安装并运行 Docker。你可以通过以下命令验证:
    docker --version docker info
    如果未安装,请参考 Docker 官方文档或使用系统包管理器安装。对于生产环境,不建议使用一键安装脚本,应遵循官方的最佳实践进行安装。
  3. 文本编辑器:用于编写 Dockerfile 和配置文件,如 Vim、VS Code、Nano 等。
  4. 网络连接:需要能够访问互联网以下载基础镜像和软件包。

2. 定制基础镜像:编写第一个 Dockerfile

我们将从一个最基础的 CentOS 镜像开始,逐步添加我们的定制内容。

2.1 创建项目目录

首先,创建一个清晰的工作目录来管理所有相关文件。

mkdir -p ~/docker-custom-image && cd ~/docker-custom-image

2.2 编写 Dockerfile

Dockerfile 是一个文本文件,其中包含了一系列指令,用于指导 Docker 如何自动构建镜像。

在项目根目录下创建名为Dockerfile的文件:

vim Dockerfile

然后输入以下内容:

# 使用官方 CentOS 7 作为基础镜像 # 注意:CentOS 8 已停止维护,CentOS 7 也即将结束生命周期,生产环境请考虑 Rocky Linux/AlmaLinux 或 Ubuntu LTS。 # 这里使用 CentOS 7 仅作演示。 FROM centos:7 # 维护者信息(非必需,但建议保留) LABEL maintainer="your-email@example.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-centos

2.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。

在项目目录下创建一个新的子目录和 Dockerfile:

mkdir nginx-service && cd nginx-service vim Dockerfile

输入以下内容:

# 第二阶段:基于我们自定义的、带仓库的基础镜像 FROM my-centos:7-with-repo # 维护者信息 LABEL maintainer="your-email@example.com" # 设置工作目录(非必须,但好习惯) WORKDIR /usr/share/nginx/html # 1. 安装 Nginx # 由于基础镜像已配置好阿里云源,这里安装会很快 RUN yum install -y nginx # 2. 创建一个简单的自定义首页,覆盖 Nginx 默认页面 RUN echo "<h1>Hello from Custom Docker Image with Nginx!</h1><p>This 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-app

4. 进阶:使用多阶段构建优化镜像

上面的例子分成了两个镜像构建步骤,清晰但产生了中间镜像。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 "<h1>Multi-stage Build Nginx</h1>" > /usr/share/nginx/html/index.html # 第二阶段:最终镜像 FROM centos:7 # 从 builder 阶段复制已安装的 Nginx RPM 数据库和文件(简化示例,实际更复杂) # 注意:直接复制二进制文件可能缺少依赖。更常见的多阶段用于编译型语言。 # 对于 yum 安装,更简单的方式是直接在最终阶段运行安装命令。 # 这里为了演示,我们换一种思路:在最终阶段换源并安装。 # 复制 builder 阶段换源后的仓库配置文件 COPY --from=builder /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/ # 在最终阶段基于换好的源安装 Nginx RUN yum install -y nginx && \ yum clean all # 复制 builder 阶段生成的首页 COPY --from=builder /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 "<h1>Optimized 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.CMDENTRYPOINT指定的命令执行失败。
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-cmdufw)。
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 镜像更安全、高效和可维护。

  1. 使用特定版本的基础镜像: 避免使用latest标签,因为它会变化,导致构建行为不可预测。始终指定稳定版本,如centos:7.9.2009nginx:1.24-alpine

    FROM centos:7.9.2009
  2. 合并 RUN 指令,清理缓存: 尽可能将多个RUN指令合并为一个,用&&连接,并在最后清理包管理器缓存,这能减少镜像层数和最终体积。

    RUN yum update -y && \ yum install -y package1 package2 && \ yum clean all && \ rm -rf /var/cache/yum
  3. 使用.dockerignore文件: 在构建上下文目录创建.dockerignore文件,排除不需要的文件(如日志、git目录、临时文件),可以加速构建过程并避免将敏感文件意外打入镜像。

    **/.git **/.log **/tmp Dockerfile* docker-compose* README.md
  4. 以非 root 用户运行容器(安全): 默认情况下,容器内的进程以 root 用户运行。为了降低安全风险,应在 Dockerfile 中创建并使用非 root 用户来运行应用。

    RUN groupadd -r appuser && useradd -r -g appuser appuser USER appuser CMD ["nginx", "-g", "daemon off;"]

    注意:如果以非 root 用户运行,需要确保该用户对必要的目录(如日志目录、临时目录)有写权限。

  5. 合理使用COPYADDCOPY用于复制本地文件到镜像。ADD功能类似,但增加了自动解压和从 URL 下载的功能(不推荐从 URL 下载,因为构建缓存无效)。在大多数情况下,优先使用COPY

  6. 为镜像打上明确的标签: 使用有意义的标签,如版本号、构建日期或 Git 提交哈希。

    docker build -t myapp:1.0.0 -t myapp:latest .
  7. 扫描镜像安全漏洞: 使用docker scan命令(或集成到 CI/CD 中)对构建的镜像进行安全漏洞扫描。

    docker scan my-nginx:custom
  8. 生产环境考虑

    • 日志:确保应用日志输出到stdoutstderr,这样可以通过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 开始了。