☰
RHEL9 安装 Docker CE 全流程:从环境准备到生产级容器部署
2026/10/5 3:29:02 网站建设 项目流程

1. RHEL9 上装 Docker,先得把这几件事想明白

最近接手了一台 RHEL9 的服务器,环境是全新的,任务是在上面把 Docker 跑起来。RHEL9 这个版本跟以前的 RHEL 系列有一个很大的不同:它默认不再把 Docker 作为容器运行时推荐,系统自带的容器工具是 Podman,官方源里也没有 docker 包。所以想在上面快速上手 Docker,不能像 CentOS 7 时代那样直接yum install docker,得走一套自己的流程。

先说清楚概念:Red Hat 从 RHEL 8 开始就把 Podman 作为默认容器管理工具,到了 RHEL 9 更是彻底把 docker 包从官方 yum 源里移除了。如果你直接用yum install docker,会提示没有可用软件包,这是正常现象。Podman 是兼容 Docker CLI 命令的,很多指令写法几乎一样,但如果你有现成的 docker-compose 文件、依赖 Docker API 的构建工具链、或者团队统一用 Docker 生态,那还是老老实实装 Docker CE 靠谱。

再说一个容易踩的概念坑:Docker 和 Docker Desktop 是两个不同的东西。Docker Desktop 是带图形界面、适合 Windows 和 macOS 的桌面版,内部靠虚拟机跑 Linux 容器;而 RHEL9 服务器上要装的是 Docker Engine,也就是纯命令行版本。我在热搜词里看到一堆 "docker desktop failed to start because virtualisation support wasn't detected" 的搜索,这说明好多人把这两者混为一谈了。Docker Desktop 启动失败多半是宿主机没开虚拟化,那是在 Windows 上的事,跟 RHEL9 装 Docker Engine 是两码事,别在服务器上尝试装 Desktop 版。

在开始之前,建议先确认三件事:系统是 RHEL9 的哪个小版本(cat /etc/redhat-release),网络能不能访问 Docker 官方仓库,机器是不是 x86_64 架构。RHEL9 的三个小版本我都试过,9.0 到 9.3 的安装流程基本一致,差异不大。如果你用的是 CentOS Stream 9,流程也通用,因为 CentOS Stream 9 跟 RHEL9 的包管理方式几乎完全一致。

2. RHEL9 安装 Docker 的完整流程

2.1 配置 Docker 官方 yum 源

RHEL9 默认没有 Docker 源,需要手工添加。先检查系统是否有yum-utils,一般 RHEL9 全新安装可能没带,直接装上:

sudo dnf install -y yum-utils

然后添加 Docker 官方仓库。注意这里我建议优先使用官方源,因为版本最完整、更新最及时。命令如下:

sudo yum-config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

这一步执行后,/etc/yum.repos.d/目录下会出现一个docker-ce.repo文件。这个仓库配置了 stable、test、nightly 三个子仓库,默认取 stable。我习惯先执行一次dnf makecache把仓库缓存刷新一下,然后在安装前先yum list docker-ce --showduplicates看看有哪些版本可选,确认源生效。

补充说明:Docker 官方源对 RHEL 的支援其实是通过rhel这个路径提供的,同时也兼容 CentOS 的 repo 配置。如果你在 RHEL 上遇到 404 或者找不到包的情况,可以检查一下/etc/yum.repos.d/docker-ce.repo里 baseurl 的路径是否命中了正确的发行版标识。

2.2 安装 docker-ce 及相关组件

RHEL9 上安装 Docker 时,最常踩的坑是缺少 container-selinux 依赖。这个包默认不在 RHEL9 的标准源里,而在 AppStream 或者 extras 源中。我的做法是先把系统已有的源全部启用:

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

如果安装过程中报错Problem: package docker-ce-... requires container-selinux >= ...,别慌,这属于 RHEL 的软件源层级问题。先执行sudo dnf module enable container-tools:3.0,再装。RHEL9 的 AppStream 里带了一个 container-tools 模块,启用之后依赖会自动解决。

装完确认版本:

docker --version

