你是不是也遇到过这样的场景:在本地开发环境跑得好好的服务,一到测试或生产环境就各种依赖缺失、版本冲突?或者团队新成员入职,光是配环境就要花掉一整天?

这正是 Docker 要解决的核心痛点。它通过“集装箱”式的封装,将应用及其所有依赖打包成一个标准化的镜像,实现了“一次构建,处处运行”。但很多初学者在接触 Docker 时,往往止步于 docker run 一个现成的镜像,对于如何从零开始定制自己的镜像、如何在容器内部进行软件安装和配置,仍然感到无从下手。

这篇文章要解决的,正是这个从“会用”到“会做”的关键跨越。我们将聚焦两个核心实战技能: 如何基于一个基础镜像,定制一个包含自己服务的简单镜像 ,以及 如何在运行的容器内部,动态配置软件源(如 Yum 仓库)并安装部署服务 。这不仅是理解 Docker 分层构建和容器运行机制的关键,更是将 Docker 真正用于日常开发和部署的必备能力。

读完本文,你将能清晰地掌握 Dockerfile 编写、镜像构建、容器内软件管理的完整流程,并能够独立完成一个简单服务的容器化封装与部署。

1. 为什么需要定制镜像与容器内配置?

在深入操作之前,我们先明确这两个场景的价值,这决定了你是否需要深入学习。

场景一:定制简单镜像 当你需要部署一个自己编写的 Python 脚本、一个 Go 语言编译的二进制文件,或者一个需要特定系统依赖的 Java 应用时,直接使用官方的 python:alpine openjdk:11 镜像往往不够。你需要将自己的代码、配置文件“塞”进去,并可能安装额外的系统包。这个过程,就是通过编写 Dockerfile 来定制镜像。它的核心价值在于 固化环境 实现自动化部署 。镜像一旦构建成功,在任何装有 Docker 的环境中,都能以完全一致的方式启动你的服务。

场景二:为容器配置 Yum 仓库并安装服务 这个场景更偏向于 动态调试 快速验证 。想象一下,你拉取了一个非常干净的 centos:7 基础镜像并启动了一个容器,发现里面缺少 vim net-tools 甚至 wget 等调试工具。或者,你想在容器内临时安装一个服务(如 nginx )进行功能测试,但基础镜像的默认软件源速度极慢甚至不可用。这时,你就需要在运行的容器内部,手动配置一个高速的 Yum 仓库(如阿里云、清华源),然后安装所需软件。虽然最佳实践是 将这类依赖的安装固化到 Dockerfile 中 ,但掌握容器内的动态配置能力,对于问题排查、临时测试和学习理解容器内部机制至关重要。

简单来说: 定制镜像是为了交付最终产品,而容器内配置是为了过程中的调试与探索。 两者相辅相成,都是 Docker 能力矩阵中不可或缺的部分。

2. 核心概念快速理解

在动手之前,快速厘清几个容易混淆的概念:

  • 镜像 (Image) :一个只读的模板,包含了运行应用所需的文件系统、依赖和配置。你可以把它理解为一个“应用程序的安装包”或“系统的快照”。镜像是分层的,每一层都代表一次修改(如添加一个文件、运行一条命令)。
  • 容器 (Container) :镜像的一个运行实例。当你运行一个镜像时,Docker 会创建一个可写的容器层(称为“容器层”),叠加在只读的镜像层之上。所有对运行中系统的修改(如创建文件、安装软件)都发生在这个容器层中。 容器停止后,这个可写层默认会被丢弃。
  • Dockerfile :一个文本文件,包含了一系列用于构建镜像的指令(如 FROM , RUN , COPY , CMD 等)。 docker build 命令通过执行这些指令,自动生成镜像。
  • 仓库 (Repository) :这里有两个“仓库”概念需要区分:
    1. Docker 镜像仓库 (Registry) :如 Docker Hub、阿里云容器镜像服务,用于存储和分发 Docker 镜像。
    2. Linux 软件包仓库 (如 Yum Repository) :用于存储 RPM 软件包及其元数据, yum dnf 命令从这里下载并安装软件。本文中“配置 Yum 仓库”指的是后者。

