☰
CentOS Docker 入门:从环境搭建到镜像容器与数据卷实战
2026/10/21 21:15:31 网站建设 项目流程

最近有人问我,CentOS 上装好了 docker 之后该从哪一步开始玩。其实这类问题挺常见的,因为 docker 的入门资料虽然多,但很多都是按别的发行版写的,放到 CentOS 上就会遇到软件源、selinux、systemd 这些差异。我自己刚接触容器那阵子,也曾经因为一块网卡配置折腾到半夜,后来才意识到不是命令记错,而是对 docker 的运行模型理解得太浅。这篇文章就是我从零开始梳理的 docker 入门第一课,核心环境是 CentOS 加 Linux 命令行,适合那种已经会敲 ls、cd 和 yum,但没真正把容器跑起来的人。看完之后,你应该能自己完成安装、拉镜像、启动容器、查看日志这些基础操作。

1. 为什么选择 CentOS 来玩 docker

1.1 CentOS 和容器运行的关系

CentOS 在服务器领域的分量不用我多说,很多公司的线上环境都基于它。选择 CentOS 跑 docker,主要图的是稳定性和系统管理方式统一。CentOS 从主流版本开始全面使用 systemd,而 docker 的服务也通过 systemd 管理,两者配合起来很顺手。另一个优点是对 Yum 的生态支持很完整,安装软件时不需要手动去编译一堆依赖,直接把官方仓库加进来就能装。这里顺便说一句,如果你是拿 CentOS 的老版本做实验,docker 也能正常跑,但最好把内核版本确认一下,因为新版 docker 对内核模块和 iptables 的依赖越来越敏感。

我实际测试过的环境通常是干净的最小化安装,没有多余的图形界面。这种系统在开机引导阶段不会加载太多无关服务,docker 启动之后端口和 iptables 规则也好排查。相比之下,带桌面的发行版虽然也能玩,但防火墙上多出的节点会让人定位问题的时候多绕几个弯。所以如果你打算从零开始学,我更推荐直接在一台干净的 CentOS 虚拟机里做实验,既能模拟真实服务器环境,又不会影响日常使用的电脑。

1.2 镜像、容器、仓库这三个词必须分清

docker 入门有一个非常关键的概念模型:镜像、容器、仓库。我刚学的时候总是把镜像和容器当成一回事,后来发现这是很多操作事故的根源。镜像相当于一个只读的模板,里面装好了操作系统基础文件、运行环境和你期望的应用代码。容器则是镜像运行起来后的实例,它有自己的文件系统可写层、网络命名空间和进程空间。你可以把镜像理解成安装光盘,容器理解成光盘装好之后运行中的系统,光盘本身不会变,系统里改什么文件都对光盘没有影响。

仓库则解决镜像的存储和分发问题,平时我们用的 docker pull 就是从远端仓库把镜像拉到本地。拉下来之后,镜像在本地以分层的形式存储,所以有时候你会发现删除镜像并没有腾出预期的空间,那是因为某些底层层还被别的镜像引用。理解这三者的关系之后,再去看那些 docker 命令就不会觉得命令之间是孤立的:docker build 是制作镜像,docker run 是根据镜像启动容器,docker push 是把镜像推到仓库,docker pull 则是反向操作。后面所有实操都围绕这三个角色展开,先把它们分清楚,能少踩一半的坑。

2. 在 CentOS 上搭建 docker 运行环境的完整步骤

2.1 安装前必须做的系统检查

拿到一台 CentOS 机器,先别急着复制安装命令。我见过太多人一上来就 yum install docker,结果装的是老版本或者直接被跳过了。第一步是确认系统版本和架构,执行cat /etc/os-release就能看到版本信息,执行uname -r看内核版本。docker 正常运行需要内核版本足够新,一般不建议低于 3.10。如果内核太老,会出现 storage driver 不支持、容器网络通信异常这类问题。

第二步是检查命令工具链是否完整。CentOS 最小化安装默认可能没有 yum-utils,这会导致你后面 yum-config-manager 用不了。我一般安装前先把这些基础工具装上:yum-utils、device-mapper-persistent-data、lvm2。device-mapper 和 lvm2 主要服务于 docker 的存储驱动,虽然现在默认是 overlay2,但保持这些库存在可以减少不必要的麻烦。安装完之后确认 selinux 的状态,这里要特别说明,不建议把 selinux 直接关闭,而是建议给 docker 目录打上合适的上下文标签,或者使用 permissive 模式做实验。完全关闭 selinux 虽然能解决部分权限问题,但线上系统这么做风险很高。