正常会输出类似Docker version 24.0.7, build afdd53b的信息。RHEL9 配合 Docker 24.x 版本,实测跑得很稳。如果系统提示 docker-compose-plugin 不在仓库里,那多半是添加的 repo 不是官方源,或者是 RHEL 订阅没有正确注册导致第三方源被禁用。

2.3 启动服务并验证安装结果

安装完成后,先别急着拉镜像。Docker 引擎的核心服务叫docker.service,同时需要containerd.service配合运行。我把启动顺序固定成这样:

sudo systemctl daemon-reload sudo systemctl enable --now containerd sudo systemctl enable --now docker

--now参数的意思是立即启动并设置开机自启。很多教程只写systemctl start docker,结果重启服务器后 Docker 没跟着起来,又要手动折腾一次。在服务器场景下,开机自启是刚需,建议一步到位。

验证服务状态:

sudo systemctl status docker sudo docker run hello-world

跑hello-world容器是检验引擎是否正常工作的最直接方法。它能验证 Docker daemon 能否拉取镜像、创建临时容器、打印日志。我在新机器上一定会做这一步,而不是干看docker version。

2.4 配置镜像加速源

RHEL9 服务器在国内网络环境下,直接拉 Docker Hub 镜像经常慢得让人怀疑人生。镜像加速源就是解决这个问题的关键。修改 Docker 的 daemon 配置文件/etc/docker/daemon.json,把 registry-mirrors 字段加进去:

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://docker.1panel.live" ] }

写完后重启 Docker:

sudo systemctl daemon-reload sudo systemctl restart docker

这里有一个细节值得注意:配置 mirror 后 Docker 并不会立刻清除已有的缓存索引,所以配置完重启后第一次拉镜像可能还是会走原来的路径,拉一次小镜像测试一下即可。加速源本质上是一个上游 registry 的缓存代理,它拉取的镜像跟官方是完全一致的,指纹校验通过即可放心使用。

实操心得:加速源不是越多越好,建议保留一到两个响应快的就好。我在实际使用中发现,某些公共加速源会不定期变更地址,写太多不仅影响配置解析,还可能因为第一个源超时而白白等待很久。加速源的选择标准只有一个——稳定、可访问、能拉到你需要的镜像。

3. 镜像与容器的核心操作要点

3.1 镜像管理高频命令

Docker 的日常使用中,镜像管理占了大头。这里把高频命令整理成节奏感比较清晰的顺序:

# 搜索镜像 docker search nginx # 拉取镜像 docker pull nginx:latest docker pull mysql:8.0 # 查看本地镜像 docker images # 删除镜像(先删容器再删镜像) docker rmi nginx:latest # 清理悬空镜像 docker image prune

有几个细节新手特别容易忽略。第一,docker rmi删除镜像时,如果还有容器在用这个镜像,会报错提示冲突,必须先删容器或者docker rm -f <容器ID>强制删除。第二,docker image prune只会清理没有被任何容器引用的悬空镜像,不会误删正在使用的镜像,可以放心执行。第三,RHEL9 上如果磁盘是 XFS 格式(默认就是),Docker 的存储驱动会自动选择 overlay2,不需要手动配置,但可以通过docker info确认。

3.2 容器生命周期管理

容器管理是 Docker 操作的另一半。拿 Nginx 举例,一个非常标准的运行命令是:

docker run -d --name my-nginx -p 8080:80 nginx:latest

拆开来看:-d是后台运行,--name给容器起名字,-p 8080:80把宿主机的 8080 端口映射到容器内的 80 端口。跑起来之后可以用docker ps查看状态,docker logs my-nginx看日志,docker exec -it my-nginx bash进容器内部排查问题。

容器启动策略这块,--restart参数很重要。我在生产环境跑 MySQL、Redis 这类核心服务时,固定加--restart=always,这样即使机器重启或容器进程异常退出,Docker 也会自动把容器拉起来。不加这个参数,宕机一次你就得手动docker start一次,半夜被叫起来非常难受。

3.3 数据卷与端口映射的底层逻辑

Docker 容器默认是无状态的,容器一删,里面的数据就全没了。解决这个问题靠数据卷(volume)。推荐用具名卷来管理:

docker volume create mysql-data docker run -d --name mysql-test \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=your_password \ mysql:8.0

