对于刚接触Docker的朋友来说,镜像(Image)和容器(Container)这两个词几乎每天都在打交道,但真正能把二者关系讲清楚的人其实不多。有人把镜像当成“安装包”,把容器当成“轻量虚拟机”,这种理解在初学阶段勉强能应付,可真到排查问题时,往往会被底层逻辑绊住。这篇内容围绕Docker最核心的镜像与容器管理展开,从底层概念、常用命令、实操细节到高频坑位依次过一遍,适合刚入门想系统梳理知识体系的读者,也适合已经用了一段时间、但总觉得“知其然不知其所以然”的朋友。
1. 先把底层逻辑捋清楚:镜像和容器到底是什么关系
很多教程会直接扔给你一堆命令,告诉你“docker run就完事了”。但如果你不理解镜像和容器的本质,后面遇到镜像删不掉、容器起不来、日志把磁盘撑爆这类问题时,会非常被动。所以我先把这两个核心概念讲透。
1.1 镜像:分层的模板,不是传统意义上的“安装包”
镜像(Image)是一个只读的、分层存储的文件系统模板。你可以把它理解为“打包好的环境快照”,里面包含了程序运行所需的操作系统基础库、运行时环境、应用代码和配置文件。
但这里有一个关键点:镜像是分层的,每一层都建立在前一层之上。Docker在拉取镜像时,如果本地已经存在底层,只需要下载新增的层,这也是为什么第二次拉取相似镜像时会感觉快很多。分层机制让多个镜像可以共享底层数据,既省磁盘又省带宽。
用一个类比来理解:镜像就像一份“蛋糕配方+半成品材料包”,本身不能直接吃,但它完整定义了最终成品的样子。你可以把这个材料包复制很多份,烤出多个一模一样的蛋糕。每一份“烤出来的蛋糕”,就是一个容器。
更贴近开发者的类比是“类与实例”:镜像相当于类,是静态定义;容器相当于实例,是运行时动态存在的对象。你无法直接运行一个类,必须实例化;同样,Docker无法直接运行一个镜像,必须创建容器。
1.2 容器:隔离的进程,不是“迷你虚拟机”
容器(Container)是基于镜像创建的运行实例。它和虚拟机最大的区别在于:容器直接共享宿主机的操作系统内核,并没有自己的内核。
传统虚拟机会虚拟化出一整套硬件设备,然后在上面跑一个完整的操作系统,所以虚拟机启动通常以分钟计,占用的内存和磁盘也很大。而容器只是通过Namespace做隔离、通过Cgroups做资源限制,本质上就是宿主机上的一个普通进程,只不过这个进程“看到”的文件系统、网络、PID等都被隔离了。
你可以把虚拟机想象成一套带全套家具的独立出租屋,每间屋子都有独立的水电表;容器则是共享同一栋楼水电基础的隔断间,各自的房间门一关,互不干扰,但水电网都是同一套基础设施。隔离性够用,成本和开销却小得多。
这也是容器“秒级启动”的根本原因:启动一个容器,本质上就是启动一个进程。
1.3 一条命令带你串起完整链路
当你执行docker run nginx时,Docker内部大致经历了以下几个步骤:
- 检查本地是否存在
nginx镜像。 - 如果不存在,则从配置的镜像仓库拉取该镜像。
- 基于镜像创建一个可写容器层。
- 分配一个网络接口(默认bridge模式),可能会分配IP地址。
- 启动容器进程,执行镜像中定义的启动命令。
所以,run是“创建+启动”的组合命令,不是单纯地“运行”。理解这一点后,你再去看docker create和docker start这两个命令,就会明白它们其实是把run拆成了两步执行,这在某些需要预创建但不立即启动的场景下非常有用。
提示:镜像本身是只读的,容器运行时的任何文件修改都发生在可写容器层。一旦容器被删除,这个可写层也一起消失。所以,容器内写数据不持久化,这句话是无数人踩过坑之后才真正理解的。
2. 镜像管理实战:拉取、查看、清理与瘦身
镜像管理是Docker日常使用中最频繁的操作,也是很多人最容易忽略细节的地方。拉一个镜像看起来再怎么简单,tag选错、架构不对、加速地址不生效,样样都能让你折腾半天。下面我把镜像这一块的操作彻底拆开讲。
2.1 拉取镜像:版本tag怎么选、加速地址怎么配
拉取镜像的命令是docker pull,但大多数新手只会用默认的latesttag。这里我要给出第一条建议:生产环境千万不要直接拉latest。
latest是维护者默认推送的版本,它随时可能变化。今天拉下来的镜像和下周拉下来的镜像,可能根本不是同一个版本。你的代码在一个版本的运行环境里测试通过,结果线上拉到了另一个版本的镜像,行为和表现可能完全不一样,排查起来非常痛苦。
正确做法是指定明确版本,例如:
docker pull nginx:1.25.3 docker pull mysql:8.0.36 docker pull python:3.12-slim版本后面还可能有后缀,比如slim、alpine、bullseye、bookworm,这些代表不同的基础系统或精简程度。alpine体积最小,适合追求镜像体积的场景,但要留意它使用的musl libc可能与glibc存在兼容性差异,某些编译型程序可能跑不起来。
关于镜像加速地址,Docker官方仓库在海外的默认连接速度确实不理想。你可以通过配置/etc/docker/daemon.json中的registry-mirrors项来指定镜像加速服务。这里提醒一个小细节:修改完配置后,需要执行systemctl restart docker或者重启Docker服务才能生效,而且只对之后拉取的镜像生效,已经拉下来的不受影响。
{ "registry-mirrors": ["https://你的加速地址"] }执行docker info,如果配置生效,在输出中能看到Registry Mirrors这一项。拉取时也可以添加--platform参数指定平台架构,例如:
docker pull --platform linux/arm64 nginx:1.25.3这在Mac或者ARM服务器上尤其重要,否则可能拉到错误架构的镜像,运行时报exec format error。
2.2 镜像查看、详情解析与标签管理
本地镜像列表用docker images查看,这个命令的输出包含仓库名、tag、镜像ID、创建时间和体积。其中镜像ID是简化后的短ID,完整ID可以用docker inspect查看。
docker images docker inspect nginx:1.25.3docker inspect是排查问题的利器。它输出的是一个JSON对象,里面包含了镜像的完整配置信息:架构、操作系统、每一层的历史记录、默认环境变量、工作目录、暴露端口、启动命令等。很多奇怪的问题都能在这里找到答案。比如容器启动后立刻退出,你先去inspect一下Config.Cmd,看看默认启动命令到底是什么。
关于tag,它本质上是一个指向镜像ID的引用,不是独立存在的对象。同一个镜像ID可以打多个tag,也可以随时新增。给已有镜像打tag用:
docker tag nginx:1.25.3 myregistry.example.com/nginx:1.25.3这里的完整名称格式是“仓库地址/命名空间/镜像名:tag”。如果要把镜像推到自己的私有仓库,tag前缀需要和仓库地址保持一致,否则docker push会报错。
2.3 镜像删除、导出导入与悬空镜像清理
删除镜像用docker rmi,后面跟镜像名或ID都可以。
docker rmi nginx:1.25.3如果这个镜像已经生成了容器,即使是停止状态的容器,Docker也会拒绝删除,提示“image is being used by container”。这时你有两个选择:先删容器再删镜像,或者用-f强制删除。但强制删除只是删除了镜像的标签引用,底层数据是否释放取决于是否还有其他引用,所以不要动不动就-f。
镜像导出和导入用于离线环境或在机器之间迁移镜像。导出的是一个完整的tar压缩包:
docker save -o nginx.tar nginx:1.25.3 docker load -i nginx.tar注意docker save和docker export的区别:save保存的是镜像,export导出的是容器的文件系统快照。前者保留了镜像的层级结构和历史信息,可以完整还原;后者是扁平化的文件系统,体积可能更小,但导入时镜像的元信息会丢失。离线传输镜像推荐用docker save,不要图省事用export。
清理悬空镜像(dangling image)也是一项日常维护工作。所谓悬空镜像,就是已经没有tag指向、但还残留在磁盘上的镜像层。这通常是因为重新构建了相同tag的镜像后,旧镜像变成无主的。执行:
docker image prune它会把所有dangling状态的镜像删除。加-a会进一步清理未被任何容器使用的镜像,但这一步操作需要谨慎,可能把你想留着的镜像一并删掉。
2.4 镜像瘦身:从几个G压到几百M的思路
应用越来越大之后,镜像体积问题就变得非常现实。一个动辄几个G的镜像,拉取慢、传输慢、构建也慢。几条实战经验如下。
一是尽量选择体积小的基础镜像。同样是Python运行时,python:3.12的完整版比python:3.12-slim大很多,python:3.12-alpine又更小。但也不要一味追求最小,有些镜像为了极小体积删掉了系统包管理器,后续想在容器里装调试工具就很麻烦。建议根据使用场景权衡。
二是多阶段构建。这是目前最推荐的做法。以编译型语言为例,第一阶段用完整的基础镜像安装编译器、依赖库,完成编译;第二阶段只把编译产物复制到一个干净的小体积基础镜像上。这样最终镜像里完全没有编译工具链。
FROM golang:1.21 AS builder WORKDIR /app COPY . . RUN go build -o myapp FROM debian:stable-slim WORKDIR /app COPY --from=builder /app/myapp . EXPOSE 8080 CMD ["./myapp"]三是利用.dockerignore排除无用文件。Docker构建时会把整个构建上下文发送给守护进程,如果你把node_modules、.git、日志目录等大文件包含进来,即使最终没被COPY进镜像,也会拖慢构建过程,占用不必要的带宽和临时存储。
四是合并RUN命令。每一条RUN指令都会产生一个新的镜像层,层数越多,镜像越大,构建时间越长。把多条RUN命令合并成一条,能显著减少层数。注意用&&连接,同时做好清理工作:
RUN apt-get update && \ apt-get install -y --no-install-recommends curl && \ rm -rf /var/lib/apt/lists/*3. 容器生命周期管理:从创建到销毁的全过程
镜像搞定了,接下来就是容器。容器的生命周期可以简单概括为:创建、启动、运行、进入、停止、删除。每一步都有对应的命令,也都有对应的坑。这一章我来完整过一遍。
3.1 创建并启动容器:run命令的关键参数详解
docker run是整个Docker中使用频率最高的命令,没有之一。一个看起来没那么复杂的启动命令,背后其实包含了很多参数。我挑几个最常用且容易出错的来拆解。
docker run -d \ --name myapp \ -p 8080:80 \ -v /data/myapp:/app/data \ -e DB_HOST=127.0.0.1 \ --restart=always \ nginx:1.25.3-d表示后台运行。如果不加,容器会以前台方式运行,终端会一直挂着日志,一按Ctrl+C容器就停了。写脚本、跑服务时几乎都要加-d。
--name给容器起一个固定名字。如果不指定,Docker会自动分配一个“形容词_人名”风格的名字,在管理和查看时非常不方便。在进入容器、查看日志、删除容器时,用名字比用容器ID好记很多。
-p做端口映射,格式是“宿主机端口:容器端口”。把宿主的8080端口映射到容器内的80端口,外部访问宿主机的8080就能到达容器服务。这里有一个常见问题:如果你只写-p 80,不写冒号左边,Docker会随机分配一个宿主端口,需要通过docker ps才能看到具体端口号。
-v做数据卷挂载,把宿主机的目录或文件挂载到容器内路径,实现数据持久化。这个后面专门讲。
-e设置环境变量,是向容器内传递配置最常用的方式。很多镜像的核心配置都是通过环境变量生效的,比如MySQL的MYSQL_ROOT_PASSWORD,不设置根本启动不了。
--restart=always设置容器的重启策略。这个参数在生产环境中非常重要。always表示无论容器因为什么原因退出,Docker守护进程都会尝试重启它。还有unless-stopped,表示除非管理员手动停止,否则都会自动重启。这两者的细微区别在于:手动stop容器后,always策略在Docker重启后仍会拉起这个容器,而unless-stopped不会。
3.2 查看容器状态、运行日志与容器内进程
查看所有容器,包括已经停止的,用:
docker ps -a不加-a只显示运行中的容器。输出中的STATUS列很有信息量:Up 5 minutes表示已运行5分钟,Exited (0)表示正常退出,Exited (1)表示异常退出。看到退出码不是0,就要开始排查了。
docker logs用来查看容器日志,这是排错的第一入口。
docker logs -f --tail 200 myapp-f是持续跟踪输出,--tail 200只显示最后200行。有些应用在启动阶段就打出了关键报错,但日志刷得太快,不限制tail的话根本看不过来。
docker top可以查看容器内部运行的进程列表,和宿主机上的top用法类似。这在确认容器内有没有僵尸进程、是否多进程混乱时很有用。
3.3 进入容器与文件交互:exec、cp、attach怎么选
最常用的进入容器方式是docker exec:
docker exec -it myapp bash-i保持标准输入打开,-t分配一个伪终端,两个参数合起来才能获得一个可以交互的shell。如果容器里没有bash,可以试sh,alpine镜像通常只有sh。
这里必须说明docker exec和docker attach的区别。attach是连接到容器的当前主进程,和容器共享同一个终端。如果你在另一个终端执行了命令,attach的终端也能看到输出。但更关键的是,当你用Ctrl+C退出attach时,会向主进程发送中断信号,可能导致容器主进程被终止。所以日常操作和排错,一律推荐exec,不要用attach。
容器和宿主机之间拷贝文件的命令是docker cp:
docker cp ./local.txt myapp:/app/ docker cp myapp:/app/logs/error.log ./error.log注意这个命令是在宿主机上执行,不是进入容器。方向搞清楚就不会搞错。
3.4 停止、重启、删除与善后
停止容器用docker stop,默认会给容器主进程发送SIGTERM信号,等待优雅退出。如果10秒后还没退,会强制发送SIGKILL。时间可以通过参数调整:
docker stop -t 30 myappdocker start可以重新启动一个已经停止的容器,但没有docker restart来得直接,后者等于“停止+启动”两步合一。在改了容器内配置、需要应用生效的场景下,docker restart是最常用的。
删除容器是docker rm:
docker rm myapp运行中的容器不能直接删,需要先stop,或者用docker rm -f强制删除。但-f会直接给容器发SIGKILL,可能丢失数据,慎用。
批量清理不用多说,两条命令值得记住:
docker container prune这条命令会删除所有已停止的容器,加-f跳过确认。结合了多个容器长期迭代的开发机上,这招能让docker ps -a的输出瞬间清爽很多。
注意:删除容器是不可逆操作。如果容器关联了未持久化的数据卷、或者容器内有未导出的文件,删除后数据就彻底没了。删之前养成
docker inspect看一眼的习惯,确认挂载和数据位置,再执行rm。
4. 数据持久化、网络互通与资源限制:把容器真正“管起来”
运行中的容器和宿主机之间不是只有端口映射这一层关系。数据怎么存、容器之间怎么通信、资源怎么限额,这三点决定了一个Docker环境能不能长期稳定运行。这一章把三件事分开讲透。
4.1 数据卷:为什么容器一删,写进去的数据全没了
前面已经提到,容器运行时的写入发生在可写容器层,这个层是临时的。容器删除后,虚拟机里的数据也跟着没了。要让数据活下来,必须用“挂载”的方式把宿主机目录或共享存储映射进容器。
常用的挂载方式有三种:
| 方式 | 特点 | 适用场景 |
|---|---|---|
| Bind Mount | 把宿主机指定目录挂载到容器内,简单直接 | 开发调试、配置文件目录 |
| Volume | 由Docker管理的存储卷,位置在 /var/lib/docker/volumes/ 下 | 生产数据持久化、跨容器共享 |
| tmpfs | 内存中的临时文件系统 | 临时缓存、敏感信息 |
Bind Mount的使用非常简单:
docker run -d --name myapp -v /data/myapp:/app/data nginx:1.25.3冒号左边是宿主机路径,右边是容器内路径。如果宿主机目录不存在,Docker会自动创建,但有时创建的目录权限不对,导致容器内无法写入,这个问题后面讲。
Volume方式推荐显式创建:
docker volume create mysql-data docker run -d --name mysql -v mysql-data:/var/lib/mysql mysql:8.0.36-v mysql-data:/var/lib/mysql这里左边不是路径,而是卷名字,Docker会把它存放在统一管理的目录中。用Volume的好处是不用关心宿主机路径的具体位置,备份、迁移时也更灵活。
不过在实际部署中,Bind Mount使用率依然很高,因为路径透明、方便直接把宿主机文件映射进去改配置。用Bind Mount时建议加:ro只读挂载,防止容器内误改宿主机文件:
docker run -d --name myapp -v /data/config:/app/config:ro nginx:1.25.34.2 端口映射与容器网络模式:如何让容器能被找到
默认情况下,容器拥有自己的网络命名空间,宿主机无法直接通过localhost访问容器。要让外部流量进入容器,最常用的就是-p端口映射。
先看端口映射的几个常见写法:
docker run -p 8080:80 nginx docker run -p 127.0.0.1:8080:80 nginx docker run -p 8080:80/tcp -p 8080:80/udp nginx第二种写法把宿主机端口绑定在回环地址上,外部无法访问,只有本机可以。在调试安全性要求较高的临时服务时,这种写法很实用。
Docker默认的网络模式是bridge,容器通过一个虚拟网桥docker0接入网络。如果不做端口映射,外部默认访问不到容器。如果使用--network host,容器直接共享宿主机网络栈,无端口隔离,-p参数会被忽略。如果使用--network none,容器只有回环接口,完全隔离网络。
容器与容器之间的通信,很多人直接在容器内用localhost访问另一个容器,这是不行的。每个容器有独立的网络命名空间。在自定义网络里,Docker自带的DNS服务可以通过容器名直接解析:
docker network create my-net docker run -d --name app1 --network my-net nginx docker run -d --name app2 --network my-net nginx docker exec app2 ping app1在my-net网络下,app2 里直接用容器名app1就能访问到对方。这是目前推荐的容器间通信方式,比通过桥接IP通信稳定得多。
4.3 资源限制:防止单个容器把整个宿主机拖垮
多容器在同一台机器上运行,最大的隐患是“一个容器吃完全部资源,导致其他容器甚至宿主机卡死”。解决方案是给容器设置资源上限。
docker run -d \ --name app \ --cpus=1.5 \ --memory=2g \ --memory-swap=2g \ nginx:1.25.3--cpus限制容器最多使用1.5个CPU核心。--memory限制内存上限为2GB。--memory-swap需要额外说明:如果只设置--memory而不设置--memory-swap,默认情况下容器可以使用的swap是内存限制的两倍;两个值都设置为2g,则禁止使用swap。对于内存型应用,我建议把--memory-swap设置为和--memory一样,也就是不用swap,避免性能闪崩。
查看资源使用情况:
docker stats这个命令展示所有容器的实时CPU、内存、网络和磁盘IO。如果想看单个容器,直接docker stats myapp。在容器出现异常高负载时,先跑这个命令确认资源占用,再决定要不要限制。
需要注意的是,如果容器超过内存上限,会触发内核OOM,容器被强制终止。有些程序的报错日志会显示Killed,基本就是内存超限导致的。
5. 常见问题与排查技巧实录
Docker用久了,你会发现报错就那么几类。我把自己踩过的、带新人时反复讲的问题整理成一份排查速查表,每一条都对应高频场景。
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 容器启动后立刻退出 | 启动命令失败、前台进程不存在 | docker logs看报错,docker inspect看ExitCode |
| 端口无法访问 | 映射未生效、防火墙拦截、服务未监听 | docker ps看端口映射,docker exec进容器确认监听 |
| 镜像拉取超时 | 网络不通、加速地址失效 | docker pull重试,检查加速配置,换公共加速地址 |
| 容器内时区错误 | 基础镜像默认UTC | 挂载/etc/localtime或用-e TZ=Asia/Shanghai设置 |
| 宿主机磁盘爆满 | 容器日志无限增长、悬空镜像和旧容器堆积 | docker system df查看空间占用,docker system prune清理 |
| docker cp失败 | 容器路径不存在、权限不足 | 进容器ls确认路径,检查宿主机文件权限 |
| exec进不去容器 | 容器内没有bash、容器状态异常 | 尝试sh,先docker ps -a确认容器还在运行 |
5.1 磁盘空间被占满:日志和悬空数据的双重夹击
这是最常遇到的问题。我用Docker三个月不到,就亲眼见过一台测试机因为容器日志暴涨导致磁盘100%占用。
Docker日志默认是写到宿主机文件里的,对应的目录在/var/lib/docker/containers/<容器ID>/*-json.log。如果应用日志量大,这个文件会持续膨胀。最有效的防线是给Docker配置全局日志上限。
在/etc/docker/daemon.json中加上:
{ "log-driver": "json-file", "log-opts": { "max-size": "50m", "max-file": "3" } }配置完成后重启Docker服务,新容器日志单文件超过50MB会自动切割,最多保留3个文件。这个配置对存量容器不生效,所以排障时如果某个容器日志已经几个G了,需要手动处理:
truncate -s 0 /var/lib/docker/containers/<容器ID>/*-json.log直接删文件也行,但为了让Docker保持对文件的句柄,推荐用truncate而不是rm。
5.2 容器启动后一直处于 Exited 状态
刚写Dockerfile或者刚拉一个镜像下来,启动容器后docker ps -a看到Exited (1),这种情况太常见了。第一步先看日志:
docker logs myapp日志能直接告诉你大部分原因。有几类高频报错值得单独提一下:
- 找不到启动文件或命令不存在,通常是镜像里没有写对
CMD或ENTRYPOINT。 - 前端进程退出。很多镜像的基础启动命令比如
nginx会以前台方式运行,但有些自定义镜像启动的是后台方式,脚本执行完容器进程就结束了,容器的生命周期也随之结束。Docker要求主进程必须是前台进程,这也是新手最容易理解偏差的点。 - 权限不足。挂载目录的宿主机权限如果只有root可写,容器内以非root用户运行时就会报
Permission denied。解决方式可以调整宿主机目录权限,或者镜像内使用useradd指定用户ID运行。
5.3 镜像拉取失败与架构不匹配
docker pull偶尔报manifest not found,大概率是tag写错了,或者该tag在特定平台架构下不存在。先用docker search确认镜像名,再确认平台参数。查看远程镜像的可用tag,可以借助docker manifest inspect命令:
docker manifest inspect nginx:1.25.3这个命令输出JSON信息,其中包含一个platforms数组,列出该镜像支持的架构。如果你的宿主机是ARM架构,但拉取的目标镜像只支持amd64,Docker通常会自动做模拟,但不是所有镜像都支持这种模拟运行,行为上也可能有差异。建议在明确知道架构需求时,直接加上--platform参数。
5.4 端口映射冲突与容器间通信不通
启动容器时报port is already allocated,表示宿主机端口已经被占用。先用docker ps确认是否已有容器占用,再用ss -lntp在宿主机上看端口被哪个进程占用。如果你确认某个容器已经删除但端口仍被占用,很可能是Docker网络资源没有及时释放,重启Docker服务一般能解决。
容器间通信不通的问题,多半是因为两个容器不在同一个自定义网络里。Docker默认bridge模式下,容器之间通过IP也能通信,但IP每次重建容器都会变。所以强烈建议:多个容器需要互相访问时,创建自定义网络,用容器名访问。这样即使容器删除重建,只要名字不变,网络引用关系就保持稳定。
5.5 时区、编码和系统语言的隐性坑
容器基础镜像默认时区是UTC,日志时间和宿主机时间差8小时,这在排查日志顺序时会非常困惑。解决办法很简单,在docker run时传入环境变量:
docker run -e TZ=Asia/Shanghai ...或者在Dockerfile里写好:
ENV TZ=Asia/Shanghai系统语言和编码类似。很多镜像默认没有中文locale,程序输出中文日志会变成乱码。在镜像里可以显式设置:
ENV LANG=C.UTF-8这些看似微小的细节,在真正遇到“时间不对”“日志乱码”的时候能省你半天排查时间。
5.6 实用清理组合:定期给Docker环境“大扫除”
日常维护中,我习惯定期执行这样一组命令:
docker system df先看空间都消耗在哪里。这一步会列出镜像、容器、数据卷分别占用的空间。然后:
docker container prune -f docker image prune -f docker volume prune -f三条命令分别清理停止的容器、悬空镜像和无主数据卷。需要注意docker volume prune会删除未被任何容器引用的数据卷,这个操作非常危险,如果你只是暂时停止容器,数据卷可能被误删。所以在生产环境或者有重要数据的机器上,一定先备份再清理。
提示:
docker system prune是总命令,默认会清理停止的容器、悬空镜像、无用的网络和构建缓存。加上-a会连未被容器使用的镜像一起清理,操作前务必看清提示内容,确认后再按y。构建缓存清理后,下一次构建会重新拉取或生成缓存,构建时间会明显变长,这一点也需要提前评估。
最后再分享一点个人体会。我带过不少新人,发现真正把Docker用明白的人,往往不是背命令最熟的人,而是先理解了“镜像分层”和“容器生命周期”这两条主线的人。命令记不住可以随时查,这两个核心逻辑不通,遇到问题就很容易靠瞎猜乱试。如果你现在刚入门,不要急着追求高级技巧,先把docker run、docker ps、docker logs、docker exec、docker inspect这几个命令用透,每个参数都搞清楚它解决什么问题,基本就超过了大部分“会用但不懂”的同行。后面再接触编排工具如Docker Compose时,你会发现所有概念都是相通的。