☰
Docker命令实战:理清镜像与容器关系,跑通MySQL与Redis主从
2026/10/1 18:05:14 网站建设 项目流程

先说个题外话。这系列文章写之前,我一直在纠结第二篇要讲什么。基础概念第一篇已经铺完了,但后台收到的留言里,“镜像命令怎么记”“容器删不掉怎么办”“为什么我的容器一启动就退”这类问题占了七成,而且几乎都指向同一个根源——没搞清楚镜像和容器之间到底是什么关系。所以这一篇我决定不按命令字典式的思路列目录,而是先带你把它俩的关系理顺,再把docker pull、docker run、docker exec、docker logs、docker rm、docker rmi这一整套核心命令串起来,配合 MySQL、Redis 主从两个真实场景,把命令落到实际环境里跑一遍。

这篇适合刚装好 Docker(无论是 Linux 服务器还是 Windows 上的 Docker Desktop)、会敲docker version、但还没能把镜像和容器玩明白的同学。看完你能独立完成“拉镜像、跑容器、进容器、查日志、挂数据卷、删容器、清镜像”这条完整链路,也能自己排查容器秒退、端口不通、镜像删不掉这类高频问题。

1. 先搞懂镜像与容器的关系,命令才记得住

很多人背命令容易忘,不是因为记性差,而是不理解命令操作的对象到底是什么。我见过不少同学在 Docker 里一连敲了好几天命令,脑中对“镜像文件放在哪个目录”“容器跑起来占多少磁盘”这些基本问题还是模糊的。这一节花点篇幅把模型讲清楚,后面所有命令都会变得“顺理成章”。

1.1 镜像像安装包,但它不是 ISO 文件

镜像(Image)本质是一个只读的、分层的文件系统快照。里面装的是你服务运行所需要的一切:操作系统基础文件(bin、lib、etc)、语言运行时、依赖库、业务代码,还有环境变量和启动命令的配置。你可以把它理解成一个打好了所有环境、随时可以交付的“应用安装包”。

但要注意一个高频混淆点:Docker 镜像不是你在网上下载的win11.iso、centos7.iso那种虚拟机的 ISO 安装镜像。热词里搜“win11镜像下载”“ubuntu官网镜像下载”“arm镜像下载”的同学,基本都是在找操作系统安装文件;而 Docker 里的“镜像”是一堆按层组织的 tar 压缩包和元数据,由 Docker 引擎管理,不会以单个.iso文件的形式躺在你的下载目录里。第一次用 Docker 的人经常跑来问我“我 pull 下来的镜像存哪儿了”——答案很简单:在 Docker 的存储驱动目录里(Linux 一般位于/var/lib/docker,Docker Desktop 则由它的虚拟机管理)。

分层结构带来两个明显好处:第一,基础层可以复用,你拉一个 Ubuntu 镜像和拉一个基于 Ubuntu 的 nginx 镜像,宿主机只需要存一份重复的底层;第二,不同镜像共享底层,拉取和磁盘占用都更高效。这个特性也解释了为什么你docker images看 SIZE 加起来很大,但实际磁盘占用没那么多——用docker system df才能看到真实占用。

1.2 容器是镜像跑起来的实例,和进程一个道理

镜像是静态的模板,容器(Container)是镜像运行后的实例。类比一下:镜像好比磁盘上的可执行文件,容器就是跑起来的一个进程。你可以从同一个可执行文件启动十个进程,同样也可以从同一个镜像启动十个容器,它们彼此隔离、互不干扰。

隔离是怎么实现的?底层是 Linux 内核的 namespace 和 cgroups。namespace 隔离文件系统、网络、进程 PID、用户等视图,让每个容器里看起来像一台独立的小机器;cgroups 则限制 CPU、内存、IO 用量。这就是热词里“容器资源隔离”的本质。如果你在 Linux 上跑 Docker,可以用docker top 容器名在宿主机视角直接看到容器里的进程——它们本质还是宿主机的进程,只是被 namespace 隔离了。新版 Docker 底层通过 containerd 来创建和调度容器,这也是为什么你会看到“containerd命令”这个热词。后面学 Kubernetes 时会跟 containerd 有更多交集,这里先有个概念就行。