关键关系 Dockerfile -> ( docker build ) -> 镜像 -> ( docker run ) -> 容器 。定制镜像修改的是左边的链条(持久化),而在容器内配置 Yum 仓库影响的是右边的单个容器实例(临时性)。

3. 环境准备与前置检查

为了完成后续所有操作,你需要一个可用的 Docker 环境。本文假设你使用的是 CentOS 7 或 Rocky Linux/AlmaLinux 等 RHEL 系发行版,这也是配置 Yum 仓库最典型的场景。

3.1 系统与内核要求

Docker 对系统内核版本有要求。打开终端,执行以下命令检查:

uname -r

确保输出内核版本 高于 3.10 。例如 3.10.0-1160.el7.x86_64 是符合要求的。

3.2 安装 Docker CE

如果你的系统尚未安装 Docker,可以参考以下最简步骤。这里采用国内常用的阿里云镜像源来加速安装。

  1. 卸载旧版本(如有)

    sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine
    
  2. 安装必要的依赖包

    sudo yum install -y yum-utils device-mapper-persistent-data lvm2
    
  3. 配置阿里云 Docker CE Yum 仓库

    sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
    

    这个命令会在 /etc/yum.repos.d/ 目录下添加一个 docker-ce.repo 文件,将软件源指向阿里云镜像。

  4. 更新 Yum 缓存并安装 Docker CE

    sudo yum makecache fast
    sudo yum install -y docker-ce docker-ce-cli containerd.io
    
  5. 启动 Docker 服务并设置开机自启

    sudo systemctl start docker
    sudo systemctl enable docker
    
  6. 验证安装

    sudo docker run hello-world
    

    如果能看到 “Hello from Docker!” 等欢迎信息,说明 Docker 已安装并运行正常。

  7. (可选)配置用户组 :为了避免每次使用 docker 命令都加 sudo ,可以将当前用户加入 docker 组。

    sudo usermod -aG docker $USER
    

    重要 :执行此命令后,你需要 完全退出当前终端并重新登录 ,或者新开一个终端,用户组变更才会生效。

至此,你的 Docker 环境已经准备就绪。

4. 实战一:定制一个简单的自定义镜像

我们的目标是创建一个简单的 Web 服务镜像。它基于 nginx 官方镜像,但将我们自定义的 HTML 页面替换掉默认的欢迎页。

4.1 创建项目目录与文件

首先,创建一个清晰的工作目录。

mkdir -p ~/docker-demo/custom-nginx
cd ~/docker-demo/custom-nginx

在该目录下,创建两个文件:

  1. Dockerfile :构建镜像的蓝图。
  2. index.html :我们自定义的首页。

4.2 编写自定义 HTML 页面

编辑 index.html 文件,内容如下:

<!DOCTYPE html>
<html>
<head>
    <title>My Custom Nginx in Docker</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding-top: 50px;
            background-color: #f0f0f0;
        }
        h1 {
            color: #333;
        }
        p {
            color: #666;
        }
    </style>
</head>
<body>
    <h1>🚀 Hello from My Custom Docker Image!</h1>
    <p>This page is served from a custom Nginx image built by me.</p>
    <p>Image build time: <span id="build-time"></span></p>
    <script>
        document.getElementById('build-time').textContent = new Date().toLocaleString();
    </script>
</body>
</html>

4.3 编写 Dockerfile

这是构建镜像的核心。在同一个目录下创建 Dockerfile (注意没有后缀名):

# 1. 指定基础镜像。我们使用官方的 nginx 镜像作为起点。
FROM nginx:alpine

# 2. 维护者信息(已弃用,但可作为元数据保留)
LABEL maintainer="your-email@example.com"

# 3. 将宿主机当前目录下的 index.html 文件,
#    复制到容器内 nginx 的默认网站根目录。
#    alpine 版 nginx 的默认根目录是 /usr/share/nginx/html
COPY index.html /usr/share/nginx/html/

# 4. (可选)暴露端口。nginx 镜像默认已暴露 80 端口,此处显式声明以示清晰。
EXPOSE 80

# 5. 容器启动时执行的命令。
#    nginx 镜像的默认 CMD 已经是启动 nginx,这里可以省略。
#    如果基础镜像没有,可以写:CMD ["nginx", "-g", "daemon off;"]

