01菜鸟:装好跑起来

第 1 关 装好跑起来
本关的产出:一个装好的运行时,一条从拉镜像、起容器到在浏览器里打开的完整链路,以及查看、进入、停止、删除这一套日常操作。
1.1 先搞清这条命令发给了谁
问题的来源:Docker 分成两半。敲下的 docker 命令是「客户端」,真正拉镜像、起容器的是常驻在后台的「服务端」(也就是守护进程)。客户端每执行一条命令,都得先知道自己该把它发给谁。
两个角色,这里说清楚一次,后面一直用。
服务端是那个常驻后台的守护进程。它掌握全部状态:有哪些镜像、有哪些容器、每个容器在跑什么、网络怎么连、数据存在哪。活都是它干的。
客户端就是敲下的
docker命令。它自己什么都不做(无状态),只干三件事:把命令翻译成一个请求,发给服务端,再把返回的结果打出来。最能说明问题的一点:客户端不掌握任何运行状态。敲
docker ps的那一瞬间,它并不知道这台机器上有几个容器,它只是问服务端「现在有哪些容器」,然后照着答案印出来。所以客户端不止一个。命令行是一个,图形界面是一个,编辑器里的插件也是一个——它们问的是同一个服务端。这正是上下文必须存在的原因:客户端可以有好几个,但「问谁」只能有一个答案。
想亲眼看到这两个角色,敲一次
docker version:它会打印两段,第一段标题是Client,第二段是Server。
在「上下文」出现之前,这个「发给谁」只能靠终端里的一个设置来指定(环境变量 DOCKER_HOST)。它有三个麻烦:
- 它是终端窗口的状态,不是系统里的一件东西:只对当前这个终端窗口有效,另开一个窗口就没了;想让它一直有效就得写进终端的配置文件,然后过几个月连自己都忘了配过。
- 没有清单:「这台机器上到底有几个 Docker 可以连」这个问题无解,目标只存在于记忆里。
- 最要命的是会连错:忘了当前指向哪,对着生产环境的守护进程敲下一句
docker rm。在上下文这个概念出现之前,这是很常见的事故。
在 macOS 上,这个问题一定会撞上:Docker Desktop 的守护进程并不直接跑在 Mac 上,而是跑在 Docker 自己管的一台小 Linux 虚拟机里,Colima 也是同样的路数。两者的守护进程各自守在不同的位置,客户端要连的地址并不相同。两个都装的时候,「这条命令到底发给谁」必须有个明确答案。
上下文就是为这件事设计的:它把「连哪个守护进程」以及连上它所需要的全部设置,打包成一个有名字、会被记住的东西,存在 ~/.docker/ 下。于是:
- 可枚举:
docker context ls把「目标」从记忆里的东西变成系统里列得出来的东西。 - 可切换:
docker context use <名字>显式切换,切完一直有效。 - 可按单条命令覆盖:
docker --context <名字> ps只影响这一条命令,不动全局设置。 - 不只终端能用:编辑器插件、图形界面工具都读它;而那个终端设置只有终端有。
它真正给人的东西:从此 docker 命令不关心守护进程在哪儿。跑在本机的虚拟机里,或者跑在另一台服务器上,对它来说都只是「一个上下文」。在同一台 Mac 上敲 docker ps,列出来的可以是远端那台机器上的容器。
一个关键性质:上下文是纯客户端的概念。守护进程完全不知道它存在,它只是客户端本地的一份连接配置。
常见的三个选择:
- Docker Desktop:官方方案,带图形界面,自带那台小 Linux 虚拟机,资源占用偏大。
- Colima:开源命令行方案,同样是虚拟机,但没有图形界面,内存占用小。
- OrbStack:Mac 上性能最好,商业软件,个人免费。
动手:
docker context ls—— 看有哪些上下文,带*的是当前在用的那个。docker context use <名字>切到另一个,再跑一次docker ps对比。- 切回来。
要观察:
docker context ls里default那条的描述写着Current DOCKER_HOST based configuration—— 它就是把上面说的「老办法」原样保留成了一个上下文,从这能看出这个机制是怎么长出来的。- 切到 Colima 那边的上下文而它又没启动时,
docker ps报的是连不上,而不是「没有容器」。这两句话的区别,就是「客户端连不上服务端」和「服务端上没东西」的区别。
易踩的坑:
- 带
*的是「当前在用」,不是「默认」。名字叫default的那个,很可能恰恰不是当前在用的。 - 切换上下文之后,之前
docker ps能看到的容器会「消失」——它们还在,只是在另一台虚拟机上。
1.2 验证安装,跑出 hello-world
目标:确认这套装好了,并看清一次 run 到底做了几件事。
- 验证:
docker version看客户端与服务端两段版本号,docker info看服务端信息(存储驱动、内核版本、总内存)。 - 跑一次:
docker run hello-world。 - 拆开这一步:去找镜像 → 没有就从仓库拉 → 用它创建一个容器 → 启动容器里的进程 → 进程输出那几行字 → 进程退出、容器停止。
要观察:
docker ps看不到刚跑完的容器,docker ps -a才看得到 —— 因为它已经退出了。- 退出码是
0,表示正常结束,不是失败。
易踩的坑:第一次拉镜像会卡在 Unable to find image ... locally 一会儿,那是真的在下东西,不是卡死。
1.3 拉下镜像、跑起来、访问它
目标:跑一个不止转瞬即逝的容器,从浏览器里访问到它。
- 先把镜像拉下来:
docker pull nginx。拉完再敲一次同样的命令,会立刻返回——镜像已经在本地了,不用再下一次。 - 起容器:
docker run -d -p 8080:80 nginx。 -d:后台运行,命令立刻返回,终端不被占住。-p 8080:80:把宿主机的 8080 端口转发到容器的 80 端口。顺序是「宿主<容器>容器>」,这里最容易记反。- 访问:浏览器打开
http://localhost:8080,看到 nginx 的欢迎页。
要观察:docker ps 的 STATUS 与 PORTS 两列分别写了什么。
易踩的坑:
- 第 1 步其实可以省:本地没有这个镜像时,
docker run会自己去拉。单独拉一次的用处是把「拉」和「跑」分成两步——出问题时一眼看得出是压根没拉下来,还是拉下来了但跑不起来。 - 8080 已被占用时会报
bind: address already in use,换个宿主端口即可(容器的 80 不用动)。 - macOS 上必须显式做端口映射。Docker Desktop 的「宿主机」其实是那台 Linux 虚拟机,不写
-p,端口就不会出现在 Mac 上,浏览器里自然打不开。
1.4 看日志与进容器
目标:容器是黑盒,学会从外面看它、钻进它里面。
- 看输出:
docker logs <容器ID>,加-f持续跟随。 - 钻进去:
docker exec -it <容器ID> bash,进去之后ls、cat、改文件都跟普通 Linux 一样。 exec与attach的区别:attach接的是容器主进程的标准输入输出,在里面按Ctrl+C会把主进程一起送走、容器跟着停;exec是另开一个进程,退出它不影响容器。
易踩的坑:
docker logs给的是容器里主进程的输出,不是 Docker 自己的日志。主进程不往标准输出写东西时它就是空的——哪怕程序正往自己的文件里勤勤恳恳地写日志。守护进程自己的日志是另外一套。exec只对运行中的容器有效,停了的容器要先start。-it两个参数缺一不可:-i保持输入,-t分配终端,少一个就没有交互。- 精简镜像(后面会遇到的 alpine、distroless)里没有
bash,得换成sh或者根本没有 shell。
1.5 停掉、删掉、找回
目标:把「容器」和「镜像」这两样东西的关系理顺。
- 停与启:
docker stop <ID>、docker start <ID>、docker restart <ID>。停掉的容器还在,能再启。 - 删容器:
docker rm <ID>,加-f可以强删运行中的容器。 - 删镜像:
docker rmi <镜像>。 - 顺序:容器是镜像的实例,所以先删容器,再删镜像。
易踩的坑:
docker rm直接删运行中的容器会报错,要么先stop,要么加-f。docker rmi报image is being used by ...时,不是权限问题,是有容器(哪怕是已停止的)还指着它。- 容器删掉之后,写进它自己文件系统里的改动一并消失——这是第 4 关要解决的问题。
1.6 看看这些东西占了多大地方
目标:知道怎么查、怎么收,别让镜像和缓存把磁盘悄悄吃满。
- 查:
docker system df,分别看镜像、容器、卷、构建缓存各占多少。 - 收:
docker system prune清掉已停止的容器和没在用的网络;-a连没被任何容器引用的镜像一起清。
要观察:docker system df 里 RECLAIMABLE 那一列,就是「现在能收回多少」。
易踩的坑:prune -a 会把构建缓存一起清掉,下次构建会明显变慢。清之前先看清楚。
本关命令速查
容器生命周期与日常操作,见 命令速查表 · 容器生命周期。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!





