🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度
如果你已经熟悉 Docker 的基础命令,能跑起来一个 nginx 或 redis 容器,但一到自己动手定制镜像、在容器里装软件就感觉无从下手,那么这篇文章就是为你准备的。
很多教程会告诉你 Docker 的“是什么”,但很少讲清楚“为什么”和“怎么做对”。比如,为什么要在容器里配置 Yum 仓库?直接 yum install 不行吗?自己写的 Dockerfile 为什么构建出来的镜像那么大?在容器里部署服务,和直接在虚拟机里部署,到底有什么本质区别?
这篇文章要解决的,正是从“会用 Docker 命令”到“能驾驭 Docker 进行实际服务部署”之间的关键断层。我们将通过三个紧密关联的实战任务,带你彻底搞懂 Docker 镜像与容器的核心操作逻辑:
- 定制一个简单的镜像 :不止是写 Dockerfile,更是理解镜像的分层原理和构建优化。
- 为容器配置 Yum 仓库 :解决容器内软件安装的依赖源问题,这是部署服务的基础。
- 在容器内安装部署服务 :将前两步结合起来,完成一个从镜像定制到服务上线的完整闭环。
你会发现,Docker 的真正价值不在于隔离,而在于提供了一套可重复、可追溯、高效率的应用交付流水线。下面,我们就从第一个核心操作开始。
1. 定制简单镜像:不止于 Dockerfile
定制镜像是 Docker 的核心能力,但很多人只停留在 FROM 、 RUN 、 COPY 几个指令的层面。我们首先要理解一个关键点: Docker 镜像的本质是分层的只读文件系统叠加 。每一层(Layer)代表一次修改(如执行一条 RUN 指令),这些层被堆叠起来,最终通过一个可写的容器层(Container Layer)来运行。
1.1 从需求出发:为什么需要定制镜像?
假设你需要一个运行 Python Flask 应用的 Web 服务。使用官方 python:3.9 镜像直接运行,虽然方便,但可能包含你不需要的库,镜像体积大,且缺乏应用特定的环境配置。定制镜像可以:
- 固化环境 :确保应用依赖(Python版本、系统库、Python包)完全一致。
- 优化体积 :移除构建过程中的中间文件和不必要的工具。
- 提升安全 :使用非 root 用户运行应用,减少攻击面。
- 简化部署 :一个镜像包含了全部所需,实现“一次构建,处处运行”。
1.2 编写你的第一个“有效” Dockerfile
让我们从一个具体的例子开始:构建一个运行简单 Flask 应用的镜像。首先创建项目目录和文件。
项目结构:
my-flask-app/
├── app.py
├── requirements.txt
└── Dockerfile
1. 应用代码 ( app.py ):
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello():
return f'Hello from Docker! Hostname: {os.uname().nodename}'
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
2. 依赖文件 ( requirements.txt ):
Flask==2.3.3
3. Dockerfile 初版 ( Dockerfile ):
# 第一阶段:使用官方Python镜像作为基础
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 声明容器运行时监听的端口
EXPOSE 5000
# 定义容器启动时执行的命令
CMD ["python", "app.py"]
这个 Dockerfile 已经具备了基本功能,但它有优化空间。直接构建: docker build -t my-flask-app:v1 .
1.3 镜像构建的深层逻辑与优化
构建命令执行后,Docker Daemon 会逐行解析 Dockerfile,每一步都会生成一个新的镜像层。优化镜像的关键在于 减少层数和每层的大小 。
优化点1:利用构建缓存 Docker 使用缓存加速构建。如果 COPY requirements.txt . 这一行之后的内容没变,那么 RUN pip install... 这一层就会使用缓存,无需重新下载包。因此,通常先复制依赖文件并安装依赖,再复制代码,这样修改代码时不会触发依赖层的重建。
优化点2:清理无用文件 在 RUN 指令中安装软件后,及时清理 apt 或 yum 的缓存,可以显著减小镜像体积。
优化点3:使用多阶段构建(针对编译型语言) 对于需要编译的应用(如 Go, Java),可以在一个镜像中编译,在另一个更小的镜像中运行,从而抛弃编译环境和中间文件。
优化后的 Dockerfile ( Dockerfile.optimized ):
# 使用更小的基础镜像
FROM python:3.9-alpine AS builder
WORKDIR /app
COPY requirements.txt .
# 在构建阶段安装依赖,可以使用 --user 安装到用户目录,但Alpine下pip行为略有不同,这里主要演示思路
RUN pip install --user --no-cache-dir -r requirements.txt
# 第二阶段:运行阶段
FROM python:3.9-alpine
WORKDIR /app
# 从构建阶段复制已安装的Python包
COPY --from=builder /root/.local /root/.local
# 确保pip安装的包在PATH中
ENV PATH=/root/.local/bin:$PATH
# 复制应用代码
COPY app.py .
# 创建一个非root用户并切换(增强安全性)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 5000
CMD ["python", "app.py"]
使用 Alpine 镜像和分阶段构建,最终镜像体积可能只有原来的一半甚至更小。构建优化版: docker build -f Dockerfile.optimized -t my-flask-app:optimized .
运行并测试两个镜像,你会发现功能一致,但后者更小、更安全。
docker run -d -p 5000:5000 --name flask-v1 my-flask-app:v1
docker run -d -p 5001:5000 --name flask-opt my-flask-app:optimized
curl http://localhost:5000
curl http://localhost:5001
2. 为容器配置 Yum 仓库:解决“软件从哪来”的问题
在定制镜像时,我们经常需要在容器内安装软件,比如 vim 、 curl 或者一些特定的系统库。在基于 Red Hat/CentOS 的镜像中,这通常通过 yum (或 dnf )包管理器完成。但容器内的环境是精简的,默认的 Yum 仓库配置可能不存在、不可用或速度慢。
2.1 为什么容器内需要单独配置 Yum 源?
- 基础镜像极小化 :像
centos:7或rockylinux:8这样的官方镜像,为了追求体积小,可能移除了默认的仓库配置文件(.repo文件)。 - 网络隔离 :容器可能运行在没有外网访问能力的环境中,需要配置内部私有仓库。
- 速度与稳定性 :配置国内镜像源(如阿里云、清华源)可以极大提升软件安装速度。
2.2 实战:在 Dockerfile 中配置 Yum 仓库
我们以创建一个基于 CentOS 7、并安装了常用工具的定制镜像为例。
Dockerfile ( Dockerfile.centos ):
# 使用CentOS 7官方镜像
FROM centos:7
# 1. 备份原有的可能不存在的repo文件(可选,但好习惯)
RUN mv /etc/yum.repos.d /etc/yum.repos.d.backup 2>/dev/null || true
# 2. 创建新的repo目录
RUN mkdir -p /etc/yum.repos.d
# 3. 添加阿里云CentOS 7的yum源配置文件
# 注意:CentOS 7官方支持已转向CentOS Stream,这里使用vault.centos.org或阿里云镜像
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
# 4. (可选)添加EPEL仓库(Extra Packages for Enterprise Linux)
RUN curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
# 5. 清理缓存并生成新缓存
RUN yum clean all && yum makecache fast
# 6. 安装我们需要的软件包
RUN yum install -y vim-enhanced curl wget net-tools telnet tree
# 7. 清理安装缓存以减小镜像体积(重要!)
RUN yum clean all && rm -rf /var/cache/yum/*
# 设置一个默认命令
CMD ["/bin/bash"]
关键步骤解析:
- 步骤1-4 :完成了 Yum 仓库的配置。我们直接使用了阿里云的镜像源,速度快且稳定。
- 步骤5 :
yum makecache fast生成元数据缓存,加速后续安装。 - 步骤6 :安装常用工具。
- 步骤7 : 至关重要 。清理 Yum 缓存,否则安装过程中下载的所有
.rpm包都会保留在/var/cache/yum/目录,导致镜像体积膨胀数百MB。
构建并运行这个镜像:
docker build -f Dockerfile.centos -t my-centos-tools .
docker run -it --rm my-centos-tools
# 进入容器后,测试工具和yum源
vim --version
curl --version
yum search nginx
2.3 高级场景:配置私有 Yum 仓库
在企业内网,你可能需要配置指向内部服务器的仓库。方法是将私有仓库的 .repo 文件直接 COPY 到镜像中。
- 在宿主机准备
mycompany.repo:[mycompany-base] name=MyCompany Base OS Repository baseurl=http://internal-repo-server/centos/$releasever/os/$basearch/ enabled=1 gpgcheck=0 # 内网环境可能不进行GPG检查 [mycompany-apps] name=MyCompany Applications baseurl=http://internal-repo-server/myapps/ enabled=1 gpgcheck=0 - 在 Dockerfile 中:
FROM centos:7 COPY mycompany.repo /etc/yum.repos.d/ RUN yum clean all && yum makecache fast
这种方式将仓库配置固化在镜像中,适合需要固定依赖版本的应用。
3. 容器内安装部署服务:完成交付闭环
现在,我们将前两章的知识结合起来,完成一个完整的服务部署:在自定义的 CentOS 镜像中,安装并配置 Nginx Web 服务器。
3.1 设计思路
我们的目标是构建一个包含 Nginx 的镜像,并且将我们自己的静态网站发布出去。
- 基础镜像 :选择
centos:7。 - 环境配置 :配置国内 Yum 源,保证安装速度。
- 服务安装 :使用
yum安装nginx。 - 服务配置 :将本地的网站文件复制到容器内,并覆盖 Nginx 的默认配置。
- 服务启动 :配置容器启动时自动运行 Nginx。
3.2 完整项目与 Dockerfile
创建项目目录:
nginx-centos/
├── html/
│ └── index.html (我们的自定义网页)
├── nginx.conf (自定义的Nginx配置文件)
└── Dockerfile
1. 自定义首页 ( html/index.html ):
<!DOCTYPE html>
<html>
<head>
<title>My Docker Nginx</title>
</head>
<body>
<h1>Successfully deployed Nginx in a Docker Container!</h1>
<p>This page is served from a custom CentOS image.</p>
</body>
</html>
2. 自定义 Nginx 配置 ( nginx.conf ): 这是一个极简配置,覆盖默认配置,将根目录指向我们自定义的 /usr/share/nginx/html 。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
}
3. 核心 Dockerfile ( Dockerfile ):
# 基于CentOS 7
FROM centos:7
# 维护者信息(可选)
LABEL maintainer="your-email@example.com"
# 1. 配置Yum仓库(使用阿里云镜像)
RUN curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo && \
curl -o /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo && \
yum clean all && yum makecache fast
# 2. 安装Nginx (来自EPEL仓库)
RUN yum install -y nginx
# 3. 创建必要的日志目录和PID文件目录(Nginx启动需要)
RUN mkdir -p /var/log/nginx /var/run && \
touch /var/run/nginx.pid
# 4. 移除默认的Nginx欢迎页面
RUN rm -rf /usr/share/nginx/html/*
# 5. 将自定义的网站文件和配置文件复制到容器内
COPY html/ /usr/share/nginx/html/
COPY nginx.conf /etc/nginx/nginx.conf
# 6. 调整文件权限(确保Nginx用户能访问)
RUN chown -R nginx:nginx /usr/share/nginx/html && \
chmod -R 755 /usr/share/nginx/html
# 7. 暴露端口
EXPOSE 80
# 8. 定义健康检查(可选但推荐)
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost/ || exit 1
# 9. 以非daemon方式启动Nginx(重要!)
# 容器内前台进程必须持续运行,否则容器会退出。
CMD ["nginx", "-g", "daemon off;"]
3.3 构建、运行与验证
- 构建镜像:
docker build -t my-nginx-centos . - 运行容器: 将容器的 80 端口映射到宿主机的 8080 端口。
docker run -d -p 8080:80 --name my-web my-nginx-centos - 验证服务:
- 方式一:浏览器访问
http://localhost:8080,应该看到自定义的 HTML 页面。 - 方式二:使用
curl命令:curl -s http://localhost:8080 | grep -o "<title>.*</title>" # 预期输出:<title>My Docker Nginx</title> - 方式三:查看容器日志:
docker logs my-web - 方式四:检查健康状态:
docker inspect --format='{{.State.Health.Status}}' my-web # 预期输出:healthy
- 方式一:浏览器访问
4. 核心流程拆解与原理深化
通过上面的实战,我们已经完成了一个闭环。现在我们来拆解其中的关键步骤,理解其背后的设计原理。
4.1 Dockerfile 指令的“副作用”与层管理
每一条 RUN 、 COPY 、 ADD 指令都会创建一个新的镜像层。层的特性是 只读的 。这意味着:
- 优点 :相同的层可以被多个镜像共享,节省存储空间。构建时,如果某层及其之前的所有层没有变化,Docker 会直接使用缓存,极大加速构建。
- 缺点 :每一层都会增加镜像体积,即使你在后面的层中删除了前面层创建的文件。例如:
RUN yum install -y huge-package # 层A:增加了500MB RUN yum remove -y huge-package # 层B:标记删除,但层A的500MB仍在镜像中!
最佳实践 :将相关的安装和清理操作放在 同一条 RUN 指令 中,用 && 和 \ 连接,使其成为一个层。
RUN yum install -y huge-package && \
yum clean all && \
rm -rf /var/cache/yum/*
# 这样,安装产生的临时文件在同一层被清理,不会保留在最终镜像里。
4.2 CMD 与 ENTRYPOINT:容器启动的“大脑”
这是容器编排中最容易混淆的点之一。
-
CMD:为容器提供默认的执行命令及其参数。可以被docker run后面的命令覆盖。
运行CMD ["nginx", "-g", "daemon off;"]docker run my-image会执行此命令。运行docker run my-image /bin/bash则会覆盖CMD,进入 bash。 -
ENTRYPOINT:配置容器启动时运行的 可执行文件 。CMD的内容会作为参数传递给ENTRYPOINT。
运行ENTRYPOINT ["nginx"] CMD ["-g", "daemon off;"]docker run my-image等同于执行nginx -g "daemon off;"。运行docker run my-image -v则等同于执行nginx -v(-v替换了CMD的内容)。 如何选择? - 如果希望镜像像一个命令一样被调用(如
docker run my-curl https://example.com),使用ENTRYPOINT定义命令主体,CMD定义默认参数。 - 如果希望镜像运行一个固定的前台进程(如 Web 服务器、数据库),通常只使用
CMD即可。使用ENTRYPOINT包装启动脚本也是一种常见模式。
4.3 数据持久化与配置管理
我们的 Nginx 示例将网站和配置都“烧录”进了镜像。这在版本固定时很好,但若需要频繁变更,则需借助数据卷(Volume)或绑定挂载(Bind Mount)。
- 配置文件动态化 :可以将
nginx.conf通过卷挂载,无需重建镜像即可修改配置。docker run -d -p 8080:80 \ -v /host/path/nginx.conf:/etc/nginx/nginx.conf:ro \ --name nginx-with-config my-nginx-centos - 网站文件动态化 :同样,可以将网站目录挂载。
docker run -d -p 8080:80 \ -v /host/path/html:/usr/share/nginx/html:ro \ --name nginx-with-html my-nginx-centos
原则 :将 应用代码和依赖 放入镜像以保证一致性;将 配置、数据和日志 通过卷管理以实现灵活性和持久化。
5. 常见问题与排查思路
在实际操作中,你几乎一定会遇到下面这些问题。这里提供一个快速排查指南。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
docker build 失败,提示 yum install 错误 | 1. 网络问题,无法访问仓库。 2. 仓库配置文件错误或缺失。 3. 镜像源地址失效。 | 1. 在 Dockerfile 中 RUN 指令前加 RUN curl -I https://mirrors.aliyun.com 测试网络。 2. 进入临时容器检查 /etc/yum.repos.d/ 目录: docker run -it --rm centos:7 cat /etc/yum.repos.d/*.repo 。 3. 查看构建日志的具体错误信息。 | 1. 确保宿主机能联网,或为 Docker Daemon 配置代理。 2. 在 Dockerfile 中显式配置正确的仓库源(如本文阿里云源)。 3. 使用更稳定的基础镜像,如 rockylinux:8 。 |
| 镜像构建成功,但运行容器立即退出 | 1. 容器内没有前台进程在运行。 2. CMD 或 ENTRYPOINT 指定的命令执行失败。 3. 应用启动报错(如端口冲突、配置错误)。 | 1. docker run -it --rm your-image sh 手动进入容器检查。 2. 查看容器日志: docker logs <container-id> 。 3. 使用 docker run -it 交互模式运行,观察启动输出。 | 1. 确保 CMD 是启动一个 前台进程 (如 Nginx 用 daemon off; )。 2. 在 Dockerfile 中调试 CMD ,可以先改成 CMD ["sleep", "3600"] 让容器保持运行,再进入排查。 3. 检查应用配置文件路径和权限。 |
| 容器内服务无法被宿主机访问 | 1. 容器没有暴露端口(缺少 EXPOSE )。 2. docker run 时没有进行端口映射 ( -p )。 3. 容器内服务监听地址错误(如 127.0.0.1 )。 4. 宿主机防火墙阻止。 | 1. docker ps 查看端口映射列。 2. docker exec -it <container-id> netstat -tlnp 查看容器内进程监听地址和端口。 3. 检查应用配置,确保监听 0.0.0.0 。 | 1. Dockerfile 中使用 EXPOSE 声明端口。 2. docker run 时使用 -p <host-port>:<container-port> 正确映射。 3. 配置应用服务绑定到 0.0.0.0 。 4. 调整宿主机防火墙规则。 |
| 镜像体积异常巨大 | 1. 每一层没有清理缓存(如 yum cache , apt cache , pip cache )。 2. 复制了不必要的文件(如 node_modules , .git )。 3. 使用了过大的基础镜像。 | 1. docker history <image-name> 查看各层大小。 2. 检查 .dockerignore 文件是否配置。 3. 考虑使用多阶段构建。 | 1. 在同一条 RUN 指令中安装并清理缓存。 2. 创建 .dockerignore 文件,排除构建上下文中的无用文件。 3. 使用 -alpine 、 -slim 等小型变体作为基础镜像。 |
| 容器内时间不正确 | 容器默认使用 UTC 时区。 | docker exec -it <container-id> date | 在 Dockerfile 中设置时区: RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone |
6. 最佳实践与工程建议
掌握了基础操作和排错方法后,遵循以下最佳实践能让你的 Docker 使用水平提升一个档次。
6.1 Dockerfile 编写最佳实践
- 使用
.dockerignore文件 :排除构建上下文(docker build命令所在目录)中不需要的文件,如*.log,*.tmp,.git/,node_modules/。这能加速构建和避免敏感信息泄露。# .dockerignore 示例 **/*.log **/.git **/node_modules Dockerfile *.md - 一个容器只运行一个进程 :这是微服务架构的核心思想。将 Nginx 和 PHP-FPM 放在不同容器中,通过 Docker 网络通信,而非放在同一个容器里。
- 使用特定标签,而非
latest:FROM python:3.9-slim比FROM python:latest更稳定,因为latest标签会变动。 - 以非 root 用户运行进程 :在 Dockerfile 末尾使用
USER指令切换用户,减少安全风险。 - 利用多阶段构建 :如前文所示,它能显著减少生产镜像的体积。
6.2 镜像构建与仓库管理
- 本地构建与测试 :在推送到远程仓库前,务必在本地完成构建和运行测试。
- 为镜像打上有意义的标签 :使用语义化版本或 Git 提交哈希。
docker build -t myapp:1.0.0 . docker build -t myapp:$(git rev-parse --short HEAD) . - 使用私有镜像仓库 :对于公司内部镜像,使用 Harbor、GitLab Container Registry 等搭建私有仓库。
6.3 容器运行与管理
- 资源限制 :使用
-m,--cpus等参数限制容器资源,防止单个容器耗尽主机资源。docker run -d -m 512m --cpus="1.5" --name my-limited-container my-image - 使用 Docker Compose 管理多容器应用 :对于需要 Nginx + App + DB 的组合,一个
docker-compose.yml文件能简化所有操作。version: '3.8' services: web: build: . ports: - "8080:80" depends_on: - db db: image: postgres:13 environment: POSTGRES_PASSWORD: secret - 日志管理 :默认日志驱动是
json-file,日志会堆积。生产环境应考虑使用journald、syslog或fluentd等日志驱动,或将容器日志目录挂载到宿主机统一收集。
从定制一个简单的 Flask 镜像,到在 CentOS 容器中配置 Yum 仓库并部署 Nginx,我们完成了一次完整的 Docker 应用交付流程演练。这其中的关键,不在于记忆命令,而在于理解 Docker 镜像分层、容器生命周期、数据管理以及网络通信的核心思想。
当你下次需要部署一个服务时,可以遵循这个思考路径: 先定义环境(Dockerfile),再解决依赖(仓库/源),然后集成应用,最后考虑运行时的配置、数据和外网访问 。把 Docker 看作一个高效的、声明式的环境打包工具,而非一个轻量虚拟机,你才能真正发挥其威力。
建议你将本文中的三个 Dockerfile 作为模板保存,并根据实际项目需求进行修改。在实践中,你会遇到更复杂的问题,比如多服务编排、健康检查、密钥管理等,那时可以再深入学习 Docker Compose、Docker Swarm 或 Kubernetes。但无论如何,扎实掌握本文所述的镜像定制与容器服务部署,都是你容器化旅程中最坚实的第一步。
🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度


被折叠的 条评论
为什么被折叠?