1.3 写时复制:为什么容器可以随便改

你可能会问:镜像既然是只读的,那容器里怎么还能装软件、改文件?答案是写时复制(Copy-on-Write,CoW)。容器启动时,Docker 在所有只读镜像层之上挂一个“可写层”,你对容器内文件的修改全部落在这个可写层里,底层镜像不受任何影响。这带来两个极其重要的推论:

  • 多个容器共享同一个镜像时,你对其中一个容器的改动不会影响其他容器;
  • 容器的可写层是临时的,删除容器,可写层随之销毁,容器就“还原”成了镜像的初始状态。

所以永远不要拿容器当持久化存储。重要数据要么用命名卷(volume),要么挂载宿主机目录,这也是后面第 4 节要演示的重点。理解了这个模型,你就知道为什么docker rm能删掉容器而不影响镜像,为什么docker rmi删镜像时会提示“被容器占用”了。

2. 镜像操作命令:拉取、查看、标记、清理一条龙

搞清镜像和容器的关系以后,先从镜像这头的命令开始。镜像操作是整个 Docker 使用链路的源头:没有镜像,容器无从谈起。

2.1 docker pull:拉镜像时不建议盲用 latest

拉取镜像的命令是:

docker pull 镜像名[:标签]

默认不写标签时拉取latest。本地没有指定镜像时,docker run也会自动执行一次拉取,所以很多新手跳过了pull直接run,也能跑起来。

但日常使用中,我对latest是相当谨慎的。原因有两个:一是latest指向的内容会随项目发版漂移,这次拉的是 8.0,过几个月可能就是 8.4,复现环境时天然带不确定性;二是生产环境出问题时,你根本说不清当前镜像里跑的是什么版本。

我的习惯是明确指定标签,比如 MySQL 就拉mysql:8.0,如果要再严格一点,按摘要拉取:

docker pull mysql:8.0.36 docker pull mysql@sha256:xxxxxxxxxxxxxxxxxxxx

用 digest 锁镜像可以保证任何时刻拉到的都是同一份内容,适合对一致性要求高的场景。国内网络拉取镜像偏慢时,可以考虑在 Docker 引擎配置里添加 registry mirror,也就是镜像加速器,选自己信任的加速节点即可。这不是必须步骤,但确实能节省大量等待时间。

2.2 docker images 与 docker history:读懂镜像信息

docker images列出本地所有镜像:

docker images

输出的四列关键信息:REPOSITORY(仓库名)、TAG(标签)、IMAGE ID(镜像 ID)、SIZE(体积)。这里有个很多人不知道的细节——IMAGE ID 不是镜像内容的哈希,而是镜像配置(config)的哈希。同一个镜像无论你打多少个 tag,IMAGE ID 都是一样的。要看内容级别的摘要,得用docker inspect 镜像名找 RepoDigests 字段。

docker history是另一个容易被忽视的排查利器:

docker history mysql:8.0

它展示镜像从底层往上是由哪些层构建的,每一层对应 Dockerfile 里的哪条指令。当你怀疑某个镜像体积异常偏大时,用这命令能顺着层找是哪条 RUN 命令塞入了多余文件,比如没清理 apk/apt 缓存、临时文件没删除等。

2.3 docker tag、commit、rmi 与清理

docker tag不是复制镜像,而是给镜像加一个别名。应用场景最典型的是准备推送私有仓库前:

docker tag myapp:1.0 registry.example.com/myapp:1.0 docker push registry.example.com/myapp:1.0

