在容器化部署的实践中,我们常常会遇到一个核心需求:如何将一个标准的操作系统镜像,快速定制成符合我们业务需求的专属镜像,并在其中部署和配置服务。很多新手在初次接触 Docker 时,会止步于拉取和使用官方镜像,一旦需要修改内部环境(比如安装特定软件、配置Yum源)就感到无从下手,要么选择进入容器手动操作(但容器重启后修改会丢失),要么觉得编写 Dockerfile 过于复杂。
本文将彻底解决这个问题,手把手带你从零开始,完成“定制镜像 -> 配置容器内部环境 -> 部署服务”的完整闭环。你将掌握如何通过 Dockerfile 构建一个包含自定义 Yum 仓库的 CentOS 镜像,并在此镜像基础上,在容器内成功部署一个 Nginx 服务。整个过程代码完整、步骤清晰,你可以完全跟着操作一遍,彻底理解 Docker 镜像定制的核心逻辑。
1. 核心概念与目标拆解
在开始动手之前,我们需要明确几个关键概念和本次实战的最终目标。
Docker 镜像是一个轻量级、可执行的独立软件包,它包含运行某个软件所需的所有内容:代码、运行时、库、环境变量和配置文件。你可以把它理解为一个应用程序的“模板”或“快照”。
Docker 容器是镜像的运行实例。当你运行一个镜像时,你就创建了一个容器。容器是隔离的、资源可控的进程空间。我们通常说的“进入容器操作”,指的是在一个正在运行的容器实例内部进行操作。
Dockerfile是一个文本文件,里面包含了一条条的指令,每一条指令构建镜像的一层。Docker 通过读取 Dockerfile 中的指令来自动构建镜像。它是实现镜像定制化、自动化的核心。
Yum 仓库是 CentOS/RedHat 系列 Linux 发行版中用于软件包管理的源。在容器内,默认的仓库可能无法访问或速度慢,配置一个本地或国内的镜像仓库是提升构建速度和确保软件包可用的关键。
本次实战目标:
- 定制简单镜像:编写一个 Dockerfile,从官方 CentOS 镜像开始,定制我们自己的基础镜像。
- 为容器配置 Yum 仓库:在定制镜像的过程中,将默认的 Yum 源替换为国内高速镜像源(如阿里云镜像源),确保后续安装软件的速度和稳定性。
- 容器内安装部署服务:在基于定制镜像启动的容器内,安装并配置一个 Nginx Web 服务器,验证服务部署成功。
最终,我们将得到一个名为mycentos:nginx的自定义镜像,运行它即可直接启动一个提供了 Nginx 服务的容器。
2. 环境准备与项目结构
在开始编写代码前,请确保你的实验环境已就绪。
2.1 基础环境要求
- 操作系统:任何支持 Docker 的 Linux 发行版(如 Ubuntu 20.04+, CentOS 7+)、macOS 或 Windows 10/11(需安装 WSL2)。本文以 Linux 环境为例进行演示。
- Docker 引擎:已安装并启动 Docker Daemon。你可以通过运行
docker --version和systemctl status docker(Linux)来验证。 - 网络连接:需要能够访问互联网,以下载基础镜像和软件包。
2.2 创建项目目录为了保持工作区整洁,我们创建一个专属目录来存放所有相关文件。
mkdir -p ~/docker-custom-image && cd ~/docker-custom-image2.3 项目最终文件结构预览在开始前,我们先了解最终会生成哪些文件:
~/docker-custom-image/ ├── Dockerfile # 镜像构建的蓝图 ├── nginx.conf # (可选)自定义的 Nginx 配置文件 └── README.md # (可选)项目说明我们主要操作的文件是Dockerfile。
3. 编写 Dockerfile:定制镜像与配置 Yum 仓库
这是整个流程的核心。我们将通过一个 Dockerfile 完成所有步骤。
3.1 创建并编辑 Dockerfile
vim Dockerfile或者使用你喜欢的文本编辑器(如nano,vscode)。
3.2 Dockerfile 内容详解将以下内容完整复制到 Dockerfile 中,每一行都有详细注释。
# 第一行:指定基础镜像。我们使用 CentOS 7 的官方镜像作为起点。 # 使用特定版本标签而非 latest,可以确保构建环境的一致性。 FROM centos:7.9.2009 # 作者信息(元数据,非必需) LABEL maintainer="your-email@example.com" # 第一步:配置 Yum 仓库为国内阿里云镜像,加速软件安装。 # 1. 备份原有的仓库配置文件(一个好习惯) RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup # 2. 下载阿里云的 CentOS 7 仓库配置文件 RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo # 3. 清理旧的缓存并生成新缓存 RUN yum clean all && yum makecache # 第二步:安装必要的系统工具和 Nginx。 # -y 参数表示自动确认安装,避免构建过程因交互提示而中断。 RUN yum install -y wget vim net-tools nginx # 第三步:配置 Nginx 服务(一个简单的自定义示例)。 # 创建一个简单的自定义首页,覆盖 Nginx 默认首页。 RUN echo "<h1>Hello, Docker Custom Image!</h1><p>This Nginx is running inside a container built from a custom image.</p>" > /usr/share/nginx/html/index.html # 第四步:暴露 Nginx 默认的 HTTP 端口 80。 # 这只是一个声明,告诉用户这个镜像中的应用程序会使用哪些端口。 EXPOSE 80 # 第五步:设置容器启动时自动执行的命令。 # 使用 `nginx -g 'daemon off;'` 在前台运行 Nginx,这是容器化应用的最佳实践。 # 容器需要有一个前台进程持续运行,否则它会认为任务结束而自动退出。 CMD ["nginx", "-g", "daemon off;"]关键指令解析:
FROM: 所有 Dockerfile 都必须以FROM指令开始。它指定了构建新镜像所基于的父镜像。RUN: 在镜像构建过程中执行命令。每一条RUN指令都会在当前镜像顶部创建一个新的层。我们通过多条RUN命令完成了换源、安装、配置等操作。EXPOSE: 声明运行时容器提供服务的端口。这只是一个元数据,方便使用者了解,实际的端口映射需要在docker run时通过-p参数指定。CMD: 指定容器启动时默认执行的命令。一个 Dockerfile 中只能有一条CMD指令,如果有多条则只有最后一条生效。这里我们让 Nginx 在前台运行。
4. 构建自定义镜像
有了 Dockerfile,我们就可以使用docker build命令来构建镜像。
4.1 执行构建命令在Dockerfile所在的目录(~/docker-custom-image)下,运行:
docker build -t mycentos:nginx .命令参数解释:
docker build: 构建镜像的命令。-t mycentos:nginx:-t参数用于给镜像打标签(Tag)。格式为name:tag。这里我们将镜像命名为mycentos,标签为nginx。.: 这个点.表示当前目录是构建上下文(Build Context)。Docker 守护进程会将当前目录下的所有文件(除非被.dockerignore排除)发送给 Docker 引擎用于构建。它也是 Dockerfile 所在的位置。
4.2 观察构建过程执行命令后,Docker 会逐行执行 Dockerfile 中的指令。你会看到类似下面的输出:
Sending build context to Docker daemon 2.048kB Step 1/9 : FROM centos:7.9.2009 7.9.2009: Pulling from library/centos Digest: sha256:... Status: Downloaded newer image for centos:7.9.2009 ---> 8652b9f0cb4c Step 2/9 : LABEL maintainer="your-email@example.com" ---> Running in abc123def456 ---> 8e7f1a2b3c4d Step 3/9 : RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup ---> Running in def456abc123 ---> 5a6b7c8d9e0f ... Step 8/9 : EXPOSE 80 ---> Running in 789012ghi345 ---> bcde456fgh789 Step 9/9 : CMD ["nginx", "-g", "daemon off;"] ---> Running in 234567jkl890 ---> 1234567890ab Successfully built 1234567890ab Successfully tagged mycentos:nginx每一行Step对应 Dockerfile 中的一条指令(或一个层)。---> Running in ...表示在一个临时容器中执行该指令,---> ...后面的一串字符是执行后生成的新镜像层的 ID。最后两行表示构建成功,并打上了我们指定的标签。
4.3 验证镜像是否创建成功构建完成后,使用docker images命令查看本地镜像列表:
docker images | grep mycentos你应该能看到类似下面的输出:
REPOSITORY TAG IMAGE ID CREATED SIZE mycentos nginx 1234567890ab 2 minutes ago 450MB至此,我们的自定义镜像mycentos:nginx已经构建完成。它包含了 CentOS 7.9 系统、配置好的阿里云 Yum 源、安装好的 Nginx 以及我们自定义的首页。
5. 运行容器并验证服务部署
镜像只是静态的模板,我们需要运行它来创建容器,并验证 Nginx 服务是否成功部署。
5.1 基于自定义镜像运行容器
docker run -d --name my-nginx-container -p 8080:80 mycentos:nginx命令参数解释:
docker run: 运行容器的命令。-d: 让容器在后台运行(Detached mode)。--name my-nginx-container: 给容器起一个名字,方便后续管理(如停止、查看日志)。-p 8080:80: 端口映射。将宿主机的8080端口映射到容器的80端口。这样,我们通过访问宿主机的8080端口就能访问容器内的 Nginx 服务。mycentos:nginx: 指定用于创建容器的镜像名称和标签。
5.2 检查容器运行状态
docker ps你应该能看到一个名为my-nginx-container的容器正在运行,状态为Up,并且端口映射为0.0.0.0:8080->80/tcp。
5.3 验证 Nginx 服务现在,Nginx 服务已经在容器内运行。我们可以通过几种方式验证:
方式一:在宿主机上使用curl命令访问
curl http://localhost:8080如果一切正常,你将看到我们之前在 Dockerfile 中写入的 HTML 内容:
<h1>Hello, Docker Custom Image!</h1><p>This Nginx is running inside a container built from a custom image.</p>方式二:通过浏览器访问打开你的浏览器,输入地址http://<你的宿主机IP>:8080。如果你的宿主机就是本地机器,可以直接访问http://localhost:8080。你应该能看到显示 “Hello, Docker Custom Image!” 的简单页面。
方式三:进入容器内部查看我们也可以进入容器内部,确认服务进程和配置。
# 进入正在运行的容器 docker exec -it my-nginx-container /bin/bash # 进入容器后,检查 Nginx 进程 ps aux | grep nginx # 输出应包含 nginx master 和 worker 进程 # 检查我们创建的首页文件 cat /usr/share/nginx/html/index.html # 检查 Yum 仓库配置是否已更换为阿里云源 cat /etc/yum.repos.d/CentOS-Base.repo | head -5 # 输出中应包含 mirrors.aliyun.com # 退出容器 exit5.4 查看容器日志如果服务没有如期运行,查看容器日志是首要的排查手段。
docker logs my-nginx-container这将输出容器内 Nginx 服务的标准输出和错误日志,对于排查启动问题非常有用。
6. 深入理解:镜像分层与构建优化
通过上面的实践,我们已经成功完成了目标。但为了写出更高效、更专业的 Dockerfile,我们需要理解背后的原理。
6.1 镜像分层(Layering)Docker 镜像由一系列只读层(Layer)堆叠而成,每一层代表 Dockerfile 中的一条指令。例如,我们的Dockerfile创建了大约 9 层(每条RUN、COPY、ADD等指令都会生成新层)。
- 优点:
- 共享与复用:如果多个镜像基于同一基础层(如
centos:7.9.2009),它们可以共享这一层,节省磁盘空间。 - 构建缓存:Docker 会缓存已构建的层。当你修改 Dockerfile 并重新构建时,只有发生变化的指令及其之后的指令会重新执行,这大大加快了构建速度。
- 共享与复用:如果多个镜像基于同一基础层(如
- 查看镜像分层:可以使用
docker history mycentos:nginx命令查看镜像的构建历史和每层大小。
6.2 Dockerfile 构建优化建议基于分层原理,我们可以优化 Dockerfile:
合并 RUN 指令:减少层数。将相关的
RUN命令用&&连接,并用\换行保持可读性。- 优化前:
RUN mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup RUN curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo RUN yum clean all && yum makecache RUN yum install -y wget vim net-tools nginx - 优化后:
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 wget vim net-tools nginx
这样将 4 层合并为 1 层,使镜像更紧凑。
- 优化前:
清理缓存:在安装软件包的同一层中,清理不必要的缓存文件,减小镜像体积。
- 在
yum install后添加&& yum clean all。 - 对于
apt-get(Debian/Ubuntu),使用&& rm -rf /var/lib/apt/lists/*。
- 在
使用
.dockerignore文件:在构建上下文目录中创建.dockerignore文件,列出不需要发送给 Docker 守护进程的文件或目录(如.git,node_modules, 日志文件等),可以加速构建过程并避免将敏感文件意外打入镜像。选择合适的基础镜像:如果对镜像大小极其敏感,可以考虑使用
alpine等更小的基础镜像,但需要注意软件包管理和兼容性差异。
7. 常见问题与排查思路
在实际操作中,你可能会遇到以下问题。这里提供排查思路。
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
docker build失败,提示Unable to find image ‘centos:7.9.2009’ locally | Docker 无法从仓库拉取指定标签的基础镜像。 | 1. 检查网络连接。 2. 确认镜像标签是否存在,可以访问 Docker Hub 网站查询。 3. 尝试使用更通用的标签,如 centos:7。 |
docker build过程中yum install失败,提示Cannot retrieve metalink for repository: epel/x86_64或下载超时。 | Yum 仓库配置未生效或网络问题。 | 1. 检查 Dockerfile 中配置 Yum 源的RUN指令是否执行成功(查看构建日志)。2. 进入构建失败的临时容器检查 /etc/yum.repos.d/下的文件内容。3. 尝试在 Dockerfile 中使用其他国内镜像源,如清华源、华为云源。 |
docker run后容器立即退出,docker ps看不到容器。 | 容器内没有前台进程持续运行。 | 1. 检查 Dockerfile 中的CMD或ENTRYPOINT指令是否正确。2. 使用 docker logs <容器ID>查看退出前的日志。3. 对于 Nginx,确保使用 nginx -g ‘daemon off;’在前台运行。4. 可以先用 docker run -it mycentos:nginx /bin/bash交互式启动,手动测试命令。 |
宿主机访问localhost:8080连接被拒绝。 | 端口映射失败或容器内服务未监听正确端口。 | 1. 确认docker run时使用了-p 8080:80参数。2. 使用 docker ps确认端口映射已建立 (0.0.0.0:8080->80/tcp)。3. 进入容器 ( docker exec) 检查 Nginx 进程是否运行 (ps aux | grep nginx)。4. 在容器内使用 curl localhost:80测试服务是否正常。 |
| 镜像体积过大。 | 构建过程中引入了不必要的文件或层数过多。 | 1. 参考第6节优化 Dockerfile,合并RUN指令并清理缓存。2. 使用多阶段构建( multi-stage builds)来分离构建环境和运行环境,这是减少镜像体积的终极利器。 |
| 如何修改已构建镜像的配置? | 镜像一旦构建完成就是只读的。 | 不要进入运行中的容器修改!正确做法是: 1. 修改 Dockerfile。 2. 重新执行 docker build -t mycentos:nginx-v2 .构建新版本镜像。3. 停止旧容器,用新镜像启动新容器。 |
8. 进阶:使用多阶段构建优化镜像
对于更复杂的应用(如需要编译的 Go、Java 程序),多阶段构建是官方推荐的最佳实践。它允许你在一个 Dockerfile 中使用多个FROM语句,每个FROM开始一个新的构建阶段。你可以将前一阶段的构建产物复制到后一阶段,而丢弃不需要的中间文件和工具,从而得到非常小的最终镜像。
这里给出一个概念性示例,展示如何为一个简单的 Go 应用构建极小的镜像:
# 第一阶段:构建阶段 (builder),使用包含完整Go编译器的镜像 FROM golang:1.19-alpine AS builder WORKDIR /app COPY . . # 编译Go程序,生成静态链接的可执行文件,适合alpine环境 RUN go build -o myapp -ldflags '-s -w' main.go # 第二阶段:运行阶段,使用极小的基础镜像 FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /root/ # 从上一阶段(builder)仅复制编译好的可执行文件,不复制源代码和编译器 COPY --from=builder /app/myapp . # 运行程序 CMD ["./myapp"]通过这种方式,最终的生产镜像只包含运行程序必需的alpine系统和可执行文件myapp,体积可能只有十几MB,而构建阶段的中间产物(编译器、源代码等)完全不会出现在最终镜像中。
9. 工程实践与总结
通过本次从 Dockerfile 编写、镜像构建到容器运行的全流程实践,你应该已经掌握了 Docker 镜像定制的核心技能。回顾一下关键点:
- Dockerfile 是蓝图:它是定义镜像构建过程的脚本,务必精心编写。
- 分层构建是核心机制:理解镜像分层有助于优化构建速度和镜像大小。
- 配置容器内部环境:像配置 Yum 源这类操作,必须在 Dockerfile 的
RUN指令中完成,以确保环境被固化到镜像里。 - 服务前台运行:容器内应用必须以前台模式运行,否则容器会退出。
- 端口映射是访问桥梁:通过
-p参数将容器端口映射到宿主机,是实现外部访问的标准方式。 - 迭代方式:永远通过修改 Dockerfile 并重建镜像来更新应用,而不是直接修改运行中的容器。
将这套流程应用到你的实际项目中:无论是部署一个 Python Flask 应用、一个 Java Spring Boot 的 JAR 包,还是一个 Node.js 服务,思路都是一致的——编写 Dockerfile,定义基础环境、安装依赖、复制代码、配置服务、设置启动命令,最后构建和运行。
你可以尝试修改本次的 Dockerfile,比如安装不同的软件(如mysql,python3),部署不同的服务(如httpd,tomcat),或者将你的应用程序代码通过COPY指令添加到镜像中。多动手实践几次,你就能熟练地将任何应用容器化。