1. 环境准备:先能跑起来,再谈命令
我见过不少刚接触 Docker 的朋友,上来就敲docker run,结果报错直接卡住,以为是命令打错了,其实问题大多出在环境没装好。这一节先把底子打好,后面所有命令才能真正用在实操里。
1.1 Linux 下安装 Docker 的最小流程
绝大多数服务器场景用的是 Linux,装 Docker 并不复杂。以 Ubuntu 和 CentOS 为例,基本套路是一样的:先删掉旧版本,再装依赖,然后添加官方源或使用镜像源,最后安装并启动服务。
# Ubuntu 系 sudo apt-get remove docker docker-engine docker.io containerd runc 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 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.ioCentOS 7 上如果遇到内核版本太老、overlay 驱动不兼容的问题,可能要先做内核升级,这个后面单独说。装完第一件事不是急着跑容器,而是验证服务状态:
sudo systemctl start docker sudo systemctl enable docker docker version docker infodocker version会同时显示 Client 和 Server 两段信息,如果只有 Client 有输出、Server 报错或者缺失,说明守护进程没起来,去查/var/log/messages或者journalctl -u docker的日志。很多人在这一步就卡住了,其实多半是 systemd 服务没起来。
1.2 Windows 上装 Docker Desktop 的常见坑
Windows 这边热词里提到最多的就是virtualization support not detected和failed to connect to the docker api at npipe。这两个问题,第一个是虚拟化没开,第二个是 Docker Desktop 的后台服务没起来。
安装 Docker Desktop 之前,先去 BIOS 确认 CPU 虚拟化(Intel VT-x / AMD-V)已经开启,这是硬门槛。Windows 自带 Hyper-V 或 WSL 2 功能也建议打开,用 PowerShell 执行:
# 以管理员身份运行 dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart装完 Docker Desktop 如果双击图标没反应,或者托盘图标一直转圈,大概率是 Docker Desktop 的 Linux 后端引擎没起来。这时可以打开终端执行docker info,如果报 npipe 连接失败,直接右键任务栏的 Docker 图标选择 Restart,还不行就彻底退出以后以管理员身份重新打开。以前还有遇到过 Windows 系统更新后 Docker 起不来的情况,重启电脑基本能解决,不要一上来就重装。
这里有一个重要提示:Windows 跑 Docker 容器,本质是靠虚拟机或者 WSL 2 垫了一层,所以镜像、容器的运行性能和 Linux 原生有差距。生产环境还是建议用 Linux 服务器,Windows 这边定位成学习和调试环境就够了。
2. 镜像管理:Docker 的兵工厂
镜像这个词,你可以理解成打包好的操作系统和应用的模板。容器是模板的实例,一个跑起来的进程;镜像是不变的静态文件。几乎所有 Docker 操作都是基于镜像展开的,所以先把镜像命令搞熟练。
2.1 搜索和拉取镜像的实用技巧
# 搜索镜像 docker search mysql # 拉取镜像 docker pull mysql:8.0 docker pull mysql:latestdocker search在终端里会显示仓库名、描述、星数、是否官方。这个命令适合在没有网页浏览器的服务器上快速找镜像。但真实项目里,大家基本不会在终端搜索,而是在 Docker Hub 网页上确认版本号,再到服务器上直接pull。
拉镜像要注意 tag。mysql:latest是 MySQL 最新的稳定版,但latest是浮动的——三个月前是 8.0.x,半年后可能变成 9.x。生产环境部署必须要固定具体版本,比如mysql:8.0.40,别用latest,否则同一个 compose 文件两天前跑得好好的,今天突然失败,就是因为版本漂移了。
镜像下载慢这个问题,在国内尤其常见。最省事的办法是在/etc/docker/daemon.json里配置镜像加速地址,类似:
{ "registry-mirrors": ["https://docker.m.daocloud.io"] }改完以后执行sudo systemctl restart docker。这个文件是 Docker 守护进程的全局配置,很多问题排查的第一步就是这个。
2.2 查看、删除与打包镜像
# 查看本地镜像 docker images # 删除镜像 docker rmi mysql:8.0 docker rmi $(docker images -q) # 删除全部 # 导出和导入镜像 docker save -o mysql.tar mysql:8.0 docker load -i mysql.tar新手经常搞混docker rmi和docker rm。rmi是删镜像,rm是删容器。如果一个容器还在引用某个镜像,直接rmi会报错,得先把对应容器删掉。docker images -q这个参数很实用,它只输出镜像 ID,配合其他命令做批量操作特别顺手。
docker save和load的应用场景是离线环境。生产服务器没有外网,开发机上把镜像打包成 tar 文件,拷到服务器上再 load,比搞什么私有仓库要快得多。注意docker save会把整个镜像的元数据和多层文件都打进去,tar 文件往往不小,一个 MySQL 8.0 能到 500MB 以上,传输前记得先看下文件大小。
docker tag和docker push也是镜像管理的常见操作,比如给本地镜像打一个私有仓库的 tag:
docker tag nginx:latest registry.example.com/ops/nginx:v1.0 docker push registry.example.com/ops/nginx:v1.0这一步在真正部署到团队内部仓库时非常常用,打好 tag 再 push,相当于给镜像做了命名空间和版本管理。
3. 容器生命周期:从 run 到 rm
容器的生命周期是 Docker 操作的核心部分。一个容器从创建到退出,中间会经历运行、暂停、停止、删除等状态,对应着一组命令。
3.1 docker run 的参数你真的懂吗
docker run是用的最多的命令,但很多参数组合没见过就容易写错。最基础的运行命令:
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0拆开看关键参数:
-d:后台运行。不加的话,命令会一直挂着,日志刷屏,终端被占住。这在调试时是有用的,但正式用一定要加-d。--name:给容器起名字,后续start、stop、rm、logs都可以直接用名字而不查 ID。-p 3306:3306:端口映射,宿主机的 3306 映射到容器的 3306。-e:设置容器内的环境变量。MySQL 镜像就是靠MYSQL_ROOT_PASSWORD这个变量决定初始密码的。
这里有个新手常踩的坑:-p后面的端口格式是宿主机端口:容器端口,前面是外部访问的端口,后面是容器内部服务的端口。有人写反了,结果外部连不上。排查的时候先确认docker port的输出和-p参数是否一致。
3.2 重启、暂停、停止与删除
容器跑起来之后,日常控制就是下面这几个:
# 停止容器 docker stop nginx # 启动已停止的容器 docker start nginx # 重启容器(先停止再启动) docker restart nginx # 暂停容器(冻结进程,不停止) docker pause nginx docker unpause nginx # 删除容器(需要先停止) docker rm -f nginxdocker stop会发送 SIGTERM 信号等容器优雅退出,等待默认 10 秒,超时再 SIGKILL。docker restart在生产环境更新配置后经常用到,比如修改了端口映射、环境变量或者挂载目录,都需要重新创建容器,而不是直接 restart 就能生效的。严格来说,docker restart只是重启同一个容器,docker run会创建一个全新的容器。如果改了-p或者-v,必须删掉旧容器再重新run,别指望 restart 能带上新的配置。
docker pause在平时运维中使用频率不高,但在数据库容器需要临时冻结、又不希望进程被销毁时很有用,比如做备份前的状态冻结。
查看容器状态:
docker ps # 只看运行中的 docker ps -a # 所有容器,包括退出状态 docker ps -aq # 只显示 ID docker ps --filter status=exited排障时docker ps -a一定是优先看的。很多容器退出码不是 0,你只知道它挂了,但不知道挂在哪一步,这时候就要看退出码和日志。
3.3 一次性容器和临时交互
还有一种常见用法,跑一次性命令,比如拿到一个现成的 Python 或 Node 环境去执行一段代码:
docker run --rm -it python:3.11-slim python3--rm表示容器退出后自动删除,不占磁盘空间,非常适合临时调试。-it是两个参数合在一起:-i保持标准输入打开,-t分配一个伪终端。这两个参数配合才能进到容器里交互,少了任何一个,你都会看到“can't open input device”之类的奇怪现象。
一个形象的类比:-it相当于让你坐在容器终端前面可以敲键盘,如果没加-i,就像只能看屏幕却无法输入。很多教程里会写“进入容器”,其实提到exec -it,后面单独讲。
4. 进入容器、拷贝文件与日志排障
容器毕竟是隔离环境,出了问题,第一时间要做的就是钻进去看,或者把日志捞出来看。这三个能力是排障三板斧。
4.1 进入容器的正确姿势:exec vs attach
# 在运行中的容器里执行命令 docker exec -it nginx bash docker exec -it mysql8 mysql -uroot -p # 用 attach 挂接 docker attach nginxexec是在容器里新起一个进程,这个是最推荐的进入方式。比如进入 MySQL 容器里执行 SQL,或者进入 NGINX 容器里查看配置文件。exec不影响容器主进程,退出 exec 的终端也不会导致容器退出。
attach是把当前终端挂到容器主进程上,操作目标不是新进程而是主进程本身。如果容器主进程在运行前台服务,你 attach 上去按一下 Ctrl+C,容器就跟着停了。所以在容器里做交互操作时优先用exec,attach我基本只在需要观察主进程输出时用。
还有一个组合命令:如果容器没有 bash,就用sh:
docker exec -it 容器ID sh纯 Alpine 镜像里一般只有sh没有bash,直接exec -it写 bash 会报错。优先尝试sh更稳妥。
4.2 docker cp:容器和宿主机之间传文件
容器里的文件和宿主机不是同一个目录,要互传数据,可以用docker cp:
# 宿主机文件拷贝到容器 docker cp /opt/app.jar nginx:/usr/share/nginx/html/app.jar # 容器文件拷贝到宿主机 docker cp nginx:/etc/nginx/nginx.conf ./nginx.conf.bakcp的路径格式是容器名:容器内路径。这个命令在排查问题时很实用,比如想知道容器的配置文件和默认配置有什么差异,先拷出来 diff 一下。如果在容器里临时改了文件,重启容器会恢复初始状态,因为容器自身的可写层在容器删除后就没了,除非你用了数据卷(volumes),这个后文讲。
4.3 日志命令:logs、stats、top
# 查看容器日志 docker logs nginx docker logs -f nginx docker logs --tail 100 nginx # 查看容器资源占用 docker stats # 查看容器内进程 docker top nginxdocker logs可以不加-f查看全部日志,也可以-f跟随输出,相当于tail -f。--tail控制只显示最近 100 行,日志太多时必加,否则刷屏刷到怀疑人生。
docker stats会实时显示每个运行中容器的 CPU、内存、网络、磁盘 IO 占用情况。这个适合快速定位哪几个容器吃内存,相当于容器版的top。
一个真实案例:有一次 MySQL 容器突然连不上,我执行docker stats发现内存占用直接顶到容器限额,然后docker logs --tail 50看到 Out of memory 相关错误,最后把容器的内存限制调大重启解决。如果没有logs和stats,这种问题只能盲猜。
日志还有个容易被忽视的方向:日志量把磁盘塞满。docker logs默认是 JSON File 日志驱动,日志文件会一直往宿主机磁盘写,时间长了/var/lib/docker/containers这个目录可能膨胀到几十 GB。所以生产中建议配置日志轮转:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }配置了之后,单个日志文件达到 10MB 自动滚动,保留 3 份。这个配置建议在安装完 Docker 后第一时间设置。
5. 网络和数据卷:让容器真正可用
很多初学者跑容器本身没问题,但容器之间互通、数据持久化、端口映射这些才是生产环境的重点难点。网络和数据卷正是解决这些问题的两个核心机制。
5.1 容器网络模式:bridge、host 与自定义网络
Docker 默认创建的三种网络:bridge(桥接)、host(主机)、none(无网络)。通过docker network ls可以看到:
NETWORK ID NAME DRIVER SCOPE xxxxx bridge bridge local xxxxx host host local xxxxx none null local默认docker run不加--network参数就是 bridge 模式,相当于容器在一个私有的网桥里,容器之间可以通过 IP 访问,但外部访问需要做端口映射。
host 模式直接将容器挂在宿主机网络上,容器内的端口和服务直接占宿主机端口,没有 NAT。这种模式理论上性能好,但会带来端口冲突,而且跨宿主机不行。生产环境,除了对网络延迟极度敏感的中间件(比如一些日志采集器、网关类应用),我一般不太推荐 host。
自定义网络是生产环境中最常见的做法:
# 创建自定义网络 docker network create mynet # 指定网络运行容器 docker run -d --name nginx --network mynet nginx docker run -d --name redis --network mynet redis:7在同一个自定义网络里,容器之间可以通过容器名直接访问,比如nginx容器里可以直接redis:6379来连接 Redis,不需要查 IP。这个设计在微服务和多容器协作部署时特别重要——只要在同一个网络里,服务发现就是靠名字,根本不用管 IP 变化。
5.2 数据卷:容器删了数据还在
容器是可删除的,但数据不能跟着删。数据卷(volume)和绑定挂载(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 mysql8 -v /opt/mysql-data:/var/lib/mysql mysql:8.0区别在于:命名的数据卷/var/lib/docker/volumes/下由 Docker 管理,使用docker volume命令操作,适合备份恢复;绑定挂载是直接把宿主机的/opt/mysql-data目录映射到容器,适合访问宿主机已有配置和文件的情况,比如把配置文件映射进 Nginx 或把代码映射进 PHP 容器。
删除容器时,要考虑-v参数是否把对应的卷也删掉。有一个我踩过的坑:很久以前执行docker rm -f删除一批测试容器,没意识到它们用的是匿名卷(就是-v /var/lib/mysql没指定卷名),容器删除后卷并没有自动清理,磁盘空间白白被占用。后来做清理盘操作才查到这几个卷是孤儿数据卷,用docker volume ls -f dangling=true定位,然后用docker volume prune清掉。测试环境这样处理问题不大,生产环境千万不要乱执行prune,删错了卷导致的后果比删错容器更严重。
5.3 端口映射与外部互联
前面提到的-p是端口映射,但一个容器可能有多个端口需要暴露,例如 GitLab 的 HTTP、SSH,或者 MySQL 的 3306 和自定义管理端口。可以用多个-p:
docker run -d \ --name gitlab \ -p 80:80 \ -p 2222:22 \ -p 8443:443 \ gitlab/gitlab-ce:latest有时会发现端口起不来,报port is already allocated,八成是宿主机有别的进程占用了端口,或者另一个容器已经用了同样的宿主端口。这时候执行netstat -tlnp | grep 端口号或者ss -tlnp | grep 端口号查占用情况。端口映射是外部访问的关键,遇到问题先排除占位。
6. 批量管理与可视化面板:从命令行到面板
服务器的容器多了以后,一个一个敲docker run实在不现实。批量管理、可视化的方法在真实部署中很重要。
6.1 批量清理与过滤
docker ps -aq和组合命令是清理神器:
# 停止所有容器 docker stop $(docker ps -q) # 删除所有已停止的容器 docker rm $(docker ps -a -q) # 清理悬空镜像和未使用的卷 docker image prune -f docker system prune -af这里必须提醒:docker system prune -af会把所有未使用的镜像、容器、网络、构建缓存全部清理掉,不小心执行之后想恢复几乎不可能。日常操作中如果没有明确目的,我建议只执行docker image prune -f清理悬空镜像,就是对<none>这种无标签镜像的清理。危险操作要分期执行,不要图省事一步到位。
还有几个和批量管理配合使用的命令:docker inspect可以查看容器、镜像、网络的详细元数据,格式是 JSON。排查问题时会用--format提取特定信息:
# 查看容器 IP docker inspect -f '{{.NetworkSettings.IPAddress}}' nginx # 查看容器的挂载信息 docker inspect -f '{{json .Mounts}}' mysql8 | python -m json.tool6.2 Portainer:另一个视角的 Docker 管理
如果你的宿主机允许额外跑一个容器管理服务,Portainer 是个不错的选择。一行命令部署:
docker volume create portainer-data docker run -d --name portainer \ -p 9000:9000 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v portainer-data:/data \ portainer/portainer-ce之后浏览器访问宿主机 9000 端口,设置管理员密码,就能看到所有容器、镜像、网络的图形化状态,支持直接一键启动、停止、删除、查看日志。这在团队的轻量运维里很受欢迎,尤其是给不会命令行的同事提供操作入口。
但要注意,Portainer 毕竟多一个容器,资源占用客观存在,而且涉及到 Docker API 权限,生产环境上要不要装,得结合团队的具体情况来定。个人维护的服务器,我觉得命令行反而更方便。
6.3 微服务或多容器场景
日常部署微服务项目时,单个docker run写起来很长,很容易出错。更主流的方式是用 docker compose,通过 YAML 文件定义多个服务及其依赖关系。虽然基础命令阶段还没到 compose,但如果你用docker run跑到第 5 个容器,就会发现需要编排手段了。
在 compose 中,service 的命名会直接作为同一网络下的 DNS 名称,这一点和自定义网络里的容器名解析是同一个原理。理解了基础命令之后,再回过来看 compose 会更容易接受:它无非是把docker run的各个参数搬到一个 YAML 文件里管理。
7. 常见问题排查:遇到报错怎么找关键点
这一节我们把热词里出现频率最高的几个报错拉出来集中过一遍,每个问题给一个确定的排查方向。
7.1 权限问题
刚装完 Docker,执行docker ps报permission denied ... dial unix /var/run/docker.sock,这是因为当前用户没有 docker 用户组的权限。解决方法:
sudo usermod -aG docker $USER newgrp docker加组后重新登录即可。我还遇到过一种情况,就是 Docker 服务本身没有启动,运行命令时报 connect 失败,用systemctl status docker一看才发现是服务挂了,先systemctl start docker再把它设置成开机自启。
7.2 镜像下载慢或超时
在国内拉镜像经常出现卡住或超时,最快的解决办法是配镜像加速器。注意,不同加速器近期都有可能调整,去网上找一个当前可用的地址填进daemon.json。改完之后重启 Docker,再次docker pull验证。
另一个思路是用代理,但这种场景比较敏感,这里就不展开了,加速器 + 海外服务器中转是最常规的路径。
此外,一次拉取多个镜像时,可以限定并发:
docker pull mysql:8.0 docker pull redis:7.0 docker pull nginx:latest这样排队拉取,看起来慢,但更稳定。并行拉取偶尔会把 docker 的存储驱动拖出奇怪的错误,这个需要多次实测才会感受到。
7.3 容器起不来:查退出码和日志
最常见的情况是docker run执行完,终端上显示的是长串 ID,但docker ps -a看到容器处于Exited (1)状态。这时候执行docker logs 容器名看报错。
以 MySQL 为例,如果日志里出现Can't connect to local MySQL server through socket,多半是/var/lib/mysql目录权限或者数据目录路径挂载错误。Redis 如果启动失败,则要看appendonly相关配置和日志文件权限。GitLab 起不来,大概率是磁盘可用空间不足或者已有占用端口的进程。
还有一种常见陷阱是容器反复重启,输出日志都是正常启动后又退出。先docker inspect看RestartCount和State,如果最后的状态是OOMKilled: true,说明容器内存超限被系统杀掉,把容器内存限制调高或者优化程序的内存占用。
7.4 Docker Desktop 启动失败
热词里的virtualization support not detected和npipe报错前面已提过,这里补充两点。第一,BIOS 开启虚拟化以后,检查 Windows 自带的“基于虚拟化的安全”是否开启,有时候需要关闭内核隔离;第二,Docker Desktop 连接 npipe 失败时,可以去服务列表里确认com.docker.service是否在运行,Windows 服务管理里把它设置为自动启动。如果还不解决问题,干净重装 Docker Desktop 往往比反复折腾更省事。
7.5 其他常见问题速查表
| 症状 | 排查方向 | 常用命令 |
|---|---|---|
| 端口映射后外部无法访问 | 检查防火墙/安全组,docker port确认映射 | firewall-cmd --list-ports |
| 容器间网络互不通 | 确保在同一自定义网络,检查网络隔离 | docker network inspect mynet |
| 容器时区不正确 | 环境变量TZ=Asia/Shanghai或挂载/etc/localtime | docker exec -it 容器名 cat /etc/timezone |
| 磁盘空间占满 | 清理悬空镜像、停止容器日志文件 | docker system df |
| 容器内 DNS 解析异常 | 查看宿主机 DNS 及容器网络模式 | docker exec -it 容器名 cat /etc/resolv.conf |
8. 常用命令速查表:贴在手边的实操清单
这里整理一份基础命令清单,粘贴到一个 Markdown 笔记或者终端别名文件夹里,用的时候对照即可。
| 场景 | 命令 | 说明 |
|---|---|---|
| 查看所有本地镜像 | docker images | 显示仓库、标签、ID、大小 |
| 搜索远程镜像 | docker search 关键词 | 支持星数排序 |
| 拉取镜像 | docker pull 镜像名:tag | 不写 tag 默认 latest |
| 删除镜像 | docker rmi 镜像名:tag | 可跟镜像 ID |
| 查看运行中的容器 | docker ps | 默认只显示运行中 |
| 查看所有容器 | docker ps -a | 包括停止的 |
| 启动容器 | docker run -d --name app -p 宿主:容器 镜像 | 最常用的创建容器组合 |
| 停止容器 | docker stop 容器名 | 优雅停止 |
| 强制删除容器 | docker rm -f 容器名 | 停止并删除 |
| 进入容器 | docker exec -it 容器名 bash | 优先用 exec |
| 查看容器日志 | docker logs -f --tail 100 容器名 | 跟踪日志 |
| 查看资源占用 | docker stats | 实时刷新 |
| 拷贝文件 | docker cp 来源 目标 | 容器和宿主机互拷 |
| 查看容器元数据 | docker inspect 容器名 | 返回 JSON |
| 创建网络 | docker network create 网络名 | 自定义 bridge |
| 创建卷 | docker volume create 卷名 | 独立管理数据目录 |
| 清理悬挂镜像 | docker image prune -f | 释放空间 |
9. 实操心得与自我复盘
最后聊点我在实际项目里反复验证过的体会。
最先想说的是:不要一次性背所有命令。我见过不少同事买了一摞 Docker 书,背出所有参数才敢上手,实际上效果很差。比较好的路径是:先装一个 Nginx 或 MySQL,把run、ps、exec、logs、rm这几个核心命令练熟,然后遇到真实需求再去查参数。命令是拿来解决问题的,不是拿来做表演的。
其次是养成加--name的习惯。很多人图省事不写容器名,容器一多,全靠随机 ID 找人,排障时非常痛苦。只要加上唯一的名字,后续几乎所有操作都可以直接用名字,不用每次docker ps查 ID。
还有一个建议:日志轮转和daemon.json的配置要趁早做。我见过生产服务器上容器日志写了 30 多 GB,把数据盘填满,应用彻底停摆,源头就是没配置日志轮转。别等事故发生了再处理,提前配置一个 10MB 滚动 + 保留 3 份的方案就够了。
排查问题的顺序也有讲究。容器挂了,先docker ps -a看退出状态,再docker logs看应用日志,然后进入容器看运行环境;如果进程在但访问异常,就docker top看有没有对应进程,再看docker stats是否资源耗尽;网络问题才看docker network和端口映射。一套流程下来,95% 的基础问题都能定位。
这篇内容覆盖了 Docker 基础命令的常用范围:环境准备、镜像管理、容器生命周期、日志排障、网络与数据卷、批量清理和常见问题排查。如果你现在准备把容器用起来,我建议先拿 MySQL 8.0 做第一个实验对象——docker pull mysql:8.0后用docker run跑起来,用 Navicat 或者命令行连上去,这个过程会把最核心的参数都过一遍。练完这个,再上手别的镜像基本畅通无阻。