学Docker最绕不开的就是那一堆指令。我遇到很多朋友,折腾了半天Docker,下载倒是搞定了,结果一上来就被docker run这一串参数搞得晕头转向。尤其是从Windows环境入门的朋友,装个Docker Desktop就够呛,好不容易装好了,准备部署个MySQL、Redis,打开命令行又不知道该敲什么。今天这篇,我就把Docker从安装到实战的指令体系彻底捋一遍,按真实使用场景拆解,每一步讲清楚为什么要这么敲,而不是甩你一本命令手册。
这篇内容适合刚接触容器、想快速上手的老手,也适合那些被各种报错折磨到怀疑人生的新手。我尽量用大白话和实际案例来拆解,把常见的坑提前给你踩平。不用死记硬背,理解了底层逻辑,指令就是个查字典的事。
1. 安装与启动:先让Docker跑起来
很多人以为安装Docker就是下载个安装包双击就完事了,但实际操作中,服务器和Windows桌面端的玩法完全不同。这一部分最核心的目标就一个:让Docker的守护进程(daemon)跑起来,并且能正常接收你的指令。
1.1 不同平台的安装指令差异
如果你用的是Linux服务器(腾讯云、阿里云或者自建的CentOS/Ubuntu),安装方式差别很大,千万别混用。
CentOS系统下,我强烈建议不要直接用yum install docker,因为默认源里的版本太老。正确操作是先安装yum-utils工具,然后添加Docker官方源的仓库地址,再安装社区版引擎。这一步骤里最关键的指令是:
sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.ioUbuntu则稍微简单点,但也是建议走官方源。有些教程让你直接apt install docker.io,那个版本虽然能用,但往往滞后,遇到新版镜像特性时会踩坑。所以如果你追求稳定和功能完整,还是建议添加官方GPG密钥和仓库后再安装。这里有个细节:安装完成后,记得把当前用户加入docker用户组,否则每次敲docker ps都要加sudo,极其影响使用体验:
sudo usermod -aG docker $USER newgrp dockerWindows用户基本就是下载Docker Desktop安装包,这个没太多指令可讲,但有个关键点:新版Docker Desktop默认基于WSL2后端,在安装前最好先跑一遍wsl --status确认子系统状态正常。安装完以后,Docker Desktop托盘图标变绿,命令行里docker version能同时看到客户端和服务端版本号,就算成功了。
1.2 验证安装与启动管理的核心指令
装完第一件事就是验证。我在服务器上最常用的验证指令是sudo docker run hello-world。这个指令会自动拉取一个极小的测试镜像,然后创建一个临时容器,成功运行后输出一段欢迎文本。如果能看到这段输出,说明客户端、守护进程、镜像仓库之间的整条链路都是通的,后面基本不会出现什么莫名其妙的环境问题。
启动管理的指令序列也要熟悉。Linux服务器上,Docker安装后不会自动启动,需要手动控制:
sudo systemctl start docker sudo systemctl enable docker sudo systemctl status docker我的建议是把enable和start一起跑,这样服务器重启后Docker能自动拉起。别小看这一步,很多人在生产环境重启后容器全丢了,其实不是容器没了,是Docker服务根本没起来。查看状态时看到active (running)就说明守护进程跑起来了。
还有一个容易忽略的验证点:配置镜像加速器。国内云服务器直接拉取官方镜像经常超时,因为Docker Hub的访问链路慢。像阿里云、腾讯云都有免费的个人加速器地址,配置方法是在/etc/docker/daemon.json里写一行registry-mirrors配置,然后重启Docker。这个操作能直接避免后面80%的拉取超时问题,是安装阶段最值得花1分钟做的事情。
1.3 Windows下Virtualization报错的实战排查
Windows用户最常踩的坑,是Docker Desktop启动时弹virtualization support not detected或者类似的虚拟化未开启提示。这个报错字面意思很直接:Docker Desktop需要硬件虚拟化支持,但系统没让它用上。
排查步骤一般是按顺序来的。先打开任务管理器,切到“性能”页签,看右下角“虚拟化”这一项是不是“已启用”。如果是“已禁用”,那就要进BIOS里找Intel VT-x或AMD-V的开关,主板厂商不同选项位置不同,但基本都在Advanced或CPU Configuration菜单里。开启后保存重启再来。
如果任务管理器显示虚拟化已经启用,但还是报这个错,那问题多半出在Windows功能没开全。控制面板里找到“启用或关闭Windows功能”,把“适用于Linux的Windows子系统”和“虚拟机平台”两项勾上,重启电脑后再启动Docker Desktop。还有一种情况是WSL2内核版本太旧,可以在命令行里跑wsl --update更新内核。实测下来,按照这个排查顺序,90%以上的虚拟化报错都能解决。
2. 基础指令体系:从镜像到容器的生命周期
Docker的指令体系看着多,其实逻辑非常清晰。只要抓住一条主线——把镜像想成“建筑图纸”,把容器想成“按图纸盖出来的房子”——大部分指令你就知道该去哪里找答案了。镜像负责定义,容器负责运行,数据卷是保险柜,网络是连接各栋房子的道路。
2.1 容器全生命周期的指令对照表
我把最常用的操作分成了四类,整理成一张速查表,虽然网上类似的表很多,但这份完全是我根据自己的使用频率筛出来的,那些一年也用不到一次的冷门指令就没有列进来。
| 用途 | 指令示例 | 使用频率 | 注意事项 |
|---|---|---|---|
| 拉取镜像 | docker pull mysql:8.0 | 极高 | 指定标签(tag),别裸拉latest |
| 列出镜像 | docker images | 极高 | 加-a可看中间层镜像 |
| 运行容器 | docker run -d -p 3306:3306 mysql:8.0 | 极高 | 核心参数后面细讲 |
| 查看容器 | docker ps | 极高 | 加-a列出已退出容器 |
| 进入容器 | docker exec -it <容器名> bash | 极高 | 容器内没bash就改用sh |
| 查看日志 | docker logs -f <容器名> | 极高 | 排错第一指令 |
| 停止容器 | docker stop <容器名> | 高 | 注意是优雅停止 |
| 删除容器 | docker rm <容器名> | 高 | 先停后删,加-f强制 |
| 删除镜像 | docker rmi <镜像ID> | 中 | 有容器引用时删不掉 |
| 资源统计 | docker stats | 中 | 实时看CPU内存占用 |
| 查看配置 | docker inspect <容器名> | 中 | 输出超级长,可配合过滤 |
| 拷贝文件 | docker cp 文件 容器:路径 | 低 | 调试时才用,平时用挂载 |
这张表的核心逻辑是:所有指令都围绕镜像和容器两个对象打转,操作前先搞清楚操作的是哪一类对象。
2.2 run指令的黄金参数:端口、数据卷、环境变量
docker run是整个Docker体系里最庞大、也最容易劝退新手的指令。它的参数多得吓人,但真正决定你是否能完成日常任务的,其实就那几个黄金参数。
端口映射-p参数是必须理解透彻的第一个。-p 3307:3306的意思是,把宿主机的3307端口和容器的3306端口打通。为什么要这么绕?因为容器有自己独立的网络命名空间,宿主机上访问不到容器内的端口。你可以把宿主机想象成小区大门,容器是里面的住户,-p就是给每户配一个专属门牌号。注意,宿主机这边端口可以随便换,但容器那边端口必须是软件本身监听的端口,比如MySQL是3306,Redis是6379,GitLab是80或443。
数据卷-v参数解决的是数据持久化问题。容器一旦被删除,里面的所有数据就跟着消失了,这对数据库来说是灾难。-v mysql-data:/var/lib/mysql的意思是,把宿主机上名为mysql-data的目录(实际存储在/var/lib/docker/volumes/下面)挂载到容器的/var/lib/mysql路径,这样哪怕容器删了重建,数据还躺在宿主机上。我强烈建议所有跑数据库的容器都加上-v,否则你迟早会经历一次“一夜回到解放前”的惨痛教训。
环境变量-e参数则是传递给容器内应用配置的主要途径。比如MySQL镜像就是通过-e MYSQL_ROOT_PASSWORD=123456来设定初始密码的,Redis镜像通过-e可以传一些启动参数。这个设计很巧妙,它让同一个镜像能通过不同环境变量适配不同场景,而不需要修改镜像本身。我自己的习惯是敏感信息不要直接写在命令行里,而是用--env-file指定一个配置文件,这样不容易被history命令泄露。
2.3 镜像搬运与清理:save、load、prune
镜像相关的高频指令中,除了pull和images,搬运和清理也是日常刚需。
在内网环境或者需要批量部署时,docker save和docker load就是救命稻草。docker save -o mysql.tar mysql:8.0可以把镜像打包成一个tar文件,拷到另一台机器上后执行docker load -i mysql.tar就能导入。这个操作在实际工作中非常常见,尤其是有些服务器不允许直接访问外网,只能通过离线包来同步镜像。
清理相关的指令则是每一个developers都应该掌握的卫生习惯。docker system df可以查看所有资源的占用情况,镜像、容器、数据卷、构建缓存加起来能吓你一跳。docker system prune会清理所有已停止的容器、悬空的镜像和未使用的网络,但要注意,这个指令默认不会删除被容器引用的镜像。如果磁盘实在告急,可以加-a参数连未被使用的镜像一起清掉,但风险是下一步pull时又要重新下载。构建缓存也要定期清,docker builder prune能释放经常高达几个G的缓存空间,这在磁盘紧张的服务器上效果立竿见影。
3. 典型业务场景的指令组合拳
光懂指令不落地等于纸上谈兵。这一节我用三个最常见也最典型的业务场景来讲透:部署MySQL 8.0、搭建Redis主从、部署GitLab。这三个案例覆盖了端口映射、数据卷挂载、配置挂载、容器间通信、容器内执行命令这五大核心技能点。
3.1 从零部署MySQL 8.0的完整指令流
部署MySQL 8.0是搜索热词里最高频的需求,也是大多数新手第一次真正用起来Docker的场景。这里面的坑特别多,我给你捋一个完整流程。
第一步拉镜像,docker pull mysql:8.0。注意这里一定要带8.0这个标签,因为MySQL镜像的latest标签在某个时期指向的还是5.7,不带标签直接拉很容易装成老版本。镜像本来不小,加上配置了国内的加速器,一般几十秒到几分钟就能拉完。
第二步启动容器,我推荐使用这条指令:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=YourPassword123 \ -v /data/mysql:/var/lib/mysql \ mysql:8.0这条指令里的每个参数都有讲究。-d让容器在后台运行,不加的话你的终端会被容器的日志霸屏;--name给容器起个名字,后续操作就不用记一长串容器ID了;-p 3306:3306把宿主机3306口接到容器3306口;-e指定root密码;-v把数据持久化到宿主机/data/mysql目录。
第三步验证容器状态。docker ps看看MySQL容器的状态是不是Up,然后docker logs mysql8看看日志里有没有ready for connections这句话。这里有个坑要提醒你:MySQL首次初始化需要大概20到60秒,启动初期容器状态是Up但连不上,这时查日志是最可靠的判断方式,不要急着排查什么网络问题。
最后一步进入容器操作。docker exec -it mysql8 mysql -uroot -p可以直接进入容器内的MySQL客户端。如果只是想看看容器内部的结构,docker exec -it mysql8 bash能打开一个shell。注意,如果提示找不到bash,说明这个基于发行版的镜像里没有bash,改用sh就行。
这里有个特别容易出问题的地方:如果你改了环境变量里的密码,容器重启后密码并不会跟着变。MySQL镜像的环境变量只在数据目录初始化时生效,后续密码修改只能通过SQL或者配置变更。所以第一次启动时密码一定要想清楚,否则后面改起来很麻烦。
3.2 Redis主从部署的指令组合
Redis主从的搭建过程比MySQL更体现容器思维。很多新手拿着物理机的思路去搭集群,结果被网络配置折磨得欲哭无泪。其实在Docker里搭Redis主从是一个天然的容器间通信练习。
先拉镜像:docker pull redis:7.0。然后创建自定义网络:
docker network create redis-net为什么要先创建网络?因为容器间通信需要在一个自定义网络里才能用容器名直接访问。默认的bridge网络不支持通过容器名互相解析,自定义网络自带DNS解析能力。这条指令能让你省掉后面百分之八十的烦恼。
接着启动主节点:
docker run -d --name redis-master \ --network redis-net \ -p 6379:6379 \ redis:7.0 --appendonly yes启动从节点的指令:
docker run -d --name redis-slave \ --network redis-net \ -p 6380:6379 \ redis:7.0 --replicaof redis-master 6379这套操作的核心理解点是:--network redis-net让两个容器进了同一个内网,从节点里的--replicaof redis-master 6379直接用容器名来定位主节点,而不是IP地址。因为Docker内部DNS会把容器名解析成对应的内网IP,这种方式比手动写IP可靠得多。容器重建后IP变了,但名字不变,配置也就永远正确。
启动后验证主从状态:docker exec -it redis-slave redis-cli info replication,看到role:slave和master_link_status:up就说明主从关系建立成功。这个场景如果你能独立跑通,那Docker的容器网络概念就已经入了一大半。
3.3 GitLab和青龙等其他常见部署的注意事项
除了MySQL和Redis,搜索热词里还频繁出现GitLab和青龙面板。这两个服务的Docker部署指令逻辑类似,但各有各的坑,我挑重点讲一点。
GitLab部署最核心的是把配置目录持久化挂载到宿主机上,因为它包含仓库数据、数据库和配置文件。启动命令一般长这样:
docker run -d --name gitlab \ -p 80:80 -p 2222:22 \ -v /data/gitlab/config:/etc/gitlab \ -v /data/gitlab/logs:/var/log/gitlab \ -v /data/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latest这里有个隐藏得很深的坑:GitLab容器启动很慢,有时要几分钟甚至十几分钟才能完全可访问,你以为卡死了,其实它内部在做初始化配置。还有一个是SSH端口映射问题,很多人用了-p 2222:22后,后续做Git操作时发现端口对不上,需要在GitLab的配置里把gitlab_shell['ssh_port']改成2222,否则克隆地址里不会有端口号。
青龙面板的部署相对简单,但它依赖管理的核心奥义是:依赖安装在容器内部,容器一旦重建就全部丢失。所以部署青龙时,除了标准的数据卷挂载,还得养成一个好习惯——把常用依赖的安装命令收集成一个脚本,容器重建后二分钟就能全部恢复。部署指令大体是拉whyour/qinglong镜像、映射端口、挂载/ql/data目录。这个案例给我们的通用启示是:容器本质上是不可变的,任何内部软件层面的修改,最好都能转成基础设施的能力,比如通过数据卷、配置文件或初始化脚本,而不是手动进容器去改。
4. 进阶编排:docker compose把多条指令合成一条
当你部署的容器越来越多,每次启动都要敲一长串docker run参数,你很快就会厌倦。这时候docker compose的出现简直是救星。它把多个容器的启动配置写进一个YAML文件,一句docker compose up -d就能全部拉起。
4.1 docker compose相比docker run的核心优势
docker compose解决的不只是指令长度问题,更重要的是它把基础设施配置代码化了。以前你部署一个微服务,手敲10条docker run指令,下次换台机器还得重新敲一遍,而且有个参数敲错了自己还发现不了。现在一个docker-compose.yml文件扔过去,docker compose up -d就完事。
它另一个核心优势是编排依赖关系。比如说你的应用依赖数据库和缓存,如果先启动应用再启动数据库,应用往往会报连接失败先退出。compose提供了depends_on配置来调整启动顺序,虽然它只控制启动顺序,不保证服务内部就绪,但配合健康检查(healthcheck)配置,可以做到真正的完整启动依赖。我用下来最舒服的一点是,compose默认会为项目创建独立的网络,所有服务自动加入这个网络,服务名就是相互访问的地址,不需要手动创建网络,这一大块踩坑点直接消失了。
Docker Compose的安装很简单,新版Docker Engine已经内置了docker compose插件,旧版需要单独下载二进制文件。验证方式是在终端里敲docker compose version,有版本输出就说明装好了。
4.2 一个可以直接抄作业的compose配置案例
我拿一个前后端分离项目举个例子,包含MySQL、Redis和一个Spring Boot应用。这个配置我改一改就能用在多数实际项目里:
version: "3.8" services: mysql: image: mysql:8.0 container_name: app-mysql environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: app_db ports: - "3306:3306" volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.0 container_name: app-redis ports: - "6379:6379" backend: build: ./backend container_name: app-backend ports: - "8080:8080" depends_on: mysql: condition: service_healthy redis: condition: service_started environment: DB_HOST: mysql DB_PORT: 3306 REDIS_HOST: redis volumes: mysql-data:这个配置里的核心知识点有三个。第一是depends_on后面的condition配置,它配合MySQL的healthcheck实现“数据库健康了才启动应用”。第二是backend里的环境变量直接写了DB_HOST: mysql,因为compose网络内服务名就是主机名。第三是最后的volumes声明,让具名数据卷能被compose管理生命周期。
实际操作时,在项目目录下执行docker compose up -d启动全部服务,docker compose ps查看状态,docker compose logs -f backend看指定服务日志,docker compose down停止并部分清理。就是这么简单,整套编排就能运转起来。要注意docker compose down默认不会删具名数据卷,所以数据是安全的,这个设计非常贴心。
4.3 compose常见的使用误区
我在实际工作中见过不少人在compose上踩坑,最典型的有三个。
第一个误区是YAML格式问题。name:前后多了一个空格,或者ports列表里的缩进不对,都可能让整个文件解析失败。好在docker compose config指令可以校验配置文件格式,执行后如果有语法错误会直接提示,这个指令在正式启动前先跑一遍是文虫不错的选择。注意,是docker compose config,不是config本身。新手最容易在这里碰到缩进两格还是四格的问题,我的建议是全文统一使用两个空格缩进。
第二个误区是depends_on被误以为保证服务就绪。默认的depends_on只控制容器创建顺序,不等待服务可用。这是新手最容易慢理解的地方——比如depends_on: mysql然后应用还是连接失败。解决办法就是我上面写的,加上健康检查和condition: service_healthy,这个组合才能做到真正意义上的等待就绪。
第三个误区是compose文件里频繁使用build而不缓存基础层。每次执行docker compose up时,如果build的上下文目录里文件变动频繁,会重新构建整个镜像。优化思路是尽量把依赖安装命令写在Dockerfile靠前的位置,利用Docker的层缓存机制,让业务代码之外的部分不会反复重建。
5. 问题排查与经验沉淀:指令之外的实战功夫
Docker用多了你会发现,真正让你生产力爆发的不是背了多久指令,而是面对问题时能用正确的顺序和组合拳快速定位。这一部分我把我私藏的排查思路和常见问题的应对方案分享出来。
5.1 docker网络不通的排查思路
网络不通是我被问得最多的问题,没有之一。不管是-p端口映射后宿主机访问不了,还是容器A访问不了容器B,基本的排查思路是固定的。
先查容器状态。docker ps -a看目标容器是不是Up状态,如果显示Exited了,那问题根本不在网络,先去查日志。日志就用docker logs --tail 100 容器名拉最近100行看原因。
容器是状态正常,再查端口映射。docker port 容器名能列出所有端口映射关系,确认宿主机端口和容器端口是否对应上了。然后你可以在宿主机上试一下curl 127.0.0.1:宿主机端口,如果能通,问题就出在宿主机的防火墙或云安全组上。这个定位很重要,因为很多云服务器默认安全组不放行你新开的端口,和Docker本身一点关系没有。
如果是容器A访问容器B不通,而且两个容器不在同一个自定义网络里,那几乎肯定是网络隔离导致。解决办法是创建自定义网络,然后把两个容器都加入这个网络,通过容器名互相访问。docker network ls查看所有网络,docker network inspect 网络名能看网络里有哪些容器,排查起来非常直观。
5.2 日志与inspect指令读懂的技巧
docker logs是最常用的排错手段,-f参数可以像tail -f一样实时跟踪日志输出。当容器内部应用启动失败时,日志里通常会给出真正的原因。比如MySQL容器密码初始化失败、Redis配置文件语法错误等,日志都会明明白白写出来。
docker inspect是另一种深层次的排查手段,它输出容器的完整配置信息,包括环境变量、网络IP、挂载卷、启动命令等。这个输出非常长,所以一般配合一些简单的方法来定位关键信息。如果在Windows PowerShell里可以用docker inspect 容器名 | Select-String "IPAddress",在Linux环境则习惯用docker inspect 容器名 | grep IPAddress。
我想强调一个细节:容器里查不到探测工具的应对思路。比如你进容器之后想跑curl,发现找不到这个命令。这是常见情况,因为官方镜像为了精简体积不会装调试工具。你可以用docker exec直接改敲宿主机的工具链,或者加载一个网络调试容器加入同一网络,比如docker run --rm --network 容器所在网络 nicolaka/netshoot,这个容器里装了全套网络调试工具,用完即删非常方便。
5.3 常见问题速查表
| 报错信息 | 常见原因 | 解决方案 |
|---|---|---|
| port is already allocated | 宿主机端口被占用 | 换宿主机端口,或docker ps找占用容器 |
| No space left on device | 磁盘满了或inode耗尽 | docker system prune清理+检查overlay2目录 |
| Cannot connect to the Docker daemon | 守护进程没起 | systemctl start docker并确认运行状态 |
| Get https://registry-1.docker.io/v2/: EOF | 镜像仓库拉取超时 | 配置镜像加速器或重试 |
| OCI runtime exec failed | 容器内没有目标命令 | 把bash换成sh,看看镜像基础发行版 |
| exit code 137 | 容器被OOM杀掉或手动kill | docker stats看内存,调整容器资源限制 |
| pull access denied | 镜像名或标签写错了 | 核实仓库名和版本标签,确认是否私有仓库 |
这张表是我日常碰到的频率最高的六类问题。处理这类问题的通用原则是:所有线上变更一定要谨慎,先弄懂问题根因再动手,而不是盲目重启。很多人遇到port is already allocated就直接乱改端口,其实先查清楚是谁占了端口更重要,因为那可能是你另一个服务的正常端口。
5.4 资源治理:别让Docker拖垮你的磁盘
容器环境跑久了,磁盘占用会越来越吓人。我见过一台闲置的docker宿主机,一年没清理,磁盘被悬空镜像和日志撑满。治理资源的第一步是正确使用docker system df,这个指令会告诉你镜像、容器、数据卷、构建缓存各自占了多大空间。
第二步是用好清理策略。docker system prune清理停止的容器、悬空的网络和悬空的镜像,这个操作相对安全,不影响正在使用的资源。docker system prune -a连没有被任何容器使用的镜像全部一起清掉,释放的空间最大,但是下次要用时得重新拉取。docker builder prune专门清理构建缓存,如果频繁打包镜像我特别推荐,因为构建缓存常常悄悄占用几十个G。
日志也是个隐形杀手。容器日志默认无限增长,如果应用本身输出量大,几天就能写满磁盘。有效做法是在docker run参数里加日志限制,比如--log-opt max-size=10m --log-opt max-file=3,或者去/etc/docker/daemon.json里配全局日志轮转。这个配置我强烈建议在一开始就做好,否则删日志的时候你会庆幸没有更晚设置。
资深使用者的几点忠告
用Docker这几年,我最大的体会是:指令本身不值得背,值得背的是它背后的模型思维。镜像定义状态,容器是运行的虚像,数据卷负责留存,网络负责互联——这四样东西的组合,就能搭出几乎所有应用的运行环境。每当你不知道用什么参数时,问问自己:我要操作的是镜像、容器、数据卷,还是网络?答案自然就出来了。
最后再分享一个我个人的小习惯。我会把工作中常用的docker指令整理成一个脚本文件,每加一个项目就多记一条。比如部署MySQL、Redis那几条标准的docker run,我根本不会去记忆完整写法,翻脚本复制粘贴改参数就够了。就算哪天换了新电脑,只要这个脚本还在,部署环境的效率不会损失太多。这也算是Docker实践中最实在的一条经验了。