说实话,Docker 的命令行看一遍文档谁都会,难的是把“能敲出几条命令”变成“脑子里有一套完整的操作逻辑”。尤其那些天天上热搜的问题:docker 安装失败、desktop 起不来、权限报错、网络不通、MySQL 和 Redis 装好却连不上,根源大多不是命令本身记不熟,而是对 Docker 的命令行体系、对象关系、网络和数据卷的底层工作原理缺少一个整体认知。
这篇内容没有太多“介绍”和“概述”,我直接把自己这些年从入门到能独立维护生产环境的经验拆开了写。全文围绕 Docker 命令行核心命令展开,从环境初始化、镜像和容器管理、Compose 编排,到网络、数据卷、高频排错,一路写到日常维护习惯。适合两类人:一类是刚接触 Docker,想系统性把命令学扎实的新手;另一类是已经在用 Docker,但遇到问题只能靠百度、搜完还是不知道原因的实践者。读完这篇,至少你拿到任何一个容器项目,都能用命令行把它盘清楚。
1. 先把环境盘明白:安装、守护进程与命令行入口
命令行本质上只是一个客户端,真正干活的是后台的 Docker 守护进程。很多人刚上手就卡在环境上,所以我先把安装、版本选择和 Socket 权限这几个基础问题说透。
1.1 Linux 安装 Docker 时,我为什么推荐走官方源而不是无脑一键脚本
Linux 上安装 Docker,常用的方式有三种:系统自带包管理器里的旧版本、官方一键脚本、手动配置官方源的完整安装。我的建议是——生产环境不要用系统源里的 docker.io,版本太老;一键脚本虽然快,但有时会默认拉取你不太想要的版本,而且脚本执行过程不可控。
以 Ubuntu 为例,我习惯手动配置 Docker 官方源,步骤并不复杂:
sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg随后写入源文件,注意 Ubuntu 版本代号要和官方路径匹配:
echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null接着执行sudo apt-get update,安装核心组件:
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里有几个点需要解释。docker-ce是社区版守护进程,docker-ce-cli是命令行工具,这两者分开装的好处是:以后即使你只要个远程客户端,也不需要在服务器上跑守护进程。containerd.io是运行时底座,镜像真正由 containerd 管理,Docker CLI 只是上层封装。docker-buildx-plugin和docker-compose-plugin一个是多架构构建工具,一个是 Compose v2 插件,后面编排都会用到。
安装完成后用docker version验证。如果只看到 Client 版本而 Server 部分是空的,说明守护进程没起来,先查systemctl status docker,必要时手动sudo systemctl start docker。这一步很多人忽略,导致后面所有命令都报“Cannot connect”。
1.2 单机环境与 Docker Desktop:CLI 行为差异
很多 Windows 和 macOS 用户接触 Docker 是从 Docker Desktop 开始的。它的本质是在本机虚拟化一个 Linux 环境,CLI 只是发指令的窗口。Desktop 的优势是图形化、免 Linux 安装,劣势是它在操作系统和 Docker Engine 之间多了一层,一旦虚拟化层出问题,命令行就会报一堆看起来很吓人的错。
安装 Docker Desktop 后,先运行docker context ls,看一下当前使用的是哪个上下文。你会看到类似desktop-linux *的条目,星号表示当前环境。如果误删或切换了 context,可以用docker context use desktop-linux切回来。这个命令知道的人不多,但排错时很关键。
Desktop 环境下还要注意,命令行报npipe:////./pipe/dockerDesktopLinuxEngine连不上,绝大多数不是你的命令写错了,而是 Docker Desktop 里 Linux Engine 没起来。常见原因有三个:宿主机没有开启硬件虚拟化,WSL2 内核或系统组件版本过旧,以及 Desktop 服务被安全软件拦截。第一个问题要到 BIOS 里找 Intel VT-x 或 AMD SVM 开关,Windows 功能里也要确保“虚拟机平台”和“适用于 Linux 的 Windows 子系统”两项已勾选。做完这些再重启 Docker Desktop,基本都能解决。
1.3 最常见的启动失败:Socket 权限与守护进程检查
Linux 下首次运行docker ps,新手最容易碰到的是:
permission denied while trying to connect to the Docker daemon socket这个报错的原因很简单:Docker CLI 默认通过/var/run/docker.sock和守护进程通信,这个 socket 文件属于 root 用户和 docker 组,普通用户不在 docker 组里,自然没权限。
解决方案两条路。临时用sudo没问题,但如果天天sudo docker,文件归属和权限会变得一团糟,而且很多生成物比如挂载出来的文件都会带 root 权限,后续清理很烦。推荐把当前用户加入 docker 组:
sudo usermod -aG docker $USER newgrp docker执行后重新登录终端,再docker ps即可。这里要泼一盆冷水:把用户加入 docker 组等同于授予该用户 root 级别的宿主机权限,因为容器可以挂载宿主机目录、甚至直接读写/。所以只对可信用户这么做,生产服务器上尽量用 sudo 临时提权,别图省事。
还有一种情况是守护进程完全没起来。systemctl status docker会显示 active (running) 才是正常,如果不是,查看journalctl -u docker -n 50看具体失败原因。常见的有网络桥接创建失败、iptables 配置被安全软件改写、磁盘满等。这些问题在命令行阶段暴露得越早越好。
2. 镜像与容器:两个核心对象吃透
Docker 的日常操作里,镜像和容器几乎占据所有高频命令。镜像是一个只读模板,容器是模板运行后的实例。很多命令就是围绕这两个对象增删改查,理解它们的关系,命令就背住了三分之二。
2.1 镜像命令:pull/images/tag/rmi/save/load
先看拉取镜像。默认从 Docker Hub 拉取:
docker pull nginx:1.25-alpine后面附带的标签(tag)非常重要。:1.25-alpine表示 Nginx 1.25 的 Alpine 精简版本,体积小、攻击面少,适合做基础镜像。不写 tag 默认拉latest,但latest是一个滑动标签,昨天是 1.25,明天可能变成 2.0,生产环境最忌讳这种不确定性。所以我会给线上服务固定一个具体 tag,必要时再附带摘要(digest)锁定镜像版本。
查看本地镜像用docker images,也可以简写为docker image ls。输出里包含 REPOSITORY、TAG、IMAGE ID、CREATED、SIZE。SIZE 看着很小,不表示完整系统占用,因为它显示的是单层大小,多层累加可能更大。
打标签是给镜像起别名:
docker tag nginx:1.25-alpine registry.example.com/prod/nginx:1.25-alpine这一步在做私有仓库推送前几乎必用,把项目名、环境、版本都写进标签里。
删除镜像用docker rmi image_id或docker image rm。遇到容器还在使用镜像时会报 conflict,需要先删容器再删镜像,或者直接加-f强删——但我不推荐轻易用强删,容易把还在运行的依赖层关系搞乱。
离线传输和备份我会用 save/load:
docker save -o nginx.tar nginx:1.25-alpine docker load -i nginx.tardocker save保留了镜像的所有历史层和元数据,适合跨机器迁移。对应地还有docker export,但它导出的是容器文件系统,不是镜像,常用于紧急取文件,不要混用。
看镜像内部细节用docker inspect,返回的是 JSON 格式,包含了 entrypoint、环境变量、端口暴露、挂载点等大量信息。我最常搭配--format输出指定字段,比如只查看入口命令:
docker inspect --format='{{.Config.Entrypoint}}' nginx:1.25-alpinedocker history则能看到每一层执行了什么命令,排查“为什么镜像这么大”时非常有用。
2.2 容器生命周期:run/ps/logs/exec/rm
容器的完整生命周期是:创建-启动-运行-暂停/停止-删除。但日常用docker run一条命令直接完成创建和启动,它也是最复杂的核心命令,没有之一。
先看一个综合示例:
docker run -d \ --name nginx-web \ -p 8080:80 \ -v /data/nginx:/usr/share/nginx/html \ -e TZ=Asia/Shanghai \ --restart unless-stopped \ nginx:1.25-alpine各参数的含义我拆开讲:
-d:后台运行容器,终端输出不挂住。--name:给容器起名,比用随机 ID 有辨识度。-p 8080:80:宿主机 8080 映射到容器 80。端口冲突时 Docker 不会自动换端口,而是直接报错,所以我习惯先ss -tlnp | grep 8080检查端口占用。-v:挂载数据卷或目录。-e:传入环境变量。时间是容器里最容易忽略的,很多容器默认 UTC 时区,必须用TZ=Asia/Shanghai指定。--restart unless-stopped:容器异常退出会自动拉起,手动 stop 后不会重启。这是最推荐的保活策略,always在某些主动停止场景下反而会反复拉起。
查看容器运行状态,docker ps只看存活容器,docker ps -a看全部,包括已退出的容器。输出里的 STATUS 字段很重要:Up 3 hours正常,Exited (0)是正常退出,Exited (137)通常是内存溢出被 kill,Restarting要重点关注。
查看日志是排错第一手段:
docker logs -f --tail 200 nginx-web-f实时跟踪输出,--tail 200只显示最后 200 行。想做时间范围过滤可以加--since 10m,否则大日志会直接刷爆终端。
进入正在运行的容器分两种:docker attach和docker exec。attach是把当前终端接到容器主进程上,容易挂住甚至导致容器等主进程退出,排错时我极少用它。exec是在容器内新起进程,更安全:
docker exec -it nginx-web sh-i保持标准输入,-t分配伪终端。两个参数经常一起用,少一个都可能出现输入无效或排版错乱。
停止和删除也有讲究。docker stop会先给容器一个优雅停止时间,默认 10 秒,超时再强制 kill。数据库类容器我建议docker stop -t 60给更多时间刷盘。删除容器用docker rm,删除正在运行的容器需要先停再删,或者docker rm -f直接强制删。但我遇到过很多次-f导致数据没来得及落盘的案例,所以生产环境我尽量不用。
2.3 用 MySQL 8.0 实战验证常用参数
理论说多了容易晕,这里用一个具体的 MySQL 8.0 部署把命令串起来。很多网上教程会直接让你跑一条 run,但实践中我的步骤是分层的。
第一步,准备好宿主机数据目录,避免 mysql 容器以 root 身份在目录里创建文件后难以维护:
mkdir -p /data/mysql/{data,conf,logs}第二步,运行容器:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD='Root@123456' \ -e TZ=Asia/Shanghai \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/conf:/etc/mysql/conf.d \ -v /data/mysql/logs:/var/log/mysql \ --restart unless-stopped \ mysql:8.0 \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci镜像后面跟的--character-set-server=utf8mb4不是 docker 命令参数,而是传给 MySQL server 的启动参数,这是 run 的一种经典透传特性。如果不方便写在命令行,还可以放在挂载的/etc/mysql/conf.d下写一个character-set.cnf,效果一样。
第三步,验证。先看启动日志里是否出现 ready for connections:
docker logs mysql8 2>&1 | grep -E "ready for connections"然后进入容器,用 MySQL 客户端测试:
docker exec -it mysql8 mysql -uroot -p如果外部工具连不上,优先检查宿主机防火墙、-p 3306:3306是否生效,以及 MySQL 里 root 的 host 是否被限制为 localhost。很多人第一次部署 MySQL 都在最后一步翻车,因为 root 用户默认只能从容器内登录。这时候在容器里执行一条授权即可:
CREATE USER 'dev'@'%' IDENTIFIED BY 'Dev@123456'; GRANT ALL PRIVILEGES ON *.* TO 'dev'@'%'; FLUSH PRIVILEGES;2.4 用 Redis 主从理解自定义网络与内部通信
Redis 主从部署看起来只是多跑一个容器,但其中藏着一个 Docker 网络的重要考点:容器之间不要依赖宿主机 IP,而应该用容器名通信。
先创建一个自定义网络:
docker network create redis-net再启动主节点:
docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7-alpine \ redis-server --appendonly yes从节点启动时,指向的不是宿主机127.0.0.1:6379,而是容器名redis-master:
docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7-alpine \ redis-server --replicaof redis-master 6379验证主从状态:
docker exec -it redis-slave redis-cli info replication输出里master_link_status:up表示主从正常。这里的关键是自定义网络上启用了 Docker 内置 DNS,容器名可以被同网络下的其他容器解析成对应 IP。如果用默认 bridge 网络,旧版本容器之间只能靠 IP 访问,一旦容器重建 IP 就变了,非常痛苦。所以从 Redis 主从这个需求开始,我就建议养成“先建网络,再跑容器”的习惯。
3. 用 Compose 把多容器编排成一条命令
当容器数量超过三四个,逐条 run 已经不适合维护了。Compose 的价值不是省那几行命令,而是把容器、网络、卷、依赖关系写进一个声明式文件,让整套环境可移植、可追踪、可审计。Docker 官方现在的推荐是docker compose(v2 插件)而不是老旧的docker-compose。
3.1 Compose 的核心配置字段与启动命令
一个最小可用的 compose 文件长这样:
services: web: image: nginx:1.25-alpine ports: - "8080:80" restart: unless-stopped不用写顶层的version字段,新版本 Compose 已经忽略它,写了反而可能带来旧版兼容提示。核心是services映射,里面每个 key 都是一个服务名,也就是网络上的主机名。
常用字段我需要单独说几个:
image:直接指定镜像。如果下面还有build,则优先构建。build:指定 Dockerfile 目录,例如build: ./backend,配合docker compose up --build使用。ports:端口映射,写成"宿主机端口:容器端口"。注意就算值看起来是数字,也建议加引号,避免 YAML 解析成其他类型。environment:环境变量,可以写成列表或字典,推荐字典格式,更清晰。volumes:数据卷或目录挂载。depends_on:依赖启动顺序。但这只是“等容器启动”,不保证“等容器可用”。比如数据库容器虽然起来了,但初始化还没完成,后端连接就失败。要解决这个问题,需要配合healthcheck。healthcheck:健康检查定义,常用test、interval、timeout、retries。
启动命令整套如下:
docker compose up -d docker compose ps docker compose logs -f backend docker compose exec backend sh docker compose down其中down本身不删数据卷,默认只停容器和网络。如果要清掉所有关联卷,用down -v。这个-v相当危险,会把数据库数据一起删掉,操作前一定确认有没有持久化。
3.2 一个后端 + MySQL + Redis 的微服务项目落地方案
空讲字段不够直观,我用一个常见的微服务启动场景完整展示。假设项目有 backend 服务,依赖 MySQL 和 Redis,后端 Dockerfile 在./backend目录。
compose 文件:
services: mysql: image: mysql:8.0 container_name: demo-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: "Root@123456" MYSQL_DATABASE: "demo" TZ: "Asia/Shanghai" volumes: - mysql-data:/var/lib/mysql ports: - "3306:3306" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-pRoot@123456"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine restart: unless-stopped command: redis-server --appendonly yes volumes: - redis-data:/data ports: - "6379:6379" healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 3s retries: 5 backend: build: ./backend restart: unless-stopped ports: - "8080:8080" environment: DB_HOST: "mysql" DB_USER: "root" DB_PASSWORD: "Root@123456" REDIS_HOST: "redis" TZ: "Asia/Shanghai" depends_on: mysql: condition: service_healthy redis: condition: service_healthy执行:
docker compose up -d注意观察依赖。MySQL 容器只有通过mysqladmin ping检查才算健康,backend才会真正创建。这套机制比单纯depends_on可靠得多,是我在 dev 和测试环境反复验证后的标准做法。
进入后端排查时,容器名或者服务名backend在网络里可以直接访问mysql:3306和redis:6379,一点都不需要关心宿主机 IP。这也是 Compose 带来的最大便利。
3.3 Compose 与生产环境相关的几个坑
第一,容器内时区。很多镜像基础层是 UTC,如果不在environment里加TZ=Asia/Shanghai,日志和数据库时间都会偏 8 小时。别指望所有镜像默认设置好时区,加一个环境变量是最稳的。
第二,挂载目录的权限。bind mount宿主机目录到容器里,很容易出现Permission denied,本质是 UID/GID 不匹配。简单粗暴的方案是把宿主机目录权限改成 777,但生产环境不推荐。更好的方式是在镜像或 compose 里指定运行用户组,或者在 Dockerfile 里useradd一个和宿主机 UID 相同的用户。
第三,端口冲突。compose 里如果把多个项目都映射到3306:3306,第二个项目启动就会失败。一个很有用的思路是:应用之间的内部访问尽量走容器网络,不要每次都映射到宿主机端口。只有真正需要外部访问的服务才暴露端口。
4. 网络与数据卷:持久化与互联的底层原理
很多人 docker 命令敲得溜,但一遇到网络不通、数据丢失就懵。这一节把两个最容易被表面命令掩盖的底层机制讲清楚。
4.1 四种网络模式与自定义网络的工作方式
Docker 网络先看大框架:默认存在四种模式。bridge是默认模式,通过虚拟网桥为容器分配内网 IP,容器和外界通信经过 NAT 转换。host模式直接共享宿主机网络栈,没有端口映射概念,性能好但隔离差,网络配置容易冲突。none模式关闭网络,用于极端隔离场景。overlay模式主要用于 Swarm 集群跨节点通信,日常单机用得少。
命令层面掌握三条:
docker network ls docker network create app-net docker network inspect app-netinspect能看到具体有哪些容器挂在网络下、每个容器的 IP、网关、子网掩码。排查网络不通时,第一步不是 ping,而是“你俩到底在不在一张网里”。不同网络下的容器不能直接互通,除非通过宿主机端口映射。
为什么自定义网络比默认 bridge 好用?核心是内置 DNS。在默认 bridge 中,Docker 旧版本不提供 DNS 解析,容器间访问只能靠--link或固定 IP;自定义 bridge 则会为每个容器记录主机名,容器名就是域名。只要两个容器在同一个app-net里,我用docker exec app1 ping app2直接就通了。这一点在 2.4 的 Redis 主从里已经体现过。
生产环境排网络问题,我的固定顺序是:
- 检查
docker network inspect,确认容器在同一网络。 - 进入容器 ping 网关和对方容器名。
- 如果内部通、外网不通,查宿主机防火墙和端口映射。
- 如果端口映射不通,查
ss -tlnp看宿主机端口有没有被占用或防火墙放行。
4.2 数据卷、绑定挂载与容器权限
容器删除后文件系统并不会消失,但只有写在挂载卷或数据卷里的内容才能持久保留。理解三种挂载方式的区别很重要:
bind mount:把宿主机目录挂到容器目录,例如-v /data/mysql:/var/lib/mysql。好处是文件直接可见、方便备份;坏处是宿主机目录权限直接决定容器内访问权限,目录不存在时需要先创建。named volume:由 Docker 管理,例如-v mysql-data:/var/lib/mysql。数据存放在/var/lib/docker/volumes/mysql-data/_data,作用上像一个数据抽屉,容器删了数据还在。tmpfs:写入宿主机内存,不落盘,适合放临时文件。
常用命令:
docker volume create app-data docker volume ls docker volume inspect app-data docker volume rm app-data备份数据卷最直接的方式是先用一个临时容器把卷打包出来:
docker run --rm \ -v app-data:/data:ro \ -v /backup:/backup \ alpine tar czf /backup/app-data.tar.gz -C /data .这里--rm表示临时容器用完自动删除,:ro表示以只读方式挂载原卷,避免备份过程中写坏数据。
4.3 GitLab 社区版部署:80/22 端口冲突与数据卷规划
把 GitLab 社区版用 Docker 跑起来是很多团队维护内部代码仓库的常见需求。它有明确的“官方 docker run 最佳实践”,直接抄即可,但里面有两个坑必须提前说清楚。
先看部署命令:
docker run -d \ --name gitlab \ --restart always \ -p 8080:80 \ -p 8443:443 \ -p 2222:22 \ -v /srv/gitlab/config:/etc/gitlab \ -v /srv/gitlab/logs:/var/log/gitlab \ -v /srv/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:16.4.0-ce.0第一个坑是宿主机 SSH 端口。GitLab 镜像内部的 SSH 默认监听 22,但宿主机 22 通常已经被 system sshd 占用,所以映射成2222:22。这意味着 git clone 地址要改成ssh://git@yourhost:2222/group/repo.git,而且需要在 GitLab 配置里同步修改:
docker exec -it gitlab editor /etc/gitlab/gitlab.rb配置项是gitlab_rails['gitlab_shell_ssh_port'] = 2222。改完执行:
docker exec -it gitlab gitlab-ctl reconfigure第二个坑是初始密码。新部署的 GitLab 会生成一个随机初始 root 密码,保存在容器的/etc/gitlab/initial_root_password文件中,且 24 小时后自动删除。如果你没设置环境变量,务必第一时间查看:
docker exec -it gitlab grep 'Password:' /etc/gitlab/initial_root_password生产环境我还会把external_url改成正式域名,例如http://gitlab.example.com,否则生成的 clone 地址永远是容器主机名。
5. 命令行排错实录:那些年我踩过的坑
这条线上踩坑多了,自然就能形成一套快速排查的直觉。我把最高频的几类问题拿出来,把原因和解决路径一次讲清。
5.1 Permission denied 与 npipe 连接失败
Linux 下的权限问题,前面已经提到加入 docker 组。但如果已经加入 docker 组依旧报错,另一个常见原因是 docker 守护进程没起来,或者 socket 文件权限被改动。先检查ls -l /var/run/docker.sock,如果这个文件不存在,说明守护进程根本没有正常启动,服务拉起来再说。
Windows 下 Docker Desktop 报npipe:////./pipe/dockerDesktopLinuxEngine连接失败,我遇到最多的情况是引擎还没启动完成。命令行等个十几秒再敲命令往往就通了,因为 Desktop 的图形面板启动很慢,但 CLI 不会告诉你“再等等”。如果一直失败,就回到 1.2 检查虚拟化、WSL2 和系统组件。不要在还没有确认 Engine 状态时反复重启终端,这会误导判断。
5.2 bind: address already in use 端口排查
启动容器时最常见的报错之一:
docker: Error response from daemon: driver failed programming external connectivity on endpoint xxx: Bind for 0.0.0.0:3306 failed: port is already allocated原因明确了:端口被占用。排查顺序:
ss -tlnp | grep 3306 netstat -tlnp | grep 3306 lsof -i:3306如果是另一个容器占用了同端口,docker ps就能看到。如果是宿主机进程占用,就要决定是杀掉旧进程还是给新容器换端口。我一般倾向换端口,而不是强杀未知进程,尤其生产服务器上你并不知道那个进程在干什么。
5.3 镜像拉不动、超时怎么办
docker pull超时或者极慢是最常见的问题之一。原因大多是默认 Docker Hub 的 registry 镜像源在我们这边访问不稳定。解决方案是配置加速源,也就是registry-mirrors。
Linux 下改/etc/docker/daemon.json:
{ "registry-mirrors": ["https://docker.mirrors.example.com"] }改完执行:
sudo systemctl daemon-reload sudo systemctl restart dockerDocker Desktop 用户则可以在设置界面的 Docker Engine 配置里加入同样的 JSON 字段,然后重启 Desktop。配置好之后再用docker pull会明显顺畅。需要注意:不要每个镜像都写死某个私有加速源,团队环境里最好统一维护一份标准配置,否则换一个人排错头大如斗。
5.4 容器秒退/反复重启的检查顺序
docker ps -a里经常看到Exited (1)或Restarting,这种状态通常不是 Docker 的问题,而是容器内应用的问题。检查顺序我固定为:
第一步,看日志:
docker logs --tail 200 --details 容器名日志里如果显示配置文件不存在、环境变量为空、端口连不上,先去补这些外部依赖。第二步,看入口命令。如果镜像默认入口是nginx -g "daemon off;",但你覆盖成了/bin/bash,没有附加-it的情况下 bash 拿到无终端输入会立刻退出。此时改成docker run -it就能看到退出前的真实输出。第三步,检查健康检查是否过严。Compose 里 healthcheck 连续失败会触发重启,但不说明应用真挂了,有时只是mysqladmin ping的密码不对,导致一直被认为不健康。
6. 提升效率的命令习惯与日常维护
命令本身学会不难,难在把它变成肌肉记忆和高效习惯。最后这部分是我个人日常维护里最常用的小技巧,不写长篇大论,都是可以直接抄作业的。
6.1 格式化输出与批量操作
docker ps默认打印所有列,列一多就眼花。我更推荐的写法是自定义格式:
docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"甚至可以把它设置成 shell 别名,比如:
alias dps='docker ps --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}"'查看资源占用用docker stats,但默认是持续刷新的,跑完就退出可以加:
docker stats --no-stream批量操作容器时,docker ps -q是核心:
docker stop $(docker ps -q) docker rm $(docker ps -aq)xargs也很常用,比如批量删除所有 exited 容器:
docker ps -aq --filter "status=exited" | xargs docker rm6.2 空间清理:prune 的正确打开方式
磁盘被镜像和容器日志占满,是我接手服务器后最常见的故障。docker system df可以先看整体占用:
docker system df输出会按镜像、容器、卷、构建缓存分别显示占用。然后谨慎使用清理命令:
docker container prune docker image prune docker volume prune docker system prune -asystem prune -a会清掉所有未使用的镜像、停止的容器和网络,但默认不会删数据卷。加上--volumes才删卷。反向警示:这条命令一旦执行,很多本地的历史镜像和旧卷将无法恢复。我的习惯是,准备清理前先docker image ls确认没有需要保留的版本,确认后再加-a -f,并且永远不要把生产库所在的数据卷交给 prune 自动处理。
6.3 我的日常命令组合拳
最后分享一个我自己的习惯。每次接手新项目,我第一步永远是docker ps -a加docker inspect看状态,而不是直接启动服务。第二步看日志,第三步看环境变量和挂载。这个顺序永远不会错。
另一个习惯是把常用命令写成脚本。比如我本地有个dc.sh,内容类似:
docker compose ps docker compose logs -f --tail 100 docker compose down --remove-orphans docker compose up -d --build这样即使换了新机器,只要脚本文件在,环境很快就能搭起来。命令行不是背出来的,是每天在真实场景里敲出来的。多部署几个 MySQL、Redis、GitLab,多把 compose 编排理解透,再遇到报错,你不需要搜遍全网,光看字段和日志就能猜到问题在哪。