docker常用命令全解析:从容器生命周期到Compose编排实践
2026/9/16 2:24:56 网站建设 项目流程

“docker 常用命令”这几个字,搜索平台上热度一直很高,但每次看完教程都会有同一种感受:命令是记住了,真要部署一个带数据卷、自定义网络、Compose 编排的服务,还是会卡壳。原因很简单,命令本身只是壳,背后的容器生命周期、镜像分层、数据卷逻辑才是真正值钱的东西。这篇文章我会按自己实际干活时的使用频率来梳理 Docker 命令,不追求大而全,而是把每一次敲命令时为什么这么敲、常见坑在哪里讲清楚。刚接触 Docker 的开发者,或者想把命令链补全的运维同学,都值得花十分钟过一遍。

1. 容器的生老病死:生命周期命令的完整闭环

1.1 从 docker run 说起:一条命令把容器拉起来

docker run是大多数人接触的第一个命令,也是参数最多、最容易记混的一条命令。一个典型的 MySQL 启动命令长这样:

docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=my-secret-pw \ -v mysql-data:/var/lib/mysql \ mysql:8.0

这里每个参数都是日常高频的:

  • -d表示后台运行,容器启动后在后台待着,终端不会被占用。
  • --name给容器起一个名字,后面所有操作都可以用这个名字代替容器 ID。
  • -p 3306:3306是端口映射,把宿主机的 3306 端口转发到容器的 3306 端口。格式是“宿主机端口:容器端口”。
  • -e设置环境变量,MySQL 镜像靠它来指定 root 密码。
  • -v挂载数据卷,这里用了命名卷mysql-data,MySQL 的数据文件会写到这个卷里,容器删了数据也不会丢。
  • mysql:8.0是镜像名和标签,标签不写默认是latest

相反,如果你要进入一个临时容器做调试,通常不会用-d,而是用交互模式:

docker run -it --rm ubuntu:22.04 bash

-it-i-t的组合,-i打开标准输入,-t分配一个虚拟终端,合起来就能像在本地终端里一样操作容器。--rm表示容器退出后自动删除,非常适合一次性调试。

我建议生产环境启动容器时一定写具体镜像标签,比如mysql:8.0.36,而不是mysql:latest。因为latest是可变的,镜像作者哪天更新了,你重新拉取再启动,容器行为可能就变了。

1.2 状态切换:ps、start、stop、restart、kill

容器跑起来之后,查询和状态切换命令的使用频率最高。

docker ps # 查看正在运行的容器 docker ps -a # 查看所有容器,包括已退出的 docker ps -a --filter status=exited # 只看已退出的

我经常用docker ps -a排查“容器明明启动了,为什么日志里没动静”的问题。加了--filter之后,输出会干净很多。

启动、停止、重启是三个配套命令:

docker start mysql8 docker stop mysql8 docker restart mysql8

这里有一个容易忽略的区别:stop会先给容器里的主进程发送 SIGTERM 信号,等进程优雅退出,默认超时时间是 10 秒,超时后再发 SIGKILL 强制杀掉。kill则直接发送 SIGKILL,进程没有机会做善后清理。所以除非进程卡死,否则优先用stop

我见过有人写脚本批量重启容器,直接一长串docker restart下去。如果容器里是数据库或有状态服务,强烈建议逐个停止、确认日志、再启动,而不是无脑 restart。

1.3 清理边界:rm 和 prune

容器不需要了,删除命令是:

docker rm mysql8

如果容器还在运行,直接删会报错,加-f可以强制删除,等价于docker killdocker rm

批量清理已退出的容器,用prune更省事:

docker container prune

它会删除所有处于停止状态的容器,问你是否继续,加-f跳过确认。

更猛的是docker system prune,它会把未使用的镜像、容器、网络、构建缓存一起清理。这个命令我一般只在磁盘空间告急时用,而且会额外提醒自己看一眼会不会误删。注意docker system prune默认不会删数据卷,但如果手贱加了--volumes,那可不是闹着玩的,数据卷一旦删除基本等于数据丢失。

1.4 实用心得:命名和 --rm 的小讲究

