1. 镜像管理命令,先把手里的“原材料”管明白
用 Docker 干活,本质上是围绕两样东西转:镜像和容器。镜像可以理解成只读的模板,容器是模板跑起来之后的实例。我刚开始接触 Docker 的时候,光对着docker pull和docker images这两个命令翻来覆去地用,后来才慢慢把整套镜像管理的操作捋顺。这一节先把这块讲透,因为后面的所有容器操作都建立在镜像之上。
1.1 镜像拉取与查看
docker pull是最常用的命令,没有之一。比如我想部署 MySQL 8.0,最简单的做法就是:
docker pull mysql:8.0这里有个小细节:默认不加 tag 会用latest,但生产环境我强烈建议指定具体的版本号,比如mysql:8.0.31。原因很简单,latest是会变的,今天拉下来一个版本,半年后再拉可能就不一样了。你写部署文档、写脚本的时候,如果锁定了版本号,其他人复现出来的环境才完全一致。我自己就吃过亏,早期写了一个用latest的部署脚本,三个月后跑了一次,数据库行为变了,排查半天才发现是镜像版本悄悄升级了。
查看本地已存在的镜像用:
docker images如果只想看某个镜像,可以加过滤条件:
docker images | grep mysql docker images --filter "dangling=true"第二个命令会列出所有悬空镜像,也就是那些标签被覆盖之后残留的中间镜像块。这类镜像特别占磁盘,后面讲清理的时候会提到。还有一个小技巧,docker images默认只显示顶层镜像,如果你想看某个镜像的历史层,可以用docker history:
docker history mysql:8.0这个命令在排查镜像体积问题的时候很好用,能看到每一层占了多大空间。
1.2 镜像删除与改名
镜像删错了或者想清理不用的镜像,用docker rmi:
docker rmi mysql:8.0 docker rmi -f mysql:8.0-f是强制删除。但是注意,如果镜像已经被某个容器使用了,直接删会报错,提示镜像正在被使用。这时候要么先删容器,要么用-f强删,不过强删之后原容器可能就无法正常工作了。
给镜像打标签用docker tag:
docker tag mysql:8.0 myregistry.com/library/mysql:8.0这个命令在本地调试镜像推送逻辑时很常用。你想推送到自己的私有仓库,先打一个带仓库地址的 tag,然后docker push推上去。比如:
docker tag myapp:1.0 registry.example.com/myapp:1.0 docker push registry.example.com/myapp:1.0这里要注意,如果镜像仓库是需要登录的,先执行docker login。
1.3 镜像构建
构建镜像是从零做一个自定义环境的核心操作。最基础的构建命令:
docker build -t myapp:1.0 .-t指定新镜像的名字和标签,最后的.是构建上下文路径,也就是 Dockerfile 所在的目录。这里有一个经常让新手困惑的点:Docker 构建时会把上下文目录里的所有文件都打包发送给 Docker 守护进程。如果你的目录里有一堆没用的缓存文件、日志文件,构建就会特别慢,而且镜像可能会变大。所以我在项目目录里一般都会放一个.dockerignore文件,语法和.gitignore类似:
node_modules .git *.log dist指定 Dockerfile 路径时用-f:
docker build -f docker/Dockerfile.prod -t myapp:1.0 .这个场景常见于同一套代码要打不同环境的镜像,比如开发环境一个 Dockerfile,生产环境一个 Dockerfile,放在不同目录里。
如果你只是想保存当前容器改动后的状态,可以用docker commit。但我必须说句实在话:正常情况下我不推荐这个命令。用 commit 生成的镜像没办法追踪每一层是怎么产生的,Dockerfile 的可复现性完全没有了。我见过有人把数据库容器跑了一阵之后直接 commit 一次拿来当备份,这个做法隐患很大,镜像里包含了运行时产生的所有状态,磁盘占用大,而且行为不可预测。正确做法永远是写 Dockerfile,用docker build构建出干净、可追溯的镜像。
1.4 镜像加速源配置
国内最常见的痛点就是镜像拉取慢。Docker Hub 的访问速度确实时好时坏,所以很多人会配置镜像加速源。Docker 守护进程配置文件通常在/etc/docker/daemon.json,Windows 上对应的是 Docker Desktop 的设置界面。配置格式如下:
{ "registry-mirrors": [ "https://docker.m.daocloud.io" ] }改完之后重启 Docker 服务:
sudo systemctl daemon-reload sudo systemctl restart docker需要注意,不同的加速源可用性随时可能变化,我一般是多配几个备用的,拉取失败时 Docker 会自动尝试下一个。另外,加速源只对公开镜像有效,你自己推到私有仓库里的镜像,或者公司的私有仓库镜像,不受加速源影响。
2. 容器生命周期操作,让容器按照你的节奏运转
镜像只是静态的模板,真正跑起来要靠容器。容器生命周期这一块,是我在日常排障时用最多的地方。一个容器从启动到结束,涉及的命令看起来不多,但参数组合起来非常灵活。
2.1 创建并运行容器
docker run是创建容器并启动的命令。用一个实际例子来看看常用的参数组合:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -v /opt/mysql-data:/var/lib/mysql \ --restart always \ mysql:8.0这一条命令包含了几个关键参数,分开解释:
-d:后台运行容器,当前终端不会阻塞。--name:给容器起名字,后续操作直接通过名字引用,比记容器 ID 方便太多。-p:端口映射,宿主机端口:容器端口。左边是外部访问的端口,右边是容器内监听的端口。-e:向容器传递环境变量,MySQL 镜像通过这个变量初始化 root 密码。-v:卷挂载,把宿主机目录映射到容器内目录。数据持久化全靠这个。--restart:重启策略,always表示容器异常退出后会自动重启,非常适合常驻服务。
关于端口映射,我要多说一句。-p 3306:3306会把宿主机的 3306 端口暴露给所有网络接口,如果服务器有公网 IP,这点要格外小心。只希望本机访问的服务,建议写成-p 127.0.0.1:3306:3306。这个细节能帮你避免很多安全上的麻烦。
如果你想临时跑一个一次性容器做测试,不想让它在后台停留,可以用:
docker run --rm -it ubuntu:22.04 bash--rm表示容器退出时自动删除,适合临时代码测试。-it是-i和-t的组合,-i保持标准输入打开,-t分配一个终端。这个组合在进行交互式操作时是固定的搭配,比如进入容器内部或者跑一个需要输入的脚本。
2.2 查看容器状态
查看运行中的容器:
docker ps查看所有容器,包括已停止的:
docker ps -adocker ps输出的信息里包含容器 ID、镜像、创建时间、状态、端口映射等。状态一栏常见的有Up、Exited、Restarting、Paused等,排障的时候看一眼状态就能大致判断问题方向。
只看某个容器的详细信息,用:
docker inspect mysql8这个命令输出的 JSON 信息非常详细,包括网络配置、挂载卷、环境变量、重启策略等。排查问题的时候,docker inspect是我的第一选择。如果你只想看某一项,可以配合--format参数:
docker inspect --format='{{.State.Status}}' mysql8 docker inspect --format='{{.NetworkSettings.IPAddress}}' mysql8这个--format语法用的是 Go template,刚开始用会有点别扭,但熟练之后特别管用。比如想知道容器映射了几个端口,一条命令就出来。
2.3 启动、停止、重启、删除
容器已经存在的情况下:
docker start mysql8 docker stop mysql8 docker restart mysql8 docker pause mysql8 docker unpause mysql8stop会先给容器内的主进程发 SIGTERM 信号,等待一段时间后再发 SIGKILL。所以停止一个容器是需要时间窗口的,如果里面有任务正在写数据,强行 kill 可能会造成数据损坏。遇到这种场景,我一般会先docker stop,观察一下,实在停不下来再考虑后续手段。
删除容器:
docker rm mysql8 docker rm -f mysql8-f可以强制删除正在运行的容器。实际操作中我遇到最多的情况是:
- 端口被占用,需要删掉旧容器重新起一个新的;
- 容器处于
Exited状态,而且再也用不到了; - 想用同一套配置重新部署,干脆删除重建。
删除前确认一下这个容器是否还有需要保留的数据。如果挂载了宿主机目录或命名卷,容器删除后数据不会消失;如果是容器内直接写数据且没有挂载,删除容器等于数据全没。这个坑请一定记住。
2.4 网络相关参数补充
容器启动时不指定网络,默认会连接到bridge桥接网络。手动指定网络可以用:
docker run -d --network my-network nginx:latest同一个自定义网络下的容器可以直接通过容器名互相访问,比如在一个部署了 Redis 和应用的 compose 网络里,应用连接 Redis 的地址可以直接写redis:6379。这种容器间通信方式比用 IP 靠谱得多,因为容器重启后 IP 是可能变化的。
3. 容器交互与排障,进去了才能看到真相
容器跑起来只是第一步,真正的挑战在于怎么跟容器里的进程对话、怎么看日志、怎么把问题捞出来。这一节讲的都是日常排障的高频操作。
3.1 进入容器内部
最常用的进入容器命令:
docker exec -it mysql8 bash如果容器里没有 bash,可以用 sh。有的精简镜像(比如 alpine 系列)连 bash 都没有,直接进 sh。
要注意exec和attach的区别。exec是启动一个新的进程进入容器,执行完命令后退出,不会影响容器主进程。attach是挂接到容器主进程的输入输出上,如果主进程不支持交互,attach 之后可能直接卡住。所以我的习惯是:诊断问题用exec,尽量不要用attach。
非交互式地执行单条命令也很常见:
docker exec mysql8 mysql -uroot -p123456 -e "SHOW DATABASES;"这种写法在写自动化脚本时会非常有用,不需要人工进入容器再执行 SQL。
3.2 查看日志
容器日志是排障的核心信息来源:
docker logs mysql8 docker logs -f mysql8 docker logs --tail 200 mysql8 docker logs --since 30m mysql8-f是持续跟踪输出,相当于tail -f。--tail控制只看最后多少行,这个在日志量大的场景下非常实用。--since可以只看某个时间段之后的日志,适合定位刚才发生的异常。
实际使用中,我遇到最多的情况是容器启动后立刻退出,这时候日志就尤其重要:
docker logs --tail 50 mysql8如果镜像是通过 entrypoint 脚本启动的,日志里会包含脚本执行的输出,基本能判断是环境变量缺失、端口被占用还是配置文件有错。
3.3 文件复制与进程查看
宿主机和容器之间互相传文件:
docker cp /opt/test.txt mysql8:/tmp/test.txt docker cp mysql8:/tmp/test.txt /opt/test.txt这个命令不需要容器处于运行状态,即使容器是Exited状态,只要容器还在就能复制文件。我在处理数据库容器崩溃时,常用这个命令把容器里的备份文件捞出来。
查看容器内进程:
docker top mysql8查看资源占用:
docker statsdocker stats会实时显示所有运行中容器的 CPU、内存、网络和磁盘 I/O 使用情况。排查性能瓶颈的时候,先跑一下这个命令,基本能定位是哪个容器在吃资源。不过要注意,docker stats输出的内存值包含了页面缓存,实际物理内存占用可能比显示的低。
3.4 端口映射查看
有时候容器起来了,但是从外部访问不通,首先要确认端口映射。用:
docker port mysql8输出会告诉你容器端口映射到了宿主机的哪个端口。如果输出为空,说明这个容器没有做端口映射。这个问题新手很容易忽略:容器内部服务明明正常,但外部访问不了,结果一看根本忘了加-p参数。
再配合前面讲的docker inspect --format='{{.NetworkSettings.Ports}}' mysql8,能看出端口绑定的具体 IP。如果你用了127.0.0.1:3306:3306这种绑定方式,外部机器当然访问不到,这不是故障,是安全策略。
4. 网络与数据卷,连通是基础,持久化是底线
容器是临时性的,删了重建很正常。但数据不能跟着消失,网络也不能各玩各的。所以网络和数据卷这两块必须单独拎出来了解。
4.1 Docker 网络模型与实际操作
Docker 默认有三种网络驱动:bridge、host、none。bridge是默认网络,容器通过虚拟网桥互相通信;host让容器直接使用宿主机网络栈,不会有 NAT 转换,性能好但隔离性弱;none没有网络,适合特殊场景。
自定义网络推荐用bridge驱动:
docker network create my-network docker network ls docker network inspect my-network docker network connect my-network mysql8 docker network disconnect my-network mysql8docker network inspect能查看这个网络下挂了多少容器、各自的 IP 和别名。排查容器间通信问题第一步就是看它们是否在同一个网络里。
容器默认有两个网络接口:一个是docker0桥接网络,一个是你在docker run时通过--network指定的网络。如果容器启动时没指定,它就在docker0这个默认桥接网络里。两个不同的自定义网络里的容器默认是不相通的,这点需要记住。
4.2 网络不通排查思路
我遇到过很多次“docker 网络不通”的问题。常见的排查路径是:
- 先确认容器是否在同一个网络里:
docker network inspect 网络名。 - 测试连通性:进入容器内
ping另一台容器的 IP 或容器名。 - 检查宿主机防火墙:如果容器网络没问题,检查宿主机 iptables 规则或者防火墙放行策略。
- 确认端口映射是否正确:
docker port 容器名。 - 查看容器内进程是否真的在监听端口:
docker exec -it 容器名 netstat -tlnp或ss -tlnp。
如果是 Docker Desktop 环境,跨容器访问还要注意 WSL2 的虚拟网络和 Windows 防火墙之间的互动。经常是 Windows 防火墙弹窗拦截了 docker 相关的网络,放行之后问题就消失了。
4.3 数据卷管理
数据卷是 Docker 中数据持久化的核心机制。理解三种挂载方式:
- 绑定挂载(bind mount):直接把宿主机目录挂进容器,比如
-v /opt/mysql-data:/var/lib/mysql。 - 命名卷(named volume):由 Docker 管理,存储目录在宿主机上但具体路径由 Docker 控制,比如
-v mysql-data:/var/lib/mysql。 - 匿名卷(anonymous volume):没有名字的卷,容器删除后卷往往还会残留。
命名卷的推荐使用方式:
docker volume create mysql-data docker run -d --name mysql8 -v mysql-data:/var/lib/mysql mysql:8.0查看和管理卷:
docker volume ls docker volume inspect mysql-data docker volume rm mysql-data绑定挂载和命名卷各有适用场景。配置文件、日志文件适合绑定挂载,因为你需要直接在宿主机上修改;数据库文件、缓存文件适合命名卷,因为数据目录的具体位置你不需要关心,Docker 帮你管好。
关于数据卷,我最想啰嗦一句的是:在你删除容器之前,先确认一下数据到底有没有落在持久化存储里。我见过不止一次,因为图省事没挂卷,容器一删,整个数据库数据全没了。要验证的话,可以docker inspect看Mounts字段:
docker inspect --format='{{json .Mounts}}' mysql8如果Mounts为空,那说明容器没有任何挂载,数据全在容器可写层,直接删容器等于删数据。
5. Docker Compose,多容器编排的核心工具
单个容器好办,多容器一多,手打docker run就完全不是办法。Docker Compose 的价值就在于把多个容器的配置统一写在一个docker-compose.yml文件里,一条命令完成启动、停止、重建。
5.1 Compose 文件结构与基本命令
一个最简示例:
services: web: image: nginx:latest ports: - "8080:80" redis: image: redis:7.0启动:
docker compose up -d-d仍然是后台运行。如果不加-d,Compose 会以前台模式运行,终端会显示所有服务的日志,这在调试时很有用,调试完按 Ctrl+C 停止。
停止并删除容器:
docker compose downdown默认会删除所有由 Compose 启动的容器,但不会删除卷。如果你想连数据卷一起删:
docker compose down -v这个操作要非常慎重,-v会连命名卷一起删掉。我建议除非你想彻底重置环境,否则不要轻易加-v。
查看 Compose 管理的服务状态:
docker compose ps docker compose logs -f docker compose exec web bash5.2 用 Compose 快速部署 Redis 主从
这个场景我在本地开发环境里反复使用。假设要搭一套 Redis 一主一从,最简单的docker-compose.yml长这样:
services: redis-master: image: redis:7.0 container_name: redis-master command: ["redis-server", "--requirepass", "123456"] ports: - "6379:6379" volumes: - redis-master-data:/data networks: - redis-net redis-slave: image: redis:7.0 container_name: redis-slave command: ["redis-server", "--slaveof", "redis-master", "6379", "--masterauth", "123456"] depends_on: - redis-master ports: - "6380:6379" volumes: - redis-slave-data:/data networks: - redis-net volumes: redis-master-data: redis-slave-data: networks: redis-net:这里面的关键点:
- 从节点通过
--slaveof redis-master 6379指向主节点,因为在同一个自定义网络里,容器之间可以直接通过容器名互访。 depends_on只控制启动顺序,不保证主节点已经可用。如果你的从节点同步失败,多半是主节点还没完全启动,可以等几秒再重启从节点。- 通过
redis-net定义了独立的桥接网络,外部访问需要通过端口映射。
启动和验证:
docker compose up -d docker compose ps docker exec -it redis-slave redis-cli -p 6379 -a 123456 info replication看到role:slave并且主从连接状态正常,就说明主从关系建立成功了。
5.3 用 Compose 部署 MySQL 8.0
MySQL 8.0 的 Compose 配置需要注意字符集和认证方式这几个点:
services: mysql: image: mysql:8.0 container_name: mysql8 ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: "root123" MYSQL_DATABASE: "appdb" TZ: "Asia/Shanghai" command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci volumes: - mysql-data:/var/lib/mysql restart: always volumes: mysql-data:启动:
docker compose up -d验证:
docker exec -it mysql8 mysql -uroot -proot123 -e "SHOW VARIABLES LIKE '%character%';"看到character_set_server是utf8mb4就说明字符集配置生效了。MySQL 8.0 默认的认证插件是caching_sha2_password,如果你用老版本客户端连接,可能会报认证失败,这个不是容器的问题,是客户端版本太老不支持新认证方式。
5.4 Compose 与 Docker Desktop 配合使用
在 Windows 上使用 Docker Desktop 时,Compose 命令同样可用。一个常见的坑是:修改了docker-compose.yml后没有重新构建镜像。如果你在文件里改了镜像的build配置,要用:
docker compose up -d --build这样 Compose 会重新构建受影响的服务镜像。如果只是修改了环境变量或端口映射,直接docker compose up -d就行,Compose 会检测配置变化并重建对应容器。
6. 清理、权限与故障排查记录
最后这部分是压箱底的实操经验。前面讲的都是怎么把容器跑起来,这一节专门讲容器如何优雅地退场,以及遇到各种报错时的排查思路。
6.1 资源清理,给 Docker 减负
容器跑久了,镜像、卷、容器堆积,磁盘空间不知不觉就被吃满了。我常用的清理命令:
docker system df这个命令会输出磁盘占用概览,分镜像、容器、卷、构建缓存几类。先看这个,再决定要清理什么。
清理悬空镜像:
docker image prune清理停止状态的容器:
docker container prune删除所有未使用的镜像、容器、网络、构建缓存:
docker system prune -a-a会删除所有没有被容器使用的镜像,如果你只是想清理悬空镜像,就不要加-a。加--volumes会连卷一起删:
docker system prune -a --volumes这个命令我很少用,因为它很可能误删你还想保留的数据卷。建议操作之前先docker volume ls看一眼。
6.2 Docker 权限错误怎么解决
Linux 下安装 Docker 后,直接跑docker ps经常会遇到权限错误,报错类似permission denied while trying to connect to the Docker daemon socket。这是因为当前用户不在docker用户组里。
解决方案一:当前命令临时加 sudo:
sudo docker ps解决方案二:把用户加入 docker 组,然后重新登录:
sudo usermod -aG docker $USER newgrp docker注意加入 docker 组之后需要重新登录会话才生效,有些环境下要重启机器。把用户加入 docker 组是常用的本地开发做法,权限上等同于 root,所以如果是在生产服务器上,请务必想清楚再执行。
另一种权限错误发生在 Docker Desktop 场景,Windows 下常见的是failed to connect to the docker api at npipe这类报错,一般意思是 Docker Desktop 的后台服务没有正常运行,或者当前终端会话没有权限连接管道。先看 Docker Desktop 的 Whale 图标状态,确认是运行状态再执行命令,不要盲改用户权限。
6.3 Docker 服务启动失败排查
Linux 下 Docker 服务启动失败,最常见的报错包括:
dockerd启动失败- 容器启动失败并退出
排查步骤:
systemctl status docker journalctl -u docker --no-pager | tail -50常见原因有:
- daemon.json 写错了 JSON 语法,导致服务启动失败。
- 磁盘空间不足。
- 与 selinux 或防火墙冲突。
- Docker 版本不兼容当前系统内核。
6.4 Docker Desktop 的坑
Windows 环境用 Docker Desktop 最常见的问题集中在 WSL2 后端。安装好 Docker Desktop 之后,启动时可能会提示虚拟化支持未启用,这个一般要去 BIOS 里开启 CPU 虚拟化,并且在 Windows 功能里打开“适用于 Linux 的 Windows 子系统”和“虚拟机平台”。
如果docker ps报错cannot connect to the Docker daemon at unix:///var/run/docker.sock(Linux)或者npipe:////./pipe/docker_engine(Windows),多半是 Docker 服务没起来。WSL2 环境下还要检查当前 WSL 发行版是否被 Docker Desktop 正确设置,可以在 Docker Desktop 设置里查看 WSL 集成选项。
我自己踩过最坑的一次是:Windows 上 WSL2 内核版本太旧,Docker Desktop 启动后一切正常,但是跑容器就崩溃,而且报错信息不明确。最后是去 Windows Update 里更新了 WSL2 的内核组件才解决。如果你遇到 Docker Desktop 能启动但容器无法工作的情况,优先检查 WSL2 内核版本。
6.5 常见报错速查表
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
port is already allocated | 宿主机端口被占用 | 换一个宿主机端口,或先停掉占用端口的进程 |
Cannot connect to the Docker daemon | Docker 服务未启动或不匹配 | Linux 检查 systemd 服务,Windows 检查 Docker Desktop 状态 |
docker: permission denied | 当前用户不在 docker 组 | 加入 docker 组或临时用 sudo |
No space left on device | 磁盘空间不足 | docker system prune清理镜像和缓存 |
exec: "bash": executable file not found | 容器镜像里没有 bash | 改用sh或其他可用 shell |
Error response from daemon: driver failed programming external connectivity | 端口映射配置冲突 | 重启 Docker 服务或删掉冲突容器 |
failed to start service unit(Linux) | Docker 服务启动失败 | 查看/etc/docker/daemon.json语法,确认磁盘空间 |
实际排障时,我有一套固定的动作:先docker ps -a看容器状态,再docker logs --tail 100 容器名看具体日志,如果日志不明确,再docker inspect看配置。这三步能解决绝大多数问题,比到处搜报错信息要高效得多。
7. 最后分享几个我一直在用的习惯
说了这么多命令,最后聊点使用习惯上的经验,这些习惯帮我少踩了不少坑。
第一个习惯是:用完即查,别硬记。Docker 命令非常多,我到现在都还经常docker run --help去查某个参数的具体写法。关键是理解各个参数之间的关系,比如-d和-it通常不会同时用,--rm和-d搭配时容器退出会自动清理,而--restart always和--rm是冲突的配置。理解了这些,查文档就会快很多。
第二个习惯是:写一个常用的 compose 模板库。比如 MySQL、Redis、Nginx、PostgreSQL,每种服务我都存了一份可复用的docker-compose.yml模板。新项目要用,直接改改配置就能跑,效率很高,而且能保证每次部署的基础配置是经过验证的。
第三个习惯是:不要随便删数据卷。清理容器时宁可多保留一个卷,也不要误删数据。容器可以重建,数据丢了就真的没了。
第四个习惯是:定期检查磁盘占用。我会在每周五快下班的时候跑一次docker system df,顺手清理悬空镜像和停止状态的容器。这个习惯让我很少遇到“磁盘被 Docker 打满”的尴尬。
Docker 的命令体系本身不算复杂,真正有价值的都是那些从实际踩坑中沉淀下来的细节。希望这篇文章里讲到的那些小知识、小习惯,能在你后面使用 Docker 的时候帮上一点忙。如果你平时也遇到什么奇怪的 Docker 问题,多试几遍排查思路,多看看日志,很多问题没有想象中那么神秘。