2.2 用 Yum 源安装 docker 引擎

前面检查没问题,就可以开始安装了。在 CentOS 上推荐通过软件仓库来安装,而不是下载一个二进制包手动放进去。手动包的问题在于,后续升级和依赖维护只能靠你自己,仓库方式则可以用 yum 自动完成。操作一般是先用 yum-config-manager 添加官方 docker 仓库,再执行 yum install docker-ce docker-ce-cli containerd.io。如果你所在网络环境访问官方仓库很慢,也可以换成国内可用的镜像源,但换之前最好确认来源可信,软件源这种入口一旦被篡改,上面跑的服务器可就不只是少个文件那么简单了。

装完之后先不要着急跑容器,先用systemctl start docker启动服务,再用systemctl enable docker设置开机自启。启动之后建议执行docker version看客户端和服务端版本是否都正常,再docker info查看存储驱动和文件系统信息。这里有一个新手经常踩的坑:docker version 客户端有输出,但服务端显示 cannot connect。看到这个提示,优先查 docker.service 是不是启动失败,而不是怀疑命令敲错。用journalctl -u docker看服务日志,比到处搜报错要快得多。

2.3 配置镜像加速器和默认日志策略

安装完成后别急着拉第一个镜像,我习惯先改一下 /etc/docker/daemon.json。这个文件是 docker 守护进程的配置入口,大部分运行期参数都可以在这里声明。最常见的配置是镜像加速源、默认日志大小和存储驱动。镜像加速不是必需的,但如果你发现自己 docker pull 一个公共镜像总是半路卡住,多半就是网络到远端仓库的链路不稳定,配置一个可用的 registry-mirror 能明显提升拉取成功率。地址可以去你所在云厂商的控制台拿,通常格式是 https://xxx,然后放到 daemon.json 里的 registry-mirrors 数组里。

日志策略也非常重要。默认情况下容器日志不限制大小,如果某个程序疯狂打日志,/var/lib/docker/containers 目录会一直膨胀,最后把磁盘撑满。我一般会在 daemon.json 里配置"log-driver": "json-file"和"max-size": "10m"、"max-file": "3",这样每个容器日志超过 10M 就会自动轮转,最多保留 3 个文件。改完 daemon.json 之后需要执行systemctl daemon-reload,然后systemctl restart docker。配置文件只要语法写错,docker 服务会起不来,所以改完一定要通过 docker info 检查一遍。

3. 镜像和容器的核心操作与实用参数

3.1 拉取镜像时要学会看版本标签

弄好环境后,第一个可以玩的东西就是拉镜像。很多人喜欢直接 docker pull 一个系统镜像,但这样做拉下来的往往是最新版本,不一定适合你的实验场景。我习惯先 docker search 或者去镜像仓库页面找到需要打的 tag,比如 ubuntu:22.04、nginx:stable-alpine,而不是直接用 latest。latest 的坏处在于它是个会漂移的指针,今天拉的是旧版,过段时间再 pull 可能就变成了新版,如果你的项目依赖某个特定的行为,版本漂移会成为隐患。

镜像拉到本地后,可以用 docker images 查看列表,能看到仓库名、标签、镜像 ID、创建时间和大小。说到镜像 ID,它其实是镜像唯一 ID 的前 12 位,用来在命令里定位某个镜像。如果你不需要某个镜像了,可以先查看有没有容器在用它,再执行 docker rmi 删除。删除的时候如果提示 image is being used by container,说明你的容器还停在那里,需要先 docker rm 删掉容器,或者加 -f 强制删除。我不建议对运行中的容器用 -f,因为会掩盖你忘记清理容器的事实,后续反复出现僵尸容器。

3.2 启动容器的常用参数和资源限制

当镜像准备好了,docker run 就是最常用的启动命令。最基本的写法是docker run -d --name nginx-demo -p 8080:80 nginx:alpine。这里 -d 表示后台运行,--name 给容器起一个容易识别的名字,-p 做端口映射,把宿主机的 8080 端口映射到容器内部的 80 端口。我一直建议每个容器都加 --name,不加名字的话每次都要查容器 ID,非常不直观,而且像 docker logs、docker stop 这种日常操作都要带容器 ID,记起来很痛苦。

