新来的同事问我:Docker 命令这么多,是不是得靠每天背几条才能记住?我说不用背,你只要把镜像、容器、数据卷、网络这四类对象的关系想清楚,剩下的所谓 Docker 常用命令全是顺水推舟的事。这篇文章就按这个思路重新梳理一遍,不搞命令大全,只讲我在实际项目里真正每天都会用到的那一批,顺带把那些"命令敲对了但结果不对"的坑也摊开说说。无论你是刚接触 Docker 的学生,还是已经在开发环境里跑过几个容器、但总觉得命令不成体系的一线开发,应该都能从里面拿到一点能直接用的东西。
1. 先把镜像、容器、数据卷、网络的关系理清,命令才有归属感
很多人觉得 Docker 命令难记,核心原因不是命令本身复杂,而是不知道每条命令到底在操作哪个对象。docker images 看的是镜像,docker ps 看的是容器,docker volume ls 看的是数据卷,docker network ls 看的是网络。每类对象都有自己的专属命令前缀,而所有命令的动作无非就是增、删、改、查这四类。你只要在脑子里给对象分好类,命令的排列组合就自然有了规律,硬背反而是最低效的办法。
1.1 镜像:一个只读的启动模板
镜像可以粗暴地理解成一个打包好的"系统盘加软件安装包"。它里面装好了操作系统的基础层、运行时环境、应用程序代码和配置。docker pull nginx 就是从远程仓库把这个模板拖回本地,docker images 是查看本地已经有了哪些模板。
这里有个关键点必须记住:镜像是只读的。你启动容器后在里面改的任何文件,都不会写回镜像本身。这也是 Docker 设计里最精髓的地方之一——镜像保证了环境的一致性,不管你在哪台机器上拉取同一个镜像,跑出来的基础环境都是一模一样的。
我见过不少新手在容器里改了配置文件,然后以为镜像也跟着更新了,下一次 docker run 又回到老样子,然后就懵了。搞清楚"镜像只读,容器可写,写的内容在容器销毁后默认消失"这个三角关系,后面很多问题都能少踩。
1.2 容器:镜像运行后的实例
如果说镜像是一张光盘,那容器就是插进光驱后真正跑起来的那台机器。同一个镜像可以同时启动多个容器,彼此互不影响,这就是"实例"的含义。
docker run 是创建容器并启动,docker ps 是查看当前正在运行的容器,docker ps -a 是把所有历史容器(包括已经退出的)都列出来。删除一个镜像之前,系统会检查有没有容器还在用它,如果有就会拒绝删除。很多刚接触 Docker 的人在这条报错上卡住:error while removing network / conflict: unable to remove repository reference,其实就是没搞清楚镜像和容器的持有关系。
1.3 数据卷和网络:容器从来不是孤立的
镜像保证了环境一致,容器提供了运行实例,但容器本身是无状态的——这句话的意思是,容器一旦被删除,里面产生的数据就全没了。要保留数据,就得把存储从容器里"拿"出来,这就是数据卷存在的意义。docker volume ls 列出所有数据卷,docker network ls 列出所有网络。容器之间要通信、容器要对外暴露端口,都离不开网络配置。
把这一层想明白之后,再回头看命令就顺了:docker xxx 这个格式里,xxx 前一半是对象类别(image、container、volume、network),后一半是动作(pull、run、ls、rm),所有常用命令都跑不出这个框架。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 镜像命令:拉取、查询、删除、构建,一条龙
镜像这一块我实际使用频率最高的命令大概就是 pull、images、rmi、build、prune 这五个。把它们串起来,就是一个完整的镜像生命周期管理流程。
2.1 拉取镜像与查看镜像
docker pull 是最基础的动作。需要注意的是 tag 的概念,也就是版本标签。很多人直接 docker pull nginx,拿回来的是 latest 标签,但这并不代表它就是官方最新发布的稳定版,latest 只是默认标签,很多项目团队会直接把某个发布版本打上 latest,所以它在不同项目里的含义可能完全不同。我的习惯是明确指定版本,比如 docker pull nginx:1.25,这样即使过了一年再回头看部署记录,也知道当时用的是哪个版本,排查问题的时候能少绕很多弯路。
docker images 查看本地镜像时会列出仓库名、标签、镜像 ID、创建时间和大小四个核心信息。镜像 ID 在删除和构建时都会用到,不过用不到完整 ID,前三四位就足够定位了。
2.2 删除镜像与清理空间
docker rmi 后面跟镜像名或镜像 ID 就能删除镜像。但正如前面说的,如果有一个容器还在基于这个镜像运行,删除就会失败。所以删除镜像之前的固定动作是:先 docker ps -a 看看有没有相关容器,有就先把容器删掉,再删镜像。
我自己的测试机吃过一次亏:几个月没清理,docker images 里躺着几十个旧镜像,磁盘直接满了。后来养成了定期清理的习惯,用 docker image prune 清掉所有悬空镜像(即标签为 none 的镜像),这些多出现在反复重新构建之后。如果要连没在用的容器、网络、构建缓存一起清掉,就用 docker system prune。注意这个命令会问你是否继续,因为清理的内容包含构建缓存,可能会导致下一次构建重新下载依赖、变慢不少,所以在生产环境上要谨慎使用。
2.3 构建自定义镜像
docker build -t my-app:1.0 . 是构建自定义镜像的标准姿势。你在当前目录下放一个 Dockerfile,里面写好基础镜像、拷贝文件、安装依赖、暴露端口、设置启动命令这些步骤,然后 build 就会按顺序执行。
这里有一个非常容易被忽略的概念:构建上下文。命令最后那个点代表当前目录,这个目录里的文件会被全部发送给 Docker 引擎作为构建上下文。所以尽量只把需要的东西放在构建目录里,不然一个巨大的 node_modules 或者 build 目录会把构建过程拖得很痛苦。
-t 参数是给镜像打标签,命名我推荐统一用 项目名/服务名:版本号 的格式,比如 shop-api:v1.2.0,这样 docker images 里扫一眼就知道谁是谁。
3. 容器命令:常用参数背后的运行逻辑与日常运维操作
容器命令是每天用得最多的部分。docker run 一条命令的参数组合可以很多,但真正常用的其实就那么几个。理解了它们背后的运行逻辑,你就能根据场景自己组合,而不是照着文档抄。
3.1 从 docker run 说起:参数决定了容器的"活法"
先看一条我最常用的完整命令:
bash复制docker run -d \
--name nginx-demo \
-p 8080:80 \
-v /home/user/html:/usr/share/nginx/html \
--restart always \
nginx:1.25
拆开看每个参数的用途:
- -d:后台运行,不加它的话容器会在前台跑,直接占用当前终端,Ctrl+C 一按容器就停了。
- --name:给容器起名字,不加的话 Docker 会随机生成一个名字,日志和操作都会变得难找。
- -p 8080:80:端口映射,宿主机 8080 端口转发到容器的 80 端口。
- -v /home/user/html:/usr/share/nginx/html:挂载目录,把宿主机上的文件夹映射进容器,后面细讲。
- --restart always:重启策略,容器挂了或服务器重启后会自动拉起,对长期运行的服务来说这条很关键。
端口映射的写法是"宿主机端口:容器端口",我刚学的时候经常写反,结果访问宿主机端口发现不通,折腾半天才发现是方向搞反了。容器端口是应用本身监听的端口,宿主机端口是你希望对外提供服务的那个端口。
3.2 生命周期管理:ps、stop、start、restart、rm
docker ps 列出运行中的容器,会显示容器 ID、镜像、创建时间、状态、端口映射和名字。docker ps -a 则把已经退出的容器也列出来,排查"我之前是不是跑过一个容器"这种问题时特别有用。
docker stop 是优雅停止,给容器里主进程发送停止信号,等它自己退出;docker start 是启动一个已存在的容器;docker restart 就是重启。这三条命令配合 --name 用是最省事的。
删除容器用 docker rm,注意它默认只能删已停止的容器。要删一个还在运行的容器,要么先 stop 再 rm,要么直接 docker rm -f 强制删除。我自己在开发环境经常用 -f,因为省事,但生产环境我会先 stop,看看日志确认没有异常再删,毕竟 -f 等于直接掐断进程。
3.3 进入容器、看日志、拷文件:日常三件套
bash复制docker exec -it nginx-demo bash
这条命令进入容器内部,拿到一个交互式 Shell。-it 是 -i 和 -t 的组合:-i 保持标准输入打开,-t 分配一个伪终端。进容器之后就能像在普通机器上一样执行命令排查问题。不过要注意有相当一部分官方镜像为了精简,不带 bash,比如很多基于 Alpine 的镜像只有 sh。此时把 bash 换成 sh 就行,这也是新手经常遇到 command not found: bash 的原因。
日志是排查线上问题最重要的入口:
bash复制docker logs -f --tail 200 nginx-demo
-f 是实时跟踪输出,--tail 200 表示只看最近 200 行。调试的时候先 --tail 50 扫一眼,发现问题再 -f 跟一下,比直接从头刷到底要高效得多。
docker cp 可以在容器和宿主机之间双向复制文件。把容器里的日志或配置拷出来:
bash复制docker cp nginx-demo:/var/log/nginx/access.log ./access.log
也可以把本地文件塞进容器。不过要注意,docker cp 适合临时排查和应急处理,如果你发现自己经常往容器里拷文件去改配置,那大概率是镜像构建或数据卷设计出了问题,正确的做法是把配置做到镜像里,或者用挂载把配置目录映射出来。
4. 持久化与网络:从单机实验走向可用环境的关键配置
如果只是本地跑个实验,不挂数据卷、不配网络其实也能跑。但一旦你想让容器承担真实工作,持久化和网络就是绕不过去的基础设施。这一章讲清楚最容易让人翻车的两块。
4.1 数据卷的两种姿势:named volume 与 bind mount
数据卷解决的核心问题只有一个:容器删了数据不丢。实现方式有两种,我列个对比表来看:
| 对比项 | named volume(命名卷) | bind mount(绑定挂载) |
|---|---|---|
| 创建方式 | docker volume create 或 run 时自动创建 | 直接把宿主机路径映射进去 |
| 命令示例 | -v app-data:/app/data | -v /home/user/app:/app |
| 宿主机文件位置 | Docker 管理的目录,一般不好直接找 | 你自己指定的任意路径 |
| 备份迁移 | 需要额外操作 | 直接拷贝宿主机目录即可 |
| 适用场景 | 数据库数据、应用产生的持久化文件 | 开发环境代码同步、配置文件覆盖 |
bind mount 在开发环境用得最多,因为你可以直接在宿主机编辑器里改代码,容器里立刻生效,不用重新构建镜像。我在前面的示例命令里已经用了一次:把宿主机某个静态文件目录挂载进 Nginx 的网页根目录,这样改页面连容器都不用重启。
对数据库这类应用,我强烈建议用 named volume,比如跑 MySQL 就指定 -v mysql-data:/var/lib/mysql。数据由 Docker 统一管理,想备份就用 docker run --rm -v mysql-data:/data -v $(pwd):/backup alpine tar czf /backup/mysql-data.tar.gz -C /data .,干脆利落。
4.2 网络模式:默认 bridge 与自定义网络
默认情况下,所有容器跑在名为 bridge 的虚拟网桥上,容器之间可以通过 IP 互通,但 IP 是动态的,每次重启可能变化。所以多容器协作时,正确的操作是创建一个自定义网络,让容器通过名字互访。
bash复制docker network create my-net
docker run -d --name app1 --network my-net my-app
docker run -d --name app2 --network my-net my-app
这里有个隐藏的坑:多个容器挂在自定义网络里时,互相访问的地址是容器名,而不是 localhost。比如 app1 要访问 app2 的 8080 端口,应该用 app2:8080,因为每个容器有自己的网络命名空间,localhost 指自己。我记得第一次带项目时,把两个容器放同一个网络里,代码里还是写 localhost,结果连不上,排查了一个多小时才意识到问题。这个错误太典型了,值得单独拎出来讲。
宿主机的端口映射在自定义网络下依然有效,从外界访问仍然用 -p 映射出来的宿主机端口。
4.3 用 Compose 管理"一组容器"时的常用姿势
当项目需要同时跑好几个容器时,一条条写 docker run 就太折磨人了。这个时候会用 docker compose 来管理。在项目目录下写一个 docker-compose.yml,把每个服务、数据卷、网络、端口映射都声明好,然后:
bash复制docker compose up -d # 构建并后台启动
docker compose ps # 查看服务状态
docker compose logs -f # 跟踪所有服务日志
docker compose down # 停止并移除容器
Compose 最大的价值是把"一组容器的运行拓扑"用代码固化下来,换一台机器也能复现同样的环境。我现在几乎所有项目都用 Compose 起步,哪怕只有一个容器,因为后续加服务、改配置都方便得多,不用靠记忆去拼 docker run 参数。
5. 一个从零到能访问的实操串联:把常用命令过一遍
理论说了不少,来一次完整的实操串联。场景很简单:在本机用 Nginx 跑一个静态页面网站,访问 8080 端口能看到页面,然后对页面做一次修改,感受下数据卷的作用,最后再清理掉。
先用一条命令创建挂载目录并写入测试页面:
bash复制mkdir -p ~/site/html
echo '<h1>Hello Docker</h1>' > ~/site/html/index.html
然后启动容器:
bash复制docker run -d \
--name site-demo \
-p 8080:80 \
-v ~/site/html:/usr/share/nginx/html \
nginx:1.25
启动后配两条验证命令:
bash复制docker ps | grep site-demo
curl http://localhost:8080
看到页面返回 Hello Docker,说明链路已经通了。接着做一个实验:直接在宿主机上改页面内容:
bash复制echo '<h1>Hello Docker Updated</h1>' > ~/site/html/index.html
再 curl 一次,发现页面立刻变了。这就是 bind mount 的效果——宿主机文件和容器内文件是同一个,改一处两边同步。这个实验做一遍,比看多少篇文章理解数据卷都管用。
手动造一个"脏数据"场景,看看清理命令怎么配合。我故意往容器里写一个多余文件再删除容器,测试数据卷是否保留:
bash复制docker exec site-demo touch /usr/share/nginx/html/temp.txt
docker rm -f site-demo
ls ~/site/html/
长见识的时刻来了:temp.txt 还在。因为文件写在了挂载目录里,而挂载目录归宿主机管,容器删除不影响它。这也是为什么数据卷几乎是生产环境必配的原因。
最后做清理。如果只是临时实验,直接删容器和镜像:
bash复制docker rm -f site-demo
docker rmi nginx:1.25
如果不想保留测试目录的话,把 ~/site 也顺手删掉。整套流程走下来,你其实已经把 pull、run、ps、exec、cp、logs、rm、rmi、volume 这一大批常用命令全部实战过了一遍,比对着文档背诵的记忆深刻得多。
6. 避坑手记:命令敲对了但结果不对的几类典型场景
这一节写我踩过的、以及在别人机器上见过的最有代表性的几个坑。它们的问题不在于命令本身写错,而在于对 Docker 运行机制的理解有偏差。
6.1 端口冲突:bind: address already in use
启动容器时报 address already in use,说明宿主机端口已经被占用。常见原因是之前有一个同名容器还在运行,或者宿主机上其他进程占着这个端口。
排查思路分两步。先看是不是有旧容器占着:
bash复制docker ps -a | grep site-demo
发现存在就直接删掉或停掉。如果是宿主机其他进程占用,用 lsof -i:8080 或者 ss -tlnp | grep 8080 找到占用进程,处理掉。避免这类问题最简单的方式是每次 docker run 之前下意识跑一条 docker ps | grep 项目名,确认当前环境状态。
6.2 容器启动后立刻退出:exit code 与前台进程问题
我见过最多的情况如下:docker run 是成功了,但 docker ps 里看不到它,docker ps -a 看到状态是 Exited (0) 或 Exited (1)。很多人第一反应是"命令写错了吧",但大概率不是。
最常见的原因:容器里没有前台进程。Docker 容器的生命周期依赖于容器内 PID 1 进程,这个进程一退出,容器就跟着退出。如果你的应用是以 daemon 方式启动的(比如某些命令默认后台运行),容器就会秒退。解决办法是在 Dockerfile 或启动命令里明确用前台方式运行,比如 Nginx 要用 nginx -g 'daemon off;',很多应用都支持这种形式。
排查时固定动作是拉日志:
bash复制docker logs 容器名
日志里往往已经写明了拒绝原因或缺少依赖的报错。学习阶段遇到这种秒退问题,先别急,养成看日志的习惯比背命令更值钱。
6.3 磁盘空间被镜像和日志占满
Docker 用久了磁盘爆掉是非常常见的问题。日志默认无限增长,尤其是那些频繁输出访问日志或报错信息的容器,几天就能吃掉几个 GB。排查命令:
bash复制docker system df
它会列出镜像、容器、数据卷、缓存各自占了多少空间。如果确认是日志导致,可以在 docker run 时加上日志大小限制:
bash复制docker run -d --log-opt max-size=10m --log-opt max-file=3 nginx
如果已经是存量问题,先 docker system prune 清缓存,再删掉不用的镜像和容器。但从长期看,启动新容器时就把日志限制加上是最省心的做法。
6.4 exec 进不去:command not found: bash
docker exec -it 容器名 bash 是标准操作,但遇到 Alpine 系列镜像就会报 bash 不存在,因为它们默认用 BusyBox 的 sh。这种情况不要纠结,把 bash 换成 sh 就能进。如果想要一个带 bash 的调试环境,可以在启动时额外拉一个调试用的镜像,或者干脆在 Dockerfile 里装好 bash 再构建。顺便说一句:好多教程里的容器都带 bash,大家用习惯了,一遇到精简镜像就手足无措,其实这不叫问题,只是镜像设计理念不同。
写到这里我想起一个习惯:无论接下来要敲什么命令,我都先跑一次 docker ps 看看当前有哪些容器在跑、状态怎么样。这个动作花两秒钟,但能让你对整个环境有数,不至于后面排查时对着结果一头雾水。把这套常用命令在自己的测试机上完整过几遍,遇到问题的第一反应就会变成"去看日志,去查状态",而不是去百度命令怎么写——这才是真正上手 Docker 的标志。