docker commit是把一个容器的当前状态保存成新镜像。这个命令我要特别提醒一句:新手经常在容器里手动装了一堆软件、改了一堆配置,然后 commit 成镜像使用。短期救急可以,长期方案绝对不要依赖它,原因有三:commit 出来的镜像层是黑盒,Dockerfile 里的构建步骤完全丢失;过程中可能把临时文件、密码、密钥一起打进层里,有安全隐患;镜像不可复现,换个环境根本没法重建。我自己的做法是:只在紧急排查时用 commit 留个现场,事后马上补写 Dockerfile,把容器里的操作翻译成可重复执行的构建步骤。

删除镜像是docker rmi:

docker rmi 镜像名:标签

常见报错是image is being used by container,说明还有容器(即使已经 exited)引用这个镜像。正确顺序是先删容器再删镜像。悬空镜像(<none>标签)可以用docker image prune一键清理;如果磁盘告急,也可以docker system prune -a连没用到的镜像、构建缓存一起清。但这条命令要慎重——受网速和镜像源限制,你重新拉取一个几 GB 的镜像可能需要很长时间。清理前先用docker system df看看各类资源占用,再决定动手范围。

3. 容器操作命令:启动、进入、巡查、销毁一套流

镜像只是“原材料”,真正跑业务的是容器。这一节把容器生命周期拉通讲一遍,从创建到销毁,每步该用什么命令、为什么要这么用,都会说到。

3.1 docker run 的核心参数与一条标准启动命令

docker run是创建并启动容器的一条命令,相当于“create + start”。最常用的一条标准启动命令长这样:

docker run -d \ --name mysql-test \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0

逐个拆开讲:

  • -d:后台运行,日志不会直接糊满终端。没有-d时容器在前台运行,Ctrl+C 会直接终止容器主进程。
  • --name:给容器命名,后续操作都靠名字而不是随机 ID,可读性高很多。
  • -p 宿主机端口:容器端口:端口映射。外部访问宿主机 3306 的流量会被转发到容器的 3306。
  • -e:传入环境变量。注意环境变量是镜像本身约定好的接口,比如MYSQL_ROOT_PASSWORD就是 MySQL 官方镜像的初始化逻辑,换成别的不一定有效。
  • -v 卷名:容器内路径:挂载数据卷,实现数据持久化。
  • -it:交互式终端,常配合 bash 使用:docker run -it ubuntu bash。
  • --rm:容器退出后自动删除,适合临时测试、跑一次性任务。
  • --restart=always:Docker 重启或容器退出时自动拉起,生产环境常用。
  • --network host:让容器直接使用宿主机网络栈,不加 NAT 转发。热词里“宝塔内某个容器让他使用宿主机的网络环境”指的就是这种模式,但它会失去端口隔离,需要自己权衡。

还有个新手必踩的坑:容器内部看到的本机 IP 不是宿主机 IP。端口映射原理是宿主机把收到的流量通过 docker-proxy 或 iptables NAT 转给容器,所以外部调试要从宿主机访问“宿主机 IP + 映射端口”,而不是直接访问容器网段 IP。

3.2 docker exec 与 attach:进容器的两种姿势

进容器调试,最常用的是docker exec:

docker exec -it mysql-test bash

exec是在已运行的容器里额外启动一个新进程,主进程不受影响。你在容器里敲exit退出,只是退出这个新 bash,容器本身照常运行。日常调试、查看文件、执行命令,用exec就对了。

而docker attach是把当前终端连接到容器的主进程上:

docker attach mysql-test

它会直接呈现主进程的输出。要特别注意:在 attach 窗口按 Ctrl+C,信号会直接发给容器主进程,很可能把容器杀掉。所以我不建议新手用 attach 做日常操作,除非你明确需要监视前台进程的实时输出。