除了端口映射,资源限制也是启动参数里的重头戏。如果不限制内存和 CPU,一个出问题的容器可能会把宿主机拖垮。我常用的限制参数是 --memory 256m、--cpus 0.5,分别限制内存上限和 CPU 使用上限。还有一个经常被忽略的参数是 --restart,它决定容器退出后如何重启。开发环境可以用 --restart unless-stopped,这样机器重启后只要容器之前没被手动停止,就会自动拉起来。线上环境则需要更细致的策略,不是所有容器都适合自动重启。

3.3 日志查询和进入容器调试

容器跑起来之后,怎么看它是否正常?我通常先 docker ps 看运行状态。如果容器存活但业务有异常,就用 docker logs --tail 100 容器名 去看最近 100 行日志。docker logs 的另一个实用参数是 -f,可以像 tail -f 一样实时跟着输出,调试回调类任务时非常有用。如果业务程序有写日志文件,也可以先用 docker exec 进容器直接看文件,不过前提是容器里有 shell 和常用命令。

进入容器的方法一般是docker exec -it 容器名 /bin/bash,如果容器里没有 bash,就用/bin/sh。进入之后可以查看进程、网络、文件路径等,确认外部反馈的问题到底出在哪。这里要提醒你,docker exec 是在运行中的容器里起了一个新进程,它和 docker attach 不一样。attach 会把当前终端绑定到容器主进程上,如果主进程是交互式 shell,退出后容器也会退出;exec 就灵活得多,exit 退出容器进程不受影响。所以日常调试我更推荐 exec,而不是 attach。

3.4 端口映射的原理和动态端口

端口映射看起来简单,但理解它的原理能避免不少误操作。docker run 的时候用 -p 是手动指定映射关系,格式是宿主机端口:容器端口。比如-p 8080:80,意思是访问宿主机的 8080 端口,流量会转发到容器的 80 端口。docker 实现这个转发主要靠 iptables 规则,所以你改了宿主机防火墙之后,如果发现容器端口访问不到,先检查 docker 创建的 iptables 链是否还在,很多防火墙重启操作会把 docker 链清掉导致端口消失。

除了手动指定端口,docker 还支持-P,它会将容器内所有暴露的端口自动映射到宿主机上随机端口。这个功能适合拿来快速验证,但不适合固定服务,因为每次创建容器分配的端口都可能不同。如果你需要给服务固定入口,还是用 -p 指定一个明确的宿主机端口更可靠。另外在写端口映射时要注意不要冲突,比如宿主机 8080 已经被别的进程占用,docker 启动容器时会直接报端口被占用,但不会帮你自动换端口。看到这种报错,可以用 ss -lntp 查一下宿主机是谁在监听。

4. 数据卷与挂载目录,解决容器数据持久化问题

4.1 容器删除后数据为什么不见了

玩一段时间 docker 你就会发现一个很反直觉的问题:容器里写了文件,然后 docker rm 删掉容器,文件也没了。这不是 bug,而是容器的设计行为。容器文件系统由镜像只读层和容器可写层叠起来,容器删除时这个可写层会被销毁。如果你在容器里写了一个配置文件,没有同步到宿主机,删除容器等于连配置一起删除。为了避免数据丢失,docker 提供了数据卷和 bind mount 两种方式,把数据保存在宿主机上。

数据卷是 docker 管理的一块存储区域,和容器生命周期解耦。创建方式是在运行容器时加-v 卷名:/路径,或者用docker volume create手动创建。docker 会把这个数据卷挂载到容器指定的目录。数据卷的一个显著优点是多个容器可以共享同一个卷,适合做日志聚合或配置文件同步。不过数据卷的位置在 /var/lib/docker/volumes 下面,直接管理起来不太直观,所以很多开发场景会更喜欢用 bind mount。

4.2 bind mount 的方式做本地开发

bind mount 就是把宿主机的某个目录直接映射进容器。启动容器时加-v /宿主机目录:/容器目录就可以。比如你本地有个代码目录 /data/www,想让 nginx 容器直接服务这个目录,就可以写docker run -d --name web -v /data/www:/usr/share/nginx/html -p 8080:80 nginx:alpine。这样宿主机上改代码,容器内马上能看到最新内容,非常适合开发调试。生产环境也可以用 bind mount 挂载证书和配置,但要注意目录权限必须匹配容器内运行的用户。