这个 Dockerfile 做了四件事:

  1. FROM :定义了构建的起点。 alpine 版本非常小巧。
  2. LABEL :为镜像添加元数据。
  3. COPY :将本地文件复制到镜像内的指定路径。这是“定制化”的关键步骤。
  4. EXPOSE :声明容器运行时监听的端口,是一个元数据,方便使用者知道。

4.4 构建自定义镜像

Dockerfile 所在的目录( ~/docker-demo/custom-nginx )执行构建命令:

docker build -t my-custom-nginx:1.0 .

命令解析:

  • docker build :构建镜像的命令。
  • -t my-custom-nginx:1.0 :为构建成功的镜像打上标签(Tag)。格式为 名称:版本 my-custom-nginx 是镜像名, 1.0 是标签。
  • . 这个点非常重要 ,它指定了构建上下文(Build Context)的路径。Docker 客户端会将这个路径下的所有文件(除了 .dockerignore 声明的)发送给 Docker 守护进程。 Dockerfile index.html 都需要在这个上下文中。

构建过程中,你会看到 Docker 逐层执行 Dockerfile 中的指令并输出日志。

4.5 运行并验证自定义镜像

构建成功后,运行这个自定义的容器:

docker run -d -p 8080:80 --name my-nginx-container my-custom-nginx:1.0

命令解析:

  • -d :后台运行(detached mode)。
  • -p 8080:80 :端口映射。将宿主机的 8080 端口映射到容器的 80 端口。
  • --name my-nginx-container :为容器指定一个名字,便于后续管理。
  • my-custom-nginx:1.0 :指定要运行的镜像及其标签。

现在,打开你的浏览器,访问 http://localhost:8080 (如果 Docker 运行在远程服务器,请替换 localhost 为服务器 IP)。你应该能看到我们自定义的 HTML 页面,而不是 Nginx 的默认欢迎页。

验证容器运行状态

docker ps

你应该能看到名为 my-nginx-container 的容器正在运行。

查看容器日志

docker logs my-nginx-container

停止并删除容器 (用于清理):

docker stop my-nginx-container
docker rm my-nginx-container

至此,你已经成功完成了第一个实战:从编写 Dockerfile 开始,构建了一个属于自己的 Docker 镜像,并运行了它。这个镜像 my-custom-nginx:1.0 可以推送到任何 Docker 镜像仓库,供他人或其它环境使用。

5. 实战二:在运行的容器内配置 Yum 仓库并安装服务

现在,我们进入第二个场景:在一个正在运行的、非常干净的基础系统容器内,动态地配置软件源并安装服务。我们以 centos:7 镜像为例。

5.1 启动一个干净的 CentOS 7 容器

首先,拉取镜像(如果本地没有)并启动一个交互式的容器:

docker run -it --name centos-test centos:7 /bin/bash

命令解析:

  • -it -i 保持标准输入打开, -t 分配一个伪终端。两者结合允许你与容器进行交互,就像登录了一台虚拟机。
  • --name centos-test :指定容器名称。
  • centos:7 :使用的镜像。
  • /bin/bash :容器启动后执行的命令,这里我们启动一个 Bash shell。

执行成功后,你的终端提示符会变成类似 [root@容器ID /]# 的样子,表示你已经进入了容器内部。

5.2 容器内配置阿里云 Yum 仓库

默认的 CentOS 7 基础镜像的 Yum 源可能速度很慢。我们将其替换为阿里云的镜像源。

  1. 在容器内的 Shell 中,首先备份原有的 Yum 源配置文件:

    cd /etc/yum.repos.d/
    mkdir bak
    mv *.repo bak/
    
  2. 下载阿里云为 CentOS 7 提供的 repo 文件:

    curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
    

    注意 :如果容器内没有 curl 命令,可以使用 wget (如果也没有,需要先通过其他方式获取文件,或者使用宿主机的文件拷贝进去,见下文备选方案)。

  3. 清理并重建 Yum 缓存:

    yum clean all
    yum makecache
    

备选方案:如果容器内没有网络工具 如果容器内既没有 curl 也没有 wget ,你可以从宿主机将 repo 文件复制到容器内。 在宿主机的另一个终端窗口 执行:

# 首先,在宿主机下载阿里云的 repo 文件
curl -o CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo

# 然后,使用 docker cp 命令复制到正在运行的容器中
docker cp CentOS-Base.repo centos-test:/etc/yum.repos.d/

# 最后,回到容器内的终端,执行缓存更新
yum clean all
yum makecache

5.3 在容器内安装服务

Yum 源配置好后,就可以安装任何你需要的软件了。例如,安装一个 nginx 服务用于测试,同时安装 vim net-tools 等常用工具。

在容器内的 Shell 中执行:

yum install -y nginx vim net-tools wget

-y 参数表示对所有询问自动回答“是”。

5.4 验证安装并运行服务

  1. 验证安装
    nginx -v
    vim --version
    ifconfig # 来自 net-tools
    
  2. 启动 Nginx
    nginx
    
  3. 检查 Nginx 进程和端口
    ps aux | grep nginx
    netstat -tlnp | grep :80
    
    你应该能看到 Nginx 主进程和工作进程,以及 80 端口处于监听状态。

重要提醒 :此时 Nginx 服务运行在容器内部,但外部(宿主机)还无法直接访问,因为我们在启动容器时没有做端口映射 ( -p )。

5.5 从容器提交为新镜像(可选但重要)

我们在容器内所做的所有修改(安装软件、配置源)都只存在于这个容器的可写层中。如果容器被删除,这些修改将全部丢失。

如果你想将当前这个配置好环境的状态保存下来,形成一个新的镜像,可以使用 docker commit 命令。 在宿主机的终端 (不是容器内的终端)执行:

# 首先,退出容器(在容器内执行 exit,或按 Ctrl+D)
# 然后,在宿主机执行提交命令
docker commit centos-test my-centos-with-nginx:1.0

命令解析:

  • centos-test :源容器的名称。
  • my-centos-with-nginx:1.0 :新镜像的名称和标签。

提交后,你可以用 docker images 看到这个新镜像。以后就可以直接运行这个镜像,而无需再次执行安装配置步骤:

docker run -it --name new-container my-centos-with-nginx:1.0 /bin/bash

进入后你会发现, nginx vim 等都已经安装好了。

请注意 docker commit 虽然方便,但在生产环境中并不推荐作为构建镜像的主要方式。因为它是一个“黑盒”操作,无法追溯镜像的生成过程,不利于维护和审计。 标准做法仍然是编写 Dockerfile ,使用 docker build 进行构建。

6. 两种方式的对比与最佳实践

通过以上两个实战,我们体验了 Docker 的两种工作流:

特性 定制镜像 (Dockerfile) 容器内配置 (动态修改)
目的 构建交付物 ,固化应用及其环境。 调试、测试、临时修改 运行中的容器。
可重复性 Dockerfile 是代码,可版本管理,每次构建结果一致。 。手动操作,难以精确复现。
自动化 完美支持 。可集成到 CI/CD 流水线。 不支持 。依赖人工交互。
镜像层级 清晰。每一层对应 Dockerfile 中的一条指令。 混乱。 docker commit 会将所有修改打包成单一新层。
推荐场景 所有需要交付或共享的应用环境。 仅用于学习、紧急问题排查或一次性实验。

最佳实践建议:

  1. 一切皆代码 :将环境配置和应用打包的过程全部写入 Dockerfile 。这是容器化的黄金准则。
  2. 使用 .dockerignore :在构建上下文目录创建 .dockerignore 文件,排除不需要发送给 Docker 守护进程的文件(如 .git , node_modules , 日志文件),可以加速构建过程并减小镜像体积。
  3. 选择合适的基础镜像 :优先选择官方镜像,并根据需求选择 -alpine (体积小)、 -slim (精简)或特定版本标签。
  4. 减少镜像层数 :将相关的 RUN 命令用 && 连接,并用 \ 换行,以减少镜像层数,使镜像更紧凑。
    # 不推荐
    RUN yum update -y
    RUN yum install -y package1
    RUN yum install -y package2
    RUN yum clean all
    
    # 推荐
    RUN yum update -y && \
        yum install -y package1 package2 && \
        yum clean all
    
  5. 清理缓存 :在安装软件的同一条 RUN 指令中,完成安装后立即清理包管理器缓存(如 yum clean all , apt-get clean ),可以有效减小最终镜像大小。
  6. 慎用 docker commit :仅将其作为从临时容器中“拯救”配置的最后手段,而不是常规构建流程。