用了几年 Docker,我最想强调的习惯是:给容器起名字。不命名的话,docker ps输出里会出现一堆随机字符串 ID,比如f3b89c...,操作时只能复制黏贴,效率极低。起名规则建议“应用名 + 角色 + 序号”,例如mysql8-masterredis-cache-01,一眼就知道是干什么的。

另外,临时容器强烈建议加--rm。比如你想验证一个镜像、看一个配置文件、试一条命令,用--rm跑起来,退出后自己就清掉了,不会在docker ps -a里留一堆僵尸容器。我的习惯是:能确认生命周期的容器才不写--rm,其他一律加上。

2. 镜像管理:拉取、构建、瘦身和搬运

2.1 pull、images、tag、rmi:镜像的基本操作

镜像和容器的关系类似于程序和进程。先从仓库拉取镜像:

docker pull nginx:1.25

不写标签的话,默认拉latest。查看本机已有的镜像:

docker images

输出里有仓库名、标签、镜像 ID、创建时间和大小。看到一堆镜像想删除某个时,用:

docker rmi nginx:1.25

如果这个镜像还被某个容器引用,哪怕是已停止的容器,docker rmi都会报错。这时候要么先删容器,要么用-f强制删除镜像,但-f会造成遗留的“悬挂镜像”,占用磁盘空间。

docker tag是镜像打标签的命令,通常用来准备推送到自己的仓库:

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

2.2 build 与 .dockerignore:构建镜像的基础

写好了 Dockerfile,构建镜像的命令是:

docker build -t myapp:1.0 .

-t指定生成镜像的名称和标签,最后的.是构建上下文路径,也就是 Docker 会把当前目录打包发给 Docker daemon 去处理。

一个常见的 Node.js 应用 Dockerfile 示例:

FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["npm", "start"]

这里有个隐藏的坑:COPY . .会把当前目录所有文件复制进镜像。如果目录里有node_modules.git、日志文件,构建镜像时会变得又大又慢。解决办法是和.gitignore写法一样的.dockerignore文件:

node_modules .git dist *.log

.dockerignore配合COPY可以有效减小构建上下文,尤其是项目里有大文件时,效果非常明显。

2.3 save/load 与 export/import:镜像迁移的四兄弟

内网环境无法直接拉取镜像,是最常见的运维场景之一。这时候需要离线搬运镜像,对应四组命令:

命令作用特点
docker save -o 文件.tar 镜像导出镜像保留镜像分层、历史、标签
docker load -i 文件.tar导入镜像导入后完整还原镜像
docker export -o 文件.tar 容器导出容器文件系统丢弃分层和历史信息
docker import 文件.tar 镜像名导入为镜像只有一层,大小可能变大

实际使用中,备份和迁移镜像优先用save/load,因为它们保留镜像原始结构,加载后能直接docker runexport/import更适合想从容器里提取文件系统、重新打包成镜像的场景。

2.4 空间清理与优化:不看占用,磁盘迟早报警

镜像和容器堆积多了,磁盘占用会非常夸张。我建议定期执行:

docker system df

它会列出镜像、容器、数据卷、构建缓存分别占了多少空间。看到镜像大小远远超过预期时,再用docker image prune清理悬挂镜像:

docker image prune # 清理 dangling 镜像 docker image prune -a # 清理所有未被容器使用的镜像

另外,docker history 镜像名可以查看镜像的分层历史,判断哪些步骤把镜像撑大了。如果你发现某条RUN命令下载了很大的文件但后面又删了,镜像体积并不会变小,因为每一层都是独立的。这种情况只能重写 Dockerfile,把下载和解压、删除放在同一个 RUN 里。

2.5 关于拉镜像慢的一点经验

很多人第一次用 Docker,卡在拉镜像这一步。除了网络本身的问题,常见的加速策略是在 Docker daemon 配置文件里配置镜像加速地址,或者在团队内部搭建一个镜像仓库,让所有机器从内网仓库拉取,速度稳定很多。

另外,如果目标机器无法访问外网,最稳妥的办法就是在有网的机器上执行docker pulldocker save,把镜像文件带到目标机器上docker load。我经常用这个思路解决离线环境部署的问题,比配置代理简单可靠。

3. 日志、端口和交互:把容器“拉出来看”的命令

3.1 docker logs:容器日志不等于文件日志