比如 MySQL 的官方镜像把数据放在/var/lib/mysql,通过-v mysql-data:/var/lib/mysql把具名卷挂载到这个目录,后续就算容器误删,数据还在卷里,重新run一个相同的容器挂载同名的卷,数据就回来了。宿主机的目录挂载类似,-v /host/path:/container/path的形式适合需要直接查看和修改文件的场景。

端口映射设计的建议是:宿主端口尽量保持高位且唯一,避免跟系统常用端口冲突。比如 MySQL 用 13306 映射到容器内 3306,Redis 用 16379 映射 6379。这样在一台多服务的服务器上,端口一目了然,也便于防火墙规则管理。

4. 实战:基于容器跑起 MySQL 8.0 和 Redis 主从

4.1 部署 MySQL 8.0 并配置远程访问

MySQL 8.0 是使用频率最高的容器化数据库之一。我的部署流程是:

docker run -d \ --name mysql8 \ --restart=always \ -p 13306:3306 \ -v mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=Root@123456 \ -e TZ=Asia/Shanghai \ mysql:8.0

--restart=always保证 MySQL 随开机自启,TZ设置时区防止容器内时间跟宿主机不一致,MYSQL_ROOT_PASSWORD是初始化 root 密码的环境变量。跑起来后验证一下容器状态和日志:

docker ps | grep mysql8 docker logs mysql8 | tail -20

容器内部正常启动后,宿主机直接用 mysql 客户端连接测试:

mysql -h 127.0.0.1 -P 13306 -u root -p

这里有一个高频问题:容器启动正常、映射端口也对,但客户端连不上去,报Host 'xxx' is not allowed to connect to this MySQL server。原因是 MySQL 8.0 默认 root 只允许 localhost 登录。解决方法是进容器里改授权:

docker exec -it mysql8 mysql -u root -p
CREATE USER 'app'@'%' IDENTIFIED BY 'App@123456'; GRANT ALL PRIVILEGES ON *.* TO 'app'@'%' WITH GRANT OPTION; FLUSH PRIVILEGES;

建议不要直接给 root 开放远程权限,而是创建一个专用的应用账户,权限范围按需控制。RHEL9 的防火墙默认是 firewalld,如果外部机器连不上,还需要检查防火墙是否放行了对应端口:

sudo firewall-cmd --permanent --add-port=13306/tcp sudo firewall-cmd --reload

4.2 搭建 Redis 主从复制集群

Redis 主从在容器环境下搭建很轻松。先在同一个自定义网络里创建两个容器,主节点:

docker run -d \ --name redis-master \ --restart=always \ -p 16379:6379 \ redis:7 redis-server --requirepass Master@123

从节点需要指定主节点的地址和认证信息。容器之间跨容器通信时,用容器名而不是 IP 更灵活,因为容器重建后 IP 会变:

docker run -d \ --name redis-slave \ --restart=always \ -p 16380:6379 \ redis:7 redis-server \ --replicaof redis-master 6379 \ --masterauth Master@123 \ --requirepass Slave@123

验证主从同步状态:

docker exec -it redis-slave redis-cli -a Slave@123 info replication

看到role:slave、master_link_status:up就说明主从关系建立成功了。这里最容易出问题的点是主从容器之间的网络隔离——如果你的容器不在同一个 network,--replicaof redis-master 6379里的redis-master根本解析不了。

实操心得:搭建 Redis 主从前,建议手动先创建 Docker 网络:

docker network create redis-net

然后两个容器都加上--network redis-net,这样容器名解析才可靠。我在不创建网络的情况下跑过,容器之间用 IP 也可以,但 IP 会漂移,维护成本高,何必给自己找麻烦。

4.3 用 docker-compose 编排多容器服务

到了多容器的场景,直接用docker run一条条命令敲,太容易出错也不便于维护。docker compose是标准解法。RHEL9 上安装 Docker 时我们已经装好了docker-compose-plugin,直接支持docker compose子命令,不用单独装 Python 版的 docker-compose。

以 MySQL + Redis 主从为例,docker-compose.yml可以这样写:

version: "3.8" services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always ports: - "13306:3306" environment: - MYSQL_ROOT_PASSWORD=Root@123456 - TZ=Asia/Shanghai volumes: - mysql-data:/var/lib/mysql redis-master: image: redis:7 container_name: redis-master restart: always ports: - "16379:6379" command: redis-server --requirepass Master@123 redis-slave: image: redis:7 container_name: redis-slave restart: always ports: - "16380:6379" command: redis-server --replicaof redis-master 6379 --masterauth Master@123 --requirepass Slave@123 depends_on: - redis-master volumes: mysql-data:

启动命令:

docker compose up -d

depends_on的作用是让从节点等主节点先启动,但它只保证启动顺序,不保证主节点内 Redis 服务就绪。实际操作中如果从节点先报错连不上,等主节点就绪后再docker compose restart redis-slave即可。这个编排文件的好处是:整个服务栈可以用一条命令启停,迁移服务器时也只需要把这个 yml 文件带过去,几分钟就能在新的 RHEL9 上复现。

5. RHEL9 上 Docker 常见问题排查

5.1 服务启动失败与 virtualization support 检查

在 RHEL9 服务器上,systemctl start docker执行后服务直接失败,systemctl status docker显示Active: failed。日志里常见的报错有两类:一类是 containerd 没起来,导致 docker 无法连接 containerd socket;另一类是 iptables 相关的问题,Docker 要往 NAT 表里写规则,系统内核模块没加载或者 firewalld 冲突就会报错。

先按顺序排查:

# 查看 docker 服务详细日志 sudo journalctl -u docker --no-pager -n 50 # 检查 containerd 状态 sudo systemctl status containerd # 检查 iptables 模块 sudo lsmod | grep iptables sudo sysctl net.bridge.bridge-nf-call-iptables

net.bridge.bridge-nf-call-iptables这个内核参数如果输出 0,需要改成 1 并持久化:

echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf sudo sysctl -p

还有一个要提醒的是,热搜词里那个 "virtualization support not detected" 的错误,那是 Docker Desktop 在 Windows 或 macOS 上启动时检测不到虚拟化扩展的报错,跟 RHEL9 的 Docker Engine 没有关系。RHEL9 服务器如果遇到内核模块加载失败,通常是没装iproute-tc或者内核版本太老,升级系统补丁即可。

5.2 Permission denied 权限问题

安装完 Docker,用普通用户执行docker ps,报错:

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

这是 Docker 的 socket 文件归 root 所有,普通用户没有访问权限。解决方法是把用户加入 docker 组:

sudo groupadd docker sudo usermod -aG docker $USER newgrp docker

newgrp docker能让当前会话立即生效,不用重新登录。加组之后如果还是报权限问题,先看 socket 文件权限:

ls -l /var/run/docker.sock

正常情况下应该是srw-rw---- root docker。如果这个文件不存在,说明 docker daemon 没有启动,先执行sudo systemctl start docker。我在新服务器上建议装完 Docker 后立刻做用户组授权,省的后面每次敲命令都加 sudo,而且不加 sudo 的命令在脚本里更容易踩坑。

5.3 容器网络不通与 DNS 问题

容器跑起来后,宿主机能访问,但容器访问外部网络超时,这是典型的网络问题。先分两层排查:第一层是容器内部能不能解析域名,第二层是能不能访问外网 IP。

# 进入容器内部测试 docker exec -it my-nginx bash # 测试 DNS ping baidu.com # 如果域名解析失败,检查 DNS 配置 cat /etc/resolv.conf

容器内/etc/resolv.conf的 DNS 默认继承宿主机配置。RHEL9 如果启用了 systemd-resolved,容器内可能会拿到一个 127.0.0.53 的 DNS 地址,而这个地址在容器网络里根本不通。解决方法是改 daemon.json 指定默认 DNS:

{ "dns": ["223.5.5.5", "8.8.8.8"] }

然后重启 Docker,重建容器才生效。容器网络还有一个常见场景是不同容器之间互相 ping 不通。这个我在前面已经提过——自定义 bridge 网络 + 容器名互访是标准做法,默认的 bridge 网络不支持容器名解析。

跨主机容器互通是另一个话题,一般用 Swarm 或 Kubernetes 来解决,初期用不上不用急着搞。单机场景下把 docker network 玩明白,基本够用。