另一个现实问题:很多精简镜像(alpine 系列尤其常见)里没有 bash,甚至没有 sh。拉一个 distroless 镜像,你会连/bin/sh都找不到。这时候exec也救不了你,只能靠日志排查。这也是我选基础镜像时的一个习惯:调试型容器尽量保留一个 shell,生产镜像可以再精简。

3.3 docker ps、logs、inspect、stats 的排障姿势

容器里没有 systemd,服务“起没起”不能靠感觉,要靠这几个命令:

docker ps # 只看运行中的容器 docker ps -a # 看所有容器,包括已退出的 docker logs 容器名 # 看日志 docker port 容器名 # 看端口映射情况

docker logs基本是排查问题的第一选择,主进程打印的 stdout/stderr 都在这里。这里提醒一个日志配置:Docker 默认把容器日志以 json-file 形式写在宿主机磁盘,如果不限制大小,疯狂打日志的容器几天就能把 C 盘/D 盘写满。启动容器时加上日志轮转参数:

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

docker inspect输出容器完整配置和状态,是 JSON 格式。想看容器 IP,可以不用一长串 JSON 里翻,直接用 Go template 取值:

docker inspect -f '{{.NetworkSettings.IPAddress}}' 容器名

docker stats看 CPU/内存实时占用,docker top看容器内所有进程,遇到“容器里跑了什么奇怪东西”时一查便知。

3.4 start、stop、restart、rm:容器生命周期收尾

  • docker start 容器名:启动一个已存在的容器。
  • docker stop 容器名:停止容器,会先发 SIGTERM 给主进程,等待默认 10 秒宽限期,再发 SIGKILL 强杀。生产环境如果希望业务能优雅下线(关闭连接池、刷新缓冲、清理临时文件),最好让应用自己处理 SIGTERM。
  • docker restart 容器名:重启容器。
  • docker rm 容器名:删除容器。容器处于运行状态时得加-f强制删除。

注意区分热词里“启动docker”和“启动容器”两件事:systemctl start docker或打开 Docker Desktop,是启动 Docker 服务本身;docker start 容器名才是启动某个容器。我遇到过不少同学把这两个概念弄混,排查问题时越绕越远。

还要再强调一次:docker rm删掉容器后,可写层的数据就没了。应用装在容器里 VS 数据放在卷里,是完全不同的持久化等级。只要数据在卷里,容器随便删,重新 run 一个挂同一个卷,数据就回来了。

4. 真实场景演练:MySQL 与 Redis 主从的完整命令行

命令一个个讲完,还是要落到组合拳上。这一节用两个最经典的部署场景,把镜像、容器、网络、数据卷命令串起来走一遍。

4.1 场景一:用一条命令跑起 MySQL 8,并挂上数据卷

第一步,拉镜像:

docker pull mysql:8.0

本地没拉过的话,docker run时也会自动拉取。然后创建并启动容器:

docker run -d \ --name mysql-test \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0

这里-v mysql-data:/var/lib/mysql里的mysql-data是命名卷,Docker 会在宿主机某个目录下帮你管理这份数据。验证启动状态和初始化日志:

docker ps docker logs mysql-test

看到日志里出现“ready for connections”之类的字样,说明起来了。进入容器执行 SQL:

docker exec -it mysql-test mysql -uroot -proot123

试一下删掉容器、再重新启动一个挂同一数据卷的容器,数据是否还在:

docker rm -f mysql-test docker run -d --name mysql-test2 -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -v mysql-data:/var/lib/mysql \ mysql:8.0

只要卷没删,数据就是完好的。这是容器持久化最核心的实操认知。

4.2 场景二:Redis 主从,顺便把自定义网络用起来

Redis 主从是练容器网络的好案例。先用docker network创建一个自定义网络:

docker network create redis-net

为什么不用默认 bridge?因为默认 bridge 网络里,容器之间不自动支持容器名互访。而自定义网络里 Docker 自带 DNS 解析,容器可以直接用名字互相访问,命令写起来非常干净。

启动主节点:

docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7

启动从节点:

docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7 \ redis-server --slaveof redis-master 6379

注意这里从节点启动命令末尾直接追加了 Redis 参数,redis-server --slaveof redis-master 6379,容器里主进程就会带着这个参数启动。因为两个容器都在同一自定义网络里,redis-master会被解析成主节点的容器 IP,不需要关心网段。然后验证主从状态:

docker exec -it redis-slave redis-cli info replication

看到role:slave和master_link_status:up基本就说明链路通了。如果想加认证,主节点用--requirepass masterpass,从节点再补--masterauth masterpass即可。

4.3 场景三:文件进出、目录权限与 C 盘清理

容器里没有 vim、没有 scp,临时改文件怎么处理?先拷出来:

docker cp 容器名:/etc/nginx/nginx.conf ./nginx.conf

在宿主机上用熟悉的编辑器改好以后,再拷回去:

docker cp ./nginx.conf 容器名:/etc/nginx/nginx.conf docker restart 容器名

docker cp是宿主机和容器之间传文件最直接的方式,不要求容器里装任何额外的工具。

挂载目录时控制读写权限也很简单:

docker run -d --name nginx-test -p 8080:80 \ -v /opt/web:/usr/share/nginx/html:rw \ nginx:1.25

:ro是只读挂载,:rw是读写挂载。默认就是读写,但显式写明可以提醒自己和团队:这个目录是不是应该让容器随便改。热词里“docker容器怎么赋予目录读写权限”在命令层面就是这两个参数的事。另外,Linux 下宿主机目录挂进容器后,如果容器内进程以非 root 运行,可能出现权限 denied。要么调整宿主机目录属主,要么在容器启动时指定用户映射,要么给目录放宽权限,三种方式按安全要求选。

Windows 上用 Docker Desktop 时间一长,C 盘会越来越小。先看占用分布:

docker system df

这个命令会列出镜像、容器、卷、构建缓存各占多少。然后按需清理:

docker system prune # 清理停止的容器、悬空镜像、无用网络 docker image prune -a # 清理所有未被容器引用的镜像 docker builder prune # 清理构建缓存 docker volume prune # 清理无用卷(这条务必谨慎,卷删了数据真的没了)

这里对应的就是热词里“c盘清理命令”在 Docker 场景下的实际版本。

5. 常见问题排查:容器秒退、端口不通、镜像删不掉

最后把实践中遇到的高频问题集中过一遍,每个都给排查思路和解决命令,你可以把它当成速查表用。

问题现象可能原因排查与解决
容器启动后立即退出主进程没有前台运行或启动报错docker ps -a看退出状态码;docker logs 容器名看日志;交互式调试用docker run -it 镜像 bash
容器内没有 vim、telnet、ping基础镜像太精简用docker cp在宿主机改文件;临时装工具用apt-get update && apt-get install,但别污染镜像
镜像删不掉还有容器引用该镜像docker ps -a找到引用容器并docker rm,再docker rmi;悬空镜像用docker image prune
端口映射不通服务没监听、防火墙拦截、映射参数错误docker port 容器名看映射;docker logs查服务;telnet 宿主机IP 端口测通断
Docker Desktop 启动失败虚拟化未开启任务管理器“性能”页看虚拟化是否已启用,未启用进 BIOS 开 VT-x/AMD-V;检查 WSL2/Hyper-V 功能
宿主机访问不到容器服务服务只监听了 127.0.0.1进入容器确认服务监听地址,修改配置让服务监听 0.0.0.0 再重启

5.1 容器启动后秒退

这大概是新手问得最多的问题。docker ps看不到容器,一查docker ps -a发现是Exited (0)或者Exited (1)。

Exited (0)很可能是你启动了一个纯工具镜像(比如ubuntu)却什么也没让它干,默认命令是 bash,没有终端时 bash 读完 stdin 就直接退出,容器主进程结束,容器自然就退了。要做实验就加上-it:

docker run -it ubuntu bash

想要长期后台运行的调试容器,可以用tail -f /dev/null保活,但更推荐直接用真正的服务镜像。Exited (1)则是启动命令报错了,比如热词里“容器 centos7.9 启动 sshd 失败”就是典型:容器里配了 sshd 但没让它以前台方式运行,sshd 一后台化,主进程就结束,容器随即退出。容器里跑 sshd 必须前台启动:/usr/sbin/sshd -D。排查这类问题统一思路:先看退出码,再看日志,最后在容器里手动执行一次启动命令看报错。

5.2 容器里没有 vim/telnet,网络还能怎么查

精简镜像里没有 vim 是常态,网络排查也有绕开工具的替代方案。

宿主机上验证端口通断,最直观的就是 telnet:

telnet 127.0.0.1 6379

能连上说明端口通了,连不上会有明确的连接失败提示。Windows 上 telnet 命令如果提示无法识别,需要先在“启用或关闭 Windows 功能”里勾选“Telnet 客户端”,也可以用 PowerShell 的等价命令:

Test-NetConnection 127.0.0.1 -Port 6379

如果容器内实在没工具装,bash 本身还自带一个网络探测技巧——用/dev/tcp直接测试 TCP 连通性:

docker exec -it 容器名 bash # 容器内执行以下命令: timeout 3 bash -c "echo > /dev/tcp/目标IP/端口" && echo "端口通" || echo "端口不通"

这个方法不依赖 telnet、nc,只要有 bash 就能用,算是我压箱底的小技巧了。

5.3 镜像删不掉、端口不通、Docker Desktop 起不来

镜像删不掉的完整处理流程:

docker ps -a | grep 镜像名 docker rm 对应的容器ID docker rmi 镜像名

如果docker images里出现一堆<none>的悬空镜像,直接:

docker image prune

不建议无脑docker rmi -f强删。强制删除会把镜像层从磁盘移除,哪怕还有容器正引用它,容器可能直接跑不起来。只有在你确认这个镜像是垃圾时,才用强删。

端口不通的排查顺序,从外到内走一遍:先确认容器在跑(docker ps),再确认端口映射正确(docker port 容器名),接着从宿主机侧测试(telnet),通的话问题可能在容器内服务监听地址,不进容器看看服务到底监听在哪个地址上(有些框架默认只监听 127.0.0.1),不通的话检查防火墙和安全组是否放行了对应端口。

最后说 Docker Desktop 在 Windows 上启动失败的一件事。如果你看到Virtualization support not detected这类提示,九成是系统虚拟化没开。先打开任务管理器,切到“性能”页,看底部“虚拟化”是否显示“已启用”;如果是“已禁用”,需要进 BIOS/UEFI 把 Intel VT-x 或 AMD-V 打开。Docker Desktop 依赖后端虚拟化,要么走 WSL2,要么走 Hyper-V,老版本会直接要求 Hyper-V。还有一个小概率情况是 WSL2 内核太旧,跑一下wsl --update更新内核也能解决。这类问题跟镜像和容器命令无关,但卡在第一步确实什么都做不了,我在这篇里一起写了,给新同学避坑。

这套命令用下来,我个人体会最深的一点是:不要试图背参数,而是先想清楚你操作的对象是镜像还是容器,再想清楚你想让它变到什么状态。一旦这个模型在脑中立起来,命令的记忆成本会断崖式下降。另外一个小习惯分享给你:第一次用某个镜像时,先docker inspect看一眼它的默认启动命令和暴露的端口,再动手run,能少踩很多配置不生效的坑。下一篇系列文章我打算聊 Dockerfile 的编写规范和私有仓库的搭建,那部分才是把“能跑”变成“可维护”的关键一步。如果你在练习这组命令时遇到别的怪问题,欢迎把报错信息发出来,我看到了都会回。

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

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

立即咨询