排查问题第一步永远是看日志:

docker logs -f --tail 100 mysql8

-f是持续跟随输出,类似tail -f--tail 100表示只看最近 100 行,避免日志太多刷屏。

这里有个核心原则:容器内的应用日志应该输出到标准输出/标准错误,而不是写文件。因为docker logs只能看到 stdout/stderr。如果你的应用把日志写到/var/log/app.log这种文件路径,docker logs什么都看不到。遇到这种情况,要么改应用日志配置输出到控制台,要么把日志目录通过-v挂载出来,在宿主机上直接看文件。

3.2 exec:进入容器不等于 SSH

需要进入容器内执行命令时,用:

docker exec -it mysql8 bash

docker exec是在正在运行的容器里执行命令,-it配合bash就能进入交互终端。需要注意:

  • 容器里通常没有 SSH 服务,进入容器用的是exec,不是 SSH。
  • 退出交互终端用exit,退出不会停止容器,只会结束当前 shell 进程。
  • 如果容器精简到连bash都没有,就试试sh
  • 也可以不进入交互 shell,直接执行单条命令:
docker exec mysql8 mysql -uroot -p

生产环境调试时,我倾向于用单条命令而非进入完整 shell,尽量减少交互式操作,避免误改容器内状态。

3.3 cp 和 port:文件与端口的快捷操作

从容器里拷文件出来,或者把文件拷进容器:

docker cp mysql8:/etc/mysql/my.cnf ./my.cnf docker cp ./my.cnf mysql8:/etc/mysql/my.cnf

docker cp本质上是绕过容器运行时直接复制文件,修改配置、备份脚本都很方便。但它不推荐做持久化手段,容器一删,里面拷贝的数据就没了。

查看端口映射情况用:

docker port mysql8

它会显示宿主机端口和容器端口的对应关系,排查“宿主机端口明明没被占用,服务就是连不上”这类问题时很实用。

3.4 inspect:给容器做一次“体检”

docker inspect会返回容器的完整 JSON 信息,包括状态、网络、挂载、环境变量、启动命令等:

docker inspect mysql8

内容很长,通常配合--format只取需要的字段。我常用的两个:

docker inspect -f '{{.State.Status}}' mysql8 docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' mysql8

第一个查容器运行状态,第二个查容器 IP。之前帮同事排查“应用连接不上数据库”的问题,就是靠inspect发现两个容器不在同一个自定义网络里,导致 IP 根本不通。注意容器 IP 不是固定的,只要容器重建就会变,所以别在代码里硬编码容器 IP。

4. 存储和网络:让容器数据真正持久化

4.1 数据卷与 bind mount:数据到底放在哪

容器本身是临时的,但数据必须持久化。Docker 提供两种主要方式:

  • 命名卷(volume):由 Docker 管理,存储在 Docker 数据目录下。
  • 绑定挂载(bind mount):直接映射宿主机目录到容器目录。

命名卷的用法:

docker volume create mysql-data docker run -d --name mysql8 -v mysql-data:/var/lib/mysql mysql:8.0

绑定挂载的用法:

docker run -d --name nginx -v /home/user/nginx/html:/usr/share/nginx/html:ro nginx:1.25

后面的:ro表示只读,宿主机文件被容器误操作的概率就小了。日常调试我经常用绑定挂载,因为宿主机里的文件可以即时改,容器内立即生效,非常适合改前端页面和配置。

这里有一个非常容易踩的坑:如果把一个本来就包含数据的容器目录挂载到一个空的宿主机目录,容器里的原始内容会被隐藏。比如某些镜像启动时会自动初始化配置到容器目录,你一绑定挂载空目录,初始化文件就没了。解决办法是先把容器目录里的内容复制到宿主机目录,再挂载。

4.2 数据卷常用命令:volume ls、inspect、rm

查看所有数据卷:

docker volume ls

查看某个卷的挂载情况:

docker volume inspect mysql-data

删除数据卷:

docker volume rm mysql-data

注意:docker run -v mysql-data:/var/lib/mysql时,如果mysql-data卷不存在,Docker 会自动创建。而如果不加名称,比如-v /var/lib/mysql,会产生匿名卷,用docker volume ls会看到一堆随机 ID 的卷,时间久了很难分辨。