bind mount 有一个常见的权限坑:宿主机目录的所有者 UID 和容器内进程 UID 不一致时,容器里可能无法读写文件。比如镜像里的 nginx 以 nginx 用户运行,它的 UID 是 101,而宿主机目录是 root 所有,容器进程就只能读不能写。遇到这种情况,可以调整宿主机目录属主,或者在 docker run 时加 --user 参数指定以某个 UID 运行。不过 --user 会改变容器内进程的权限,不能盲目设置,得先想清楚业务到底需要哪个用户权限。保持 UID 一致通常是最省事的方式。

4.3 数据卷备份和多容器共享

数据卷虽然好用,但很多人忽略了对它做备份。因为卷独立于容器存在,如果你只把容器删掉,卷还在,但一旦把整个 /var/lib/docker 目录误删,所有卷数据也会跟着没。常规思路是用一个临时容器把卷打包出来,比如docker run --rm -v 卷名:/data -v 宿主机备份目录:/backup alpine tar czf /backup/volume.tar.gz /data,这样就把卷内容压缩到了宿主机指定位置。tar 命令完成之后容器自动删除,但备份文件保留下来,逻辑很清晰。

多容器共享同一份数据时,数据卷也有优势。两个容器可以通过同一个卷实现文件级别的协同。比如一个容器写应用日志,另一个容器负责收集日志并转发,只要它们挂载同一个卷,就不需要额外的网络文件传输。不过在共享时要注意并发写同一个文件可能导致内容错乱,应用自身最好做过文件锁或者按目录拆分。docker 容器之间并不是天生适合共享同一份数据,设计存储方案时要考虑实际业务写入模式。

5. 常见问题与排查技巧实录

5.1 权限问题:普通用户不想每次输 sudo

用 docker 最常遇到的权限问题就是普通用户执行 docker 命令会报 Permission denied。这是因为 docker 的守护进程默认以 root 运行,访问它需要 root 权限。简单粗暴的做法是给 docker 命令加 sudo,但如果项目里每天要敲几十条 docker 命令,每次都输 sudo 会很烦。更常见的做法是把执行 docker 命令的用户加入 docker 组,这样用户就不需要再提权。执行sudo usermod -aG docker 你的用户名,然后重新登录 shell 才能生效。

这里要注意,加入 docker 组的用户实际上获得了接近于 root 的能力,因为 docker 可以挂载宿主机目录、修改网络配置,甚至可以启动特权容器。所以不要把随便一个用户都加进 docker 组,特别是多人共用的服务器,至少要保证这个用户是可信任的管理员。如果你只是临时用一次,还是用 sudo 更安全。从安全角度讲,docker 组权限和 sudo 权限级别很像,千万不能当成普通账户权限来对待。

5.2 镜像拉取失败和 DNS 排查思路

新手最常见的报错是拉取镜像时卡住或提示类似 TLS handshake timeout 的信息。出现这种问题,大概率是网络访问远端仓库不稳定,尤其在一些跨网环境下特别明显。排查顺序我一般是先确认宿主机能否访问公网,用 ping 或 curl 测试一个固定站点,再看 DNS 配置是否正确。如果 DNS 有问题,容器内部解析域名也可能失败,会出现 temporary failure in name resolution。这时候检查 /etc/resolv.conf,确认 nameserver 地址没有被改写,或者手动指定容器的 DNS 参数。

如果网络没问题但拉取还是慢,那就考虑配置镜像加速器。在 daemon.json 里改 registry-mirrors 是推荐做法。配置完成之后不要光看配置文件里写没写,要看 docker info 输出的 Registry Mirrors 列表是否更新。没有更新说明守护进程还没重载配置,或者服务没重启。还有一个折中办法是使用 docker save 和 docker load 在离线环境迁移镜像,把镜像从一台有网络的机器导出成 tar 包,再拷到目标机器 load 进去,非常适合内网环境。

5.3 容器起来又秒退,怎么定位原因

容器一启动就退出,这个问题出现的频率非常高。最常见的原因是前台进程跑完就结束。docker 容器不会自己维持一个持续存在的系统,如果你启动的容器只执行了一条 echo 命令,命令执行完,容器回到 exited 状态。解决办法是让主进程保持前台运行。比如启动一个 nginx 容器,命令里不要写成后台启动方式,要保证 nginx 是前台进程,容器才会持续运行。很多软件镜像已经默认做好这一点,但你自己打包镜像时经常会踩中这个坑。

