
你是不是也遇到过这样的场景在本地开发环境跑得好好的服务一到测试或生产环境就各种依赖缺失、版本冲突或者团队新成员入职光是配环境就要花掉一整天这正是 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)这里有两个“仓库”概念需要区分Docker 镜像仓库 (Registry)如 Docker Hub、阿里云容器镜像服务用于存储和分发 Docker 镜像。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可以参考以下最简步骤。这里采用国内常用的阿里云镜像源来加速安装。卸载旧版本如有sudo yum remove docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine安装必要的依赖包sudo yum install -y yum-utils device-mapper-persistent-data lvm2配置阿里云 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文件将软件源指向阿里云镜像。更新 Yum 缓存并安装 Docker CEsudo yum makecache fast sudo yum install -y docker-ce docker-ce-cli containerd.io启动 Docker 服务并设置开机自启sudo systemctl start docker sudo systemctl enable docker验证安装sudo docker run hello-world如果能看到 “Hello from Docker!” 等欢迎信息说明 Docker 已安装并运行正常。可选配置用户组为了避免每次使用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在该目录下创建两个文件Dockerfile构建镜像的蓝图。index.html我们自定义的首页。4.2 编写自定义 HTML 页面编辑index.html文件内容如下!DOCTYPE html html head titleMy 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 pThis page is served from a custom Nginx image built by me./p pImage build time: span idbuild-time/span/p script document.getElementById(build-time).textContent new Date().toLocaleString(); /script /body /html4.3 编写 Dockerfile这是构建镜像的核心。在同一个目录下创建Dockerfile注意没有后缀名# 1. 指定基础镜像。我们使用官方的 nginx 镜像作为起点。 FROM nginx:alpine # 2. 维护者信息已弃用但可作为元数据保留 LABEL maintaineryour-emailexample.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做了四件事FROM定义了构建的起点。alpine版本非常小巧。LABEL为镜像添加元数据。COPY将本地文件复制到镜像内的指定路径。这是“定制化”的关键步骤。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 源可能速度很慢。我们将其替换为阿里云的镜像源。在容器内的 Shell 中首先备份原有的 Yum 源配置文件cd /etc/yum.repos.d/ mkdir bak mv *.repo bak/下载阿里云为 CentOS 7 提供的 repo 文件curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo注意如果容器内没有curl命令可以使用wget如果也没有需要先通过其他方式获取文件或者使用宿主机的文件拷贝进去见下文备选方案。清理并重建 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 makecache5.3 在容器内安装服务Yum 源配置好后就可以安装任何你需要的软件了。例如安装一个nginx服务用于测试同时安装vim和net-tools等常用工具。在容器内的 Shell 中执行yum install -y nginx vim net-tools wget-y参数表示对所有询问自动回答“是”。5.4 验证安装并运行服务验证安装nginx -v vim --version ifconfig # 来自 net-tools启动 Nginxnginx检查 Nginx 进程和端口ps aux | grep nginx netstat -tlnp | grep :80你应该能看到 Nginx 主进程和工作进程以及 80 端口处于监听状态。重要提醒此时 Nginx 服务运行在容器内部但外部宿主机还无法直接访问因为我们在启动容器时没有做端口映射 (-p)。5.5 从容器提交为新镜像可选但重要我们在容器内所做的所有修改安装软件、配置源都只存在于这个容器的可写层中。如果容器被删除这些修改将全部丢失。如果你想将当前这个配置好环境的状态保存下来形成一个新的镜像可以使用docker commit命令。在宿主机的终端不是容器内的终端执行# 首先退出容器在容器内执行 exit或按 CtrlD # 然后在宿主机执行提交命令 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会将所有修改打包成单一新层。推荐场景所有需要交付或共享的应用环境。仅用于学习、紧急问题排查或一次性实验。最佳实践建议一切皆代码将环境配置和应用打包的过程全部写入Dockerfile。这是容器化的黄金准则。使用.dockerignore在构建上下文目录创建.dockerignore文件排除不需要发送给 Docker 守护进程的文件如.git,node_modules, 日志文件可以加速构建过程并减小镜像体积。选择合适的基础镜像优先选择官方镜像并根据需求选择-alpine体积小、-slim精简或特定版本标签。减少镜像层数将相关的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清理缓存在安装软件的同一条RUN指令中完成安装后立即清理包管理器缓存如yum clean all,apt-get clean可以有效减小最终镜像大小。慎用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临时调整进容器。为了让你构建的镜像更专业、更高效这里有几个进阶方向多阶段构建 (Multi-stage Build)对于需要编译的应用如 Go、Java可以在一个镜像中编译在另一个更小的镜像中运行从而极大减小最终镜像体积。使用 Docker Compose当你需要定义和运行多个相关联的容器如一个 Web 应用 一个数据库时docker-compose.yml文件能帮你一键管理整个应用栈。镜像安全扫描使用docker scan或集成工具对构建的镜像进行漏洞扫描确保交付物的安全性。搭建私有镜像仓库使用 Harbor 或 Docker Registry 在企业内部搭建私有的镜像仓库用于存储和分发自定义镜像。Docker 的世界远不止于此但扎实的基础是探索一切高级特性的前提。建议你将本文中的示例代码亲手运行一遍并尝试修改Dockerfile和容器内的操作将其应用到你的实际项目中。当你成功将自己的第一个服务封装进 Docker 镜像时你会对“开发与运维的一致性”有更深的理解。