我个人的清理策略是:容器确定不再需要时,先删容器,再确认卷里的数据是否有用,最后手动docker volume rm,而不是依赖prune自动清。

4.3 网络模式:bridge、host、none 与自定义网络

Docker 默认的网络模式是 bridge,容器通过一个虚拟网桥和宿主机通信,对外端口需要手动映射。最常用的三种网络模式:

  • bridge:默认模式,适合大部分场景,容器有独立网络命名空间,端口需映射。
  • host:容器直接使用宿主机网络,没有端口映射,性能损耗低,但端口冲突由宿主机负责。
  • none:容器没有网络,适合隔离要求极高的场景。

日常开发用得最多的是自定义网络:

docker network create app-net

自定义网络最方便的地方是内置 DNS 解析。同一个网络里的容器可以直接用容器名互相访问,不需要记 IP。比如 MySQL 容器名为mysql8,应用容器里配置数据库地址写mysql8:3306就能连通,容器重建后 IP 变了也不影响。

查看、连接、断开网络:

docker network ls docker network inspect app-net docker network connect app-net 容器名 docker network disconnect app-net 容器名

4.4 一个实际例子:MySQL + Redis 的卷和网络怎么配

假设我要部署一套 Web 应用依赖的 MySQL 和 Redis,命令可以这样写:

docker network create app-net docker run -d \ --network app-net \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123 \ -e MYSQL_DATABASE=test \ -v mysql-data:/var/lib/mysql \ mysql:8.0 docker run -d \ --network app-net \ --name redis-server \ -p 6379:6379 \ -v redis-data:/data \ redis:7 redis-server --appendonly yes

注意,如果应用容器也加到app-net网络,它访问 MySQL 和 Redis 时完全不需要-p暴露端口,直接通过容器名访问即可。-p只是给宿主机外部访问用的。很多人一上来把所有端口都映射出来,一方面增加端口冲突风险,另一方面也扩大了暴露面。

5. Docker Compose:把命令写成配置,而不是“背命令”

5.1 为什么要用 Compose

手敲docker run启动三五个容器,还能忍。但一旦服务多起来,命令越来越长,参数越写越乱,打字容易漏,维护成本直线上升。Compose 解决的问题就是把容器的启动参数写进一个 YAML 文件,用一条命令启动整组服务。

新版 Docker 已经内置了docker compose子命令,不需要额外安装。旧教程里常见的docker-compose是独立命令,如果你机器上没有,可以直接用docker compose

5.2 compose 高频命令:up、down、ps、logs、exec

docker-compose.yml所在目录执行:

docker compose up -d

-d表示后台启动。第一次执行会拉取镜像并创建容器,之后执行会根据配置变化增量更新。

停止并删除由 compose 管理的容器和默认网络:

docker compose down

注意down默认不会删除数据卷,想要连数据卷一起清掉需要显式加-v,这也是不少人“服务都删了,数据还在”的原因。

查看服务状态:

docker compose ps

查看日志:

docker compose logs -f --tail 100

进入某个服务容器:

docker compose exec mysql8 bash

还要提一个命令:

docker compose pull

这个命令会把 compose 文件里所有镜像提前拉取一遍,非常适合发布前做镜像预拉取,减少服务启动时的等待时间。

5.3 把 run 命令翻译成 compose 文件

用前面 MySQL + Redis 的例子,对应的docker-compose.yml长这样:

services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: test ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql networks: - app-net redis-server: image: redis:7 container_name: redis-server restart: always command: redis-server --appendonly yes volumes: - redis-data:/data networks: - app-net networks: app-net: volumes: mysql-data: redis-data:

然后一条命令:

docker compose up -d

两个服务、一个自定义网络、两个数据卷就全部创建好了。配置一目了然,新增服务时只需要在services下面加一段。

5.4 compose 中的常见注意点

第一个注意点:不要把密钥明文写在 compose 文件里。我见过不少仓库把数据库密码直接提交到 git,这是很危险的习惯。生产环境可以用.env文件配合${MYSQL_ROOT_PASSWORD}这样的变量引用,.env文件本身加入.gitignore