另外要看的执行结果。先用 docker logs 容器名 看输出,再 docker inspect 查看状态里的 ExitCode 和 OOMKilled 字段。如果 OOMKilled 是 true,说明容器被系统杀掉了,需要调高 --memory 限制。还有一个容易忽略的问题是启动命令写错路径,比如镜像里实际没有 /bin/bash,只有 /bin/sh,你强制用 bash 启动就会直接失败。这类问题通过 docker logs 能立刻看到 error,关键是养成先日志再猜测的习惯,能省下大量时间。

5.4 磁盘空间被容器日志和镜像占满

服务器跑一段时间之后,docker 占用的磁盘空间会越来越大,主要集中在镜像层、容器可写层和日志文件三块。查看占用最简单的是执行docker system df,它会列出镜像、容器、本地卷、构建缓存占到多少空间。如果发现日志占大头,可以检查 daemon.json 里的 log-driver 和 max-size 是否生效。如果没提前配置,已经存在的容器不会自动重建,所以要手动清理一下日志文件,或者重新创建容器。

清理空间最常用的命令是 docker system prune -a。它会把所有未使用的镜像、已停止的容器、无用的网络和构建缓存一次性清掉,释放空间非常明显。但这里要提醒,-a会把没有被容器使用的镜像全部删除,包括你可能想留作备份的镜像层。所以如果你只是想清理停止容器和空卷,就用不带 -a 的 docker system prune;想彻底清理再考虑加 -a。还有docker volume prune只清理未被任何容器使用的数据卷,要小心操作,因为卷里可能有重要数据。

6. 我玩 docker 后的几条实操心得

6.1 写一个小脚本记录常用命令

玩了几个月 docker 之后,我最大的体会是命令不要靠背,靠的是场景化理解。我写了一个简单的 shell 脚本来封装最常用的启动和清理命令,比如启动 web 容器、查看日志、清理未使用资源这些别名。好处是你不需要每次都咬牙切齿地敲一长串 docker run 参数,尤其参数多的时候,少一个 -v 或 -p 都可能导致完全不同的结果。封装脚本还能统一团队内部的操作习惯,比如日志大小、重启策略都放在脚本里,避免每个人手动加参导致线上配置不统一。脚本不算复杂,但实用性很强。

还有一个小技巧是给容器名称加上项目前缀,比如 app-web、app-worker,这样 docker ps 输出一眼就能看出这个容器属于哪个应用。容器名称本身只是一个标识,但命令里的匹配和查看会很方便。docker 容器数量一旦上到几十个,一个规范命名能帮你节省大量检索时间。

6.2 定期整理 docker 配置和目录

容器用多了之后,宿主机上的配置文件会比较分散。我习惯每次部署完都会把 daemon.json、docker 启动参数、挂载目录整理到一个本地文档里,至少记清楚哪个容器用了哪些端口和数据卷。这样做不是为了写文档而写文档,而是在容器重建的时候能快速恢复。很多时候容器出现问题,最简单的办法就是删掉重建,但重建需要知道原来的启动参数,如果你认真记录过,整个过程基本就是复制粘贴。

对于目录本身,我建议把持久化数据统一放在 /data 下面,而不是各自分散在乱七八糟的路径。这样备份、迁移和权限管理都会轻松很多。docker 本身不关心目录放在哪,但一个清晰的目录约定,会让排查问题的时候少走很多弯路。

6.3 下一步可以往哪个方向扩展

这篇教程带的还只是 docker 的基础玩法。当你熟悉了镜像和容器命令之后,下一步可以尝试自己写 Dockerfile,用 docker build 把应用打包成自定义镜像。还可以用 docker network 创建自定义网络,让不同容器之间通过容器名直接互通。再往后就是编排层面的事,比如用 compose 或集群工具管理多容器应用。这些都是同一个学习路径上很自然的方向,但前提是先把这篇里的基础操作多用几遍,容器生命周期真正熟悉了,后面的内容学起来会轻松很多。

每次踩坑之后我都会记一笔,慢慢就会形成一套适合自己习惯的工作流。如果你也是刚开始在 Linux 下玩 docker,建议别急着追新概念,先把这篇文章里的命令逐个跑通,遇到问题就翻日志、查配置,跑多了自然就有感觉。基础操作扎实之后,后面不管是做自动化部署还是环境隔离,都会顺手很多。

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

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

立即咨询