7. 常见问题与排查思路

在实践过程中,你可能会遇到以下问题:

问题现象 可能原因 排查方式 解决方案
docker build 失败,提示找不到文件 COPY ADD 指令引用的文件不在构建上下文中。 检查 docker build 命令最后的 . 指定的路径,以及 Dockerfile 中文件的相对路径。 确保所有需要复制的文件都在构建上下文目录或其子目录下。
容器启动后立即退出 容器内没有前台进程在运行。Docker 容器需要至少一个前台进程才能保持运行。 docker logs <容器名> 查看日志。检查 Dockerfile 中的 CMD ENTRYPOINT 指令。 确保启动命令是前台执行(如 nginx -g ‘daemon off;‘ )。对于测试,可以用 docker run -it ... /bin/bash 交互式进入。
宿主机无法访问容器服务 启动容器时没有使用 -p 参数映射端口,或者防火墙/安全组规则阻止。 1. docker ps 查看端口映射列。2. 宿主机执行 curl localhost:<映射端口> 。3. 检查宿主机防火墙 ( firewall-cmd ) 或云服务商安全组。 确保 docker run 时正确使用 -p <宿主机端口>:<容器端口> 。开放宿主机对应端口的防火墙。
容器内无法连接外网(如无法 yum install 容器 DNS 配置问题,或宿主机的网络策略限制。 1. 容器内 ping 8.8.8.8 测试网络连通性。2. cat /etc/resolv.conf 查看 DNS。 检查宿主机的网络。运行容器时可尝试 --network host (使用主机网络)测试,或指定 DNS --dns 8.8.8.8
docker run 提示端口已被占用 宿主机上该端口已被其他进程(可能是另一个容器)使用。 netstat -tlnp | grep :<端口号> 查看占用进程。 更改 docker run -p 的宿主机端口,或停止占用端口的原有容器。
镜像体积过大 基础镜像过大,或在构建过程中产生了大量中间文件和缓存。 docker images 查看镜像大小。检查 Dockerfile 中是否有多余的层,是否清理了缓存。 使用更小的基础镜像(如 Alpine)。合并 RUN 指令,并在同一层中清理包管理缓存。使用 .dockerignore
容器内修改文件后, docker commit 的镜像启动失败 docker commit 会保存容器当前状态,包括可能不完整的配置或临时文件。 对比原基础镜像的启动命令和文件结构。 优先使用 Dockerfile 重建 。如果必须 commit,确保容器处于一个稳定、干净的状态(如服务已正常停止)。

8. 总结与进阶方向

通过“定制简单镜像”和“容器内配置 Yum 仓库”这两个核心实战,我们走完了 Docker 从静态构建到动态交互的完整闭环。你不仅学会了如何用 Dockerfile 打造一个可交付的应用镜像,也掌握了在容器内进行临时操作和问题排查的技巧。

理解这两者的区别和联系,是掌握 Docker 自主权的关键。记住核心原则: 固化环境用 Dockerfile,临时调整进容器。

为了让你构建的镜像更专业、更高效,这里有几个进阶方向:

  1. 多阶段构建 (Multi-stage Build) :对于需要编译的应用(如 Go、Java),可以在一个镜像中编译,在另一个更小的镜像中运行,从而极大减小最终镜像体积。
  2. 使用 Docker Compose :当你需要定义和运行多个相关联的容器(如一个 Web 应用 + 一个数据库)时, docker-compose.yml 文件能帮你一键管理整个应用栈。
  3. 镜像安全扫描 :使用 docker scan 或集成工具对构建的镜像进行漏洞扫描,确保交付物的安全性。
  4. 搭建私有镜像仓库 :使用 Harbor 或 Docker Registry 在企业内部搭建私有的镜像仓库,用于存储和分发自定义镜像。

Docker 的世界远不止于此,但扎实的基础是探索一切高级特性的前提。建议你将本文中的示例代码亲手运行一遍,并尝试修改 Dockerfile 和容器内的操作,将其应用到你的实际项目中。当你成功将自己的第一个服务封装进 Docker 镜像时,你会对“开发与运维的一致性”有更深的理解。

更多推荐