5.4 镜像下载慢的解决办法

RHEL9 服务器上拉镜像慢,原因无非两种:网络链路问题或者 Docker Hub 本身访问不稳。前面第 2.4 节配置过加速源,这里再补充两个后续手段。

一个是给docker pull加超时重试,可以在 pull 命令失败后反复执行几次,实测偶尔能成功。另一个手段是设置代理镜像。有些官方镜像在 Docker Hub 上地址特殊,可以改用镜像的完整路径:

docker pull docker.m.daocloud.io/library/mysql:8.0

然后打标签改成标准名称:

docker tag docker.m.daocloud.io/library/mysql:8.0 mysql:8.0 docker rmi docker.m.daocloud.io/library/mysql:8.0

这个方法的本质是利用加速源的域名直接拉取,避免了 daemon.json 配置可能失效的问题。我遇到某些加速源不稳定时,就用这个方式兜底。另外,拉取镜像前先确认本地是不是已经有同名不同 tag 的镜像,有时候你需要的镜像之前已经拉过了,只是 tag 不同,可以直接打 tag 复用,省得重新下载几百兆。

6. 实操心得与避坑指南

6.1 我在 RHEL9 上踩过的坑

这段时间在 RHEL9 上折腾 Docker,踩过几个印象深刻的坑,写出来给大家省点时间。

第一个是container-selinux 依赖的坑。RHEL9 的最小化安装只带基础源,Docker 安装到一半报依赖缺失是常事。解决办法是先dnf install container-selinux,再装 docker-ce。如果这一步还是不行,检查系统有没有正确启用 AppStream 仓库:

sudo subscription-manager repos --list-enabled dnf repolist

第二个是firewalld 跟 Docker 的端口冲突。RHEL9 默认开着 firewalld,Docker 的 iptables 规则跟 firewalld 的端口管理是两套体系。如果你改了 firewalld 规则后发现 Docker 端口映射失效,先别怀疑 Docker,试着重启 firewalld 和 docker 的服务顺序:

sudo systemctl restart docker sudo firewall-cmd --reload

第三种常见情况是RHEL9 上不想用 sudo 跑 docker。按照 5.2 节的方法加入 docker 组后,如果用的还是 SSH 登录会话,一定要重新登录一次让组权限生效,单纯newgrp docker只在当前 shell 生效,新开的 SSH 会话还是旧权限。

第四个是日志暴增把磁盘撑满。容器默认的日志驱动是 json-file,日志文件无限增长。我的建议是启动容器时加上日志轮转参数:

docker run -d \ --log-opt max-size=10m \ --log-opt max-file=3 \ nginx:latest

或者在 daemon.json 里统一配置:

{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

生产环境不管跑什么容器,这个日志限制都建议加上。我在一台跑了二十多个容器的 N100 小主机上试过,不加日志限制,一周不到磁盘就告警了,加完之后磁盘占用非常稳定。

6.2 生产环境建议

在 RHEL9 上跑 Docker 到生产环境级别,有几个建议要重点拎出来说。

先把 Docker 的 daemon.json 一次性配置到位:

{ "registry-mirrors": [ "https://docker.m.daocloud.io", "https://docker.1panel.live" ], "dns": ["223.5.5.5", "8.8.8.8"], "data-root": "/var/lib/docker", "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }

>{ "data-root": "/data/docker" }

改完>docker image prune -f docker container prune -f docker volume prune -f

加上这行巡检逻辑后,我的服务器磁盘从没因为 Docker 资源堆积出过问题。

最后想说的是,RHEL9 的容器化选型不一定非要 Docker。如果你是纯 Red Hat 生态的系统管理员,Podman 其实已经足够日常使用了。但如果你跟我一样,手里有一堆 docker-compose 编排文件、依赖 Docker Hub 镜像生态、或者团队成员已经习惯了 Docker 的工作流,那在 RHEL9 上部署 Docker CE 完全可行,只要按照上面这套流程走,从安装到生产可用基本可以控制在半小时以内。这也正是我把整个实操过程记下来的原因——工具选型从来不是死板的,关键是搞清楚自己的业务诉求,再选择顺手的那一个。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询