第二个注意点:容器名不要写死。如果部署多套环境,比如测试环境和预发环境,container_name相同会导致冲突。去掉container_name,Compose 会自动用“项目名_服务名_序号”的方式命名容器,环境之间互不干扰。

第三个注意点:改了配置文件后要重新 up。不是改完 YAML 就生效,需要再次执行docker compose up -d,Compose 会对比配置变化并重建对应容器。

第四个注意点:down 和 prune 的区别。docker compose down只清理 compose 管理的容器和网络,卷默认留着;docker system prune是全局清理,别把两个概念搞混。

6. 环境问题和权限坑:很多时候不是命令不对,是环境不对

6.1 权限报错:docker: permission denied

新手最常见的报错是:

Got permission denied while trying to connect to the Docker daemon socket

这是因为当前用户不在docker用户组里,Docker 命令需要访问 Docker daemon 的 socket 文件。解决方案是:

sudo usermod -aG docker $USER newgrp docker

执行完后重新登录终端,再试docker ps就不需要sudo了。临时应急可以先sudo docker ps,但不要长期依赖 sudo,因为每个命令都加 sudo 会带来文件权限混乱的问题。

这里必须提醒一句:把用户加入docker组,实际上等于给了这个用户接近 root 的权限,因为用户可以通过挂载宿主机目录的方式读写系统文件。生产环境的服务器不要轻易给不信任的用户加 docker 组。

6.2 Docker Desktop 启动失败与虚拟化检测

Windows 上使用 Docker Desktop,很多人会遇到这样的报错:

Virtualization support not detected Docker Desktop failed to start because virtualisation support wasn't detected

核心原因是本机的 CPU 虚拟化没有开启,或者 Windows 需要的虚拟化组件没有启用。排查步骤:

  • 打开任务管理器,切到“性能”标签,点击 CPU,看右下角“虚拟化”是不是“已启用”。
  • 如果显示“已禁用”,需要进 BIOS 开启 Intel VT-x 或 AMD-V。
  • 开启后重启系统,再确认控制面板里“启用或关闭 Windows 功能”中的“适用于 Linux 的 Windows 子系统”和“虚拟机平台”是否勾选。
  • 如果用的是 Docker Desktop 的 WSL2 后端,还要在 PowerShell 执行wsl --status确认 WSL 版本。

另外,如果 Docker Desktop 报错信息里出现failed to connect to the docker api at npipe:////./pipe/docker-desktop这类内容,通常也是 Docker Desktop 的后端引擎没有真正启动起来。先看系统托盘里 Docker Desktop 图标的状态,再重新启动,不要一上来就想着重装。

6.3 镜像拉不动、拉得慢,先别急着重装

docker pull卡住或慢,原因往往不是命令没敲对。可以依次排查:

  • 本机磁盘是否满了。镜像拉取需要临时空间,磁盘满的时候会一直卡在等待状态。
  • 是否使用了不稳定的镜像标签。某些热门镜像的多个架构版本都比较大,可以先拉明确的单架构标签。
  • 网络链路问题。可以考虑在daemon.json里配置镜像加速地址,或者从内网私有仓库拉取。
  • 目标机器完全离线时,使用前面提到的save+load做离线搬运。

我不建议在公网上随意下载来路不明的镜像文件,尤其是打包好的“一键安装包”,里面很可能被塞了不该有的东西。靠谱的做法是只从官方源或公司内部可信仓库获取镜像。

6.4 不背命令的工作流:让 Docker 自己告诉你

最后分享一个我自己很受用的工作方式:不背命令,而是让 Docker 当你的命令手册。

docker --help docker run --help docker compose --help

这些命令会列出所有可用参数和简短说明。忘了某条命令怎么拼,直接--help比去搜索引擎翻旧博客快得多。另外,可以把 Docker 命令行补全配置好,按两次 Tab 就能看到候选命令和参数,效率提升非常明显。

说白了,docker 常用命令这张清单,能帮你应付日常工作中绝大多数场景;剩下容易踩的坑,几乎都集中在数据卷和网络这两块。真正的效率不是把命令背熟,而是养成几个固定习惯:容器命名规范、临时容器加--rm、所有多容器服务都用 Compose 管理。坚持几个月,你再回头看那些曾经以为很难的部署,其实就那几条命令在反复排队。

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

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

立即咨询