
很多人学Docker第一步就是打开搜索引擎查“Docker常用命令”然后把这个列表背下来。背完之后往往发现命令是记住了但不知道什么时候用哪条甚至同一类操作在docker run、docker start、docker attach、docker exec四个命令之间纠结半天。我最初也踩过这个坑以为多记几条命令就算会用了结果把MySQL容器一关数据全没了才意识到死记参数没用得先搞懂Docker命令背后那套对象模型和生命周期。这篇东西我不想再给你列一个“命令大全”。网上的大全多得是抄来抄去没差别。我更想按照实际干活时的使用顺序把高频命令串起来讲一遍先怎么启动容器再怎么做日常管理怎么进容器排查问题怎么处理数据和网络最后用真实部署场景把命令真正跑通。适合刚接触容器、想在服务器上落地的开发者也适合已经用了一段时间但命令理解还比较零散的人。1. 先把docker run拆明白命令结构比背诵参数更重要1.1 docker run背后的“三层语义”docker run可能是用得最多的命令也是误解最多的命令。很多人以为docker run就是把镜像启动一下就像双击exe。但实际上它做了三层事情先创建容器再启动容器最后把容器的标准输入输出接到你的终端。这三步对应docker create、docker start和docker attach只是run把它们合并成一步了。理解这点很重要。run不是“运行镜像”而是“从一个镜像创建出容器实例”。同一个镜像可以run出多个容器每个容器之间互相隔离有独立的文件系统、网络栈和进程空间。这就像同一份菜谱可以做出无数盘菜每盘菜都是独立的一份。所以排查问题的时候如果容器启动失败你可以拆开看是哪一步出了问题是创建失败镜像本身有问题、参数写错还是启动失败容器内进程崩溃、资源不足还是启动成功但终端没连上交互参数不对。很多人一看到docker run报错就怀疑镜像坏了其实多数时候是参数或者环境没给对。1.2 从镜像到容器的启动顺序与常用参数我平时最常用的run参数组合是这样的docker run -d --name myapp \ -p 8080:80 \ -e MYSQL_HOSTdb \ -v ./config:/etc/app/config \ --restart unless-stopped \ nginx:1.25逐个说下为什么这样写-d后台运行。不加的话容器会占住整个终端CtrlC一按容器就停了。生产环境几乎都用-d。--name给容器起名。不起名的话Docker会给一串随机ID后续操作全靠ID非常痛苦。-p 8080:80端口映射宿主机8080端口转发到容器内80端口。注意左边是宿主机右边是容器内方向反了会连不上。-e注入环境变量。MySQL的root密码、Redis的requirepass、各种服务的连接串基本都是用-e传进去的。-v挂载数据卷。把宿主机的目录或Docker卷挂到容器内这样容器删了数据也不会丢。--restart unless-stopped崩溃重启策略。服务器重启之后容器能自动拉起来unless-stopped表示只有手动stop过的容器才不自动启动是我个人最推荐的值。如果只是临时跑个测试容器我习惯额外加一个--rm参数容器退出时自动删除不留垃圾。但注意一旦加了--rm容器停止就会被清理里面未提交的数据也跟着没了想保留数据就别用它。1.3 前台运行还是后台运行区分run与start还有一类常见问题容器起来了但马上又退出了。这时有个概念容易被忽略Docker容器的生命周期由主进程决定。主进程退出容器就退出哪怕你用-d也一样。举例来说很多人做测试时这么跑docker run ubuntu然后发现容器瞬间退出。因为Ubuntu镜像默认的CMD是bash没有终端交互时bash立刻退出容器的主进程没了容器自然停止。正确做法是加-it开启交互终端docker run -it --rm ubuntu bash但如果你已经把容器停在后台了想再进终端也不用重新run直接docker start再docker exec进去就行。start命令启动一个已存在的容器exec则是在运行中的容器里再开一个进程。这两个命令和run的侧重点完全不同千万别混用。2. 容器日常管理ps、stop、restart、rm的使用节奏2.1 查看容器状态的三个命令与状态差异管理容器第一步是看清当前状态。docker ps只管运行中的容器docker ps -a列出所有容器包括已经停止的、异常退出的。状态字段里有created、running、paused、exited、dead几种看到exited别急着认为“容器坏了”先看退出时间原因。我常用的排查组合是docker ps -a docker ps -a --filter statusexited--filter很好用可以按状态、镜像、名称过滤容器多了以后比grep管道干净很多。另外docker stats可以看到每个容器的实时CPU、内存、网络流量发现某个容器吃满内存不用上服务器看监控这个命令最快。2.2 真正删除容器时要注意的两种清理删除容器用docker rm删除镜像用docker rmi这两个命令长得太像我见过太多人把rm敲成rmi结果镜像没了。删除容器的时候一定要先确认容器已经停止否则得用docker rm -f强制删。还有更狠的清理大法docker container prune把所有已停止的容器一次性清掉。docker image prune则是清理未被任何容器使用的悬空镜像。这两个prune命令在生产环境要慎用因为它们会全局清理建议先加-a或--filter缩小范围。2.3 一个容易混淆的场景容器退出后数据还在吗关键问题来了容器删了数据还在吗分两种情况。如果你是用docker run起的容器里面写入的文件只存在容器的可写层docker rm之后全部消失。这就是为什么前面强调必须用-v挂载卷。但如果你只是docker stop而没有rm容器还在里面的数据还在重新docker start就能恢复。很多新手在这块栽过跟头MySQL容器运行几个月某天想改配置直接rm了容器重新run结果数据库里的数据全没了因为从来没用volume挂载/var/lib/mysql。这个坑确实太常见了我一会儿在部署案例里会再强调一次。3. 镜像管理高频操作pull、build、tag、push、save与load3.1 拉取镜像时的版本选择与tag规范下载镜像用docker pull看起来简单但tag选择有讲究。nginx不带tag默认拉latestlatest在生产环境是个坑你以为每次都是一样的版本其实仓库更新后latest内容会变再pull可能镜像行为就变了。我的习惯是锁定精确版本号例如mysql:8.0.36、redis:7.2.4而不是mysql:8.0甚至mysql:latest。锁定版本至少你的环境是可复现的。另外国内拉取官方镜像经常超时建议先检查镜像加速是否配置好docker info可以直接看到当前配置的Registry Mirror。3.2 本地构建镜像从Dockerfile到build命令自己构建镜像时docker build是最核心的命令。它的基本形态是docker build -t myapp:v1.0 .末尾的.指定构建上下文目录。很多人以为点号指的是Dockerfile位置其实它指的是“把哪些文件发送给Docker守护进程作为构建上下文”。如果你的目录里有一堆无关的大文件构建会变得异常慢所以项目根目录应该放一个.dockerignore文件排除node_modules、dist、日志等目录。构建过程中每一步RUN指令都会生成一个镜像层。层数越多镜像越大。为了让镜像精简常见的做法是把临时文件清理和编译操作合并到同一条RUN指令里这样不会在中间层里留下缓存垃圾。这块其实已经不只是命令的问题而是Dockerfile编写习惯的问题了但至少你要知道build命令输出里那些Step信息是什么意思出错了才好定位到具体某一行。3.3 离线分发与仓库推送内网环境或者跨机器迁移镜像时docker save和docker load是救命稻草docker save -o myapp.tar myapp:v1.0 docker load -i myapp.tar注意save保存的是镜像本身不是容器。如果想保存容器当前的状态可以用docker commit打成新镜像但我不太建议对运行中的容器commit因为容器内往往有大量运行时临时文件产出的镜像既不干净也不可复现。正确路径永远是“改Dockerfile然后重新build”commit只适合临时抢救一个跑起来但没留下构建文件的环境。推送到镜像仓库用docker push但推送之前必须把镜像打好仓库地址的tagdocker tag myapp:v1.0 registry.example.com/library/myapp:v1.0 docker push registry.example.com/library/myapp:v1.0忘记打tag就直接push会报错提示需要指定repository不是因为网络问题而是因为你没先搞清楚target。4. 深入容器内部exec、attach、logs、cp的正确打开方式4.1 exec与attach的本质区别容器在后台跑怎么进去看两个命令docker exec和docker attach。docker exec是在运行中的容器里新启动一个进程这个进程和容器主进程相互独立。最常见的用法是docker exec -it mysql8 mysql -uroot -p docker exec -it webapp bashdocker attach则是把当前终端重连到容器的主进程上。如果容器主进程是nginxattach上去你看到的可能是nginx访问日志流不是shell。很多人attach上去之后发现“卡住了”按什么都没反应其实是因为主进程不接收输入。想退出attach用CtrlP然后CtrlQ这个快捷键是分离而不是终止容器直接CtrlC会把主进程干掉容器就停了。所以我个人的使用习惯是99%的情况用exec只有调试容器主进程的输入输出时才用attach。4.2 日志排查的常用组合命令排错第一件事永远是看日志。docker logs的基本用法docker logs -f --tail 200 mysql8-f是持续跟踪像tail -f一样实时滚出来--tail 200是只看最近200行不然日志文件几百兆的时候直接刷屏啥也看不清。还有一个容易忽略的参数是--since和--until可以看某个时间窗口内的日志docker logs --since 10m mysql8 docker logs --since 2025-01-01T00:00:00 --until 2025-01-01T01:00:00 mysql8容器一直反复重启但就是起不来时docker logs只能看到当前容器实例的日志历史实例的日志会跟着容器删除而消失。这时候应该先docker ps -a看一下容器的重启次数和退出码比如exit code 137通常是内存被杀139是段错误结合日志一起才能定位。4.3 文件进出容器cp命令与挂载的取舍临时要从容器里拷文件出来或者把文件塞进容器用docker cpdocker cp myapp:/app/logs/app.log ./logs/ docker cp ./backup.sql mysql8:/tmp/backup.sql这个命令不需要容器里预装scp或者ftp直接走Docker守护进程操作很方便。但我得说清楚docker cp适合临时操作不适合日常同步文件。如果你每次部署都要往容器里拷代码那你应该重新build镜像或者用-v把目录挂载进容器改宿主机的文件容器内立刻生效。我在开发环境里经常这么干用-v ./code:/app挂载源码目录改一行代码刷新就能看到不用反复build镜像。但是生产环境不要这样挂载源码因为宿主机目录内容变化是不可控的镜像的“可复现性”就没有了。5. 网络与数据卷端口映射、volume与网络模式5.1 端口映射的坑为什么“映射了但连不上”端口映射是Docker新手最容易卡住的地方。常见的报错场景docker run -p 8080:80 nginx然后浏览器访问http://服务器IP:8080发现连不上。排查顺序是这样的第一看容器是否真的把80端口监听了docker ps里看PORTS列第二看宿主机端口监听情况ss -tlnp | grep 8080第三看云服务商安全组有没有放行8080端口。很多时候Docker本身没问题是防火墙或者安全组挡了。另一个坑是docker run之后才发现端口写错了想改端口映射。端口映射是容器创建时定好的不能直接修改只能重新创建容器。这也是为什么--name要起到位间接换端口时方便操作。5.2 数据持久化volume、bind mount与匿名卷数据持久化有三种方式它们的区别值得认真理解匿名卷-v /container/path创建Docker自动生成卷名很难管理不推荐。命名卷-v mydata:/container/pathDocker托管卷数据存在/var/lib/docker/volumes下备份迁移稍麻烦但安全。绑定挂载-v /host/path:/container/path直接把宿主机目录映射进去适合配置文件、日志、开发代码。判断该用哪种标准很简单容器删掉后这些数据还有没有存在的价值。数据库数据、缓存数据、上传文件用命名卷需要人工编辑的配置文件、日志文件用绑定挂载。查看卷的状态docker volume ls docker volume inspect mydata有时候容器明明挂了卷重启后数据还是不对这时候基本是挂载路径写错了。容器内具体把数据写到哪个目录一定要去镜像的官方文档里确认比如MySQL的默认数据目录是/var/lib/mysqlRedis持久化文件放/usr/local/etc/redis或自定义路径写错了等于白挂。5.3 自定义网络与容器互联默认情况下容器启动会自动连到名为bridge的默认网络几个容器之间可以通过IP互相访问但重启后IP可能变化。更好的方式是自己建一个网络让容器通过容器名互相访问docker network create mynet docker run -d --name mysql8 --network mynet -e MYSQL_ROOT_PASSWORDroot123 mysql:8.0 docker run -d --name webapp --network mynet -p 8000:80 myapp:v1.0这样webapp里直接写mysql8:3306就能连上MySQL不需要关心MySQL容器在哪个IP。这个方式在docker compose里用得更多但即使你只用命令行这个习惯也值得养成。网络排查时docker network inspect mynet可以看到这个网络上所有容器及其IP检查容器是否真的在同一网络里一查就清楚。6. docker compose场景下的命令速查6.1 up、down、ps、logs日常编排四件套容器多了以后一条条docker run管理起来太痛苦用docker compose做编排就成了必然路径。compose文件写的是服务定义与之配套的命令和纯docker命令不是一一对应的它有自己的一套操作方式。最核心的就是up、down、ps、logs四个命令docker compose up -d docker compose ps docker compose logs -f docker compose downup -d会读取当前目录下的compose.yaml创建并启动所有服务。注意up不只会创建新容器发现配置有变化时还会重新创建受影响的服务容器这点务必记住。down则是停止并删除所有由这个compose文件管理的容器和默认网络。如果你只想停服务但保留容器用docker compose stop。很多新人上来就down结果容器没了又要重建浪费时间。6.2 修改配置后如何优雅重启服务改compose文件里的镜像版本、环境变量、挂载路径之后想让改动生效正确命令是docker compose up -d --force-recreate或者指定某个服务单独重建docker compose up -d mysql8docker compose restart也可以重启但注意它只是重启现有容器不会应用新的配置。如果改了compose文件只用restart会发现环境变量、端口这些根本没变容易产生“改了没生效”的错觉。6.3 依赖管理与启动顺序compose文件里可以配置depends_on控制服务启动顺序。比如应用依赖MySQL和Redis通常这样写services: webapp: build: . depends_on: - mysql8 - redis核心逻辑是先启动依赖服务再启动应用服务。但depends_on只保证启动顺序不保证依赖服务“已就绪”。MySQL容器启动了不代表3306端口已经能接受连接。所以应用侧还是要做连接重试或者在compose里面用健康检查配合depends_on的condition字段。这个细节在真实项目中经常踩到webapp启动时连MySQL报“Connection refused”重试几次就好了。不是命令问题而是服务就绪顺序问题。7. 真实部署案例串讲MySQL 8.0、Redis主从、GitLab7.1 安装MySQL 8.0并完成初始化配置搜索热词里关于Docker安装MySQL的问题非常多这里给一个我实测过多次的最小可用方案docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123 \ -e MYSQL_DATABASEappdb \ -e TZAsia/Shanghai \ -v mysql8_data:/var/lib/mysql \ mysql:8.0有几个要点必须说明白。第一MYSQL_ROOT_PASSWORD初次创建容器时设置之后不会通过环境变量修改生效。想改密码进容器用SQL改。第二MYSQL_DATABASE会自动创建一个空库但这不代表连接权限都配好了应用账号最好单独创建。第三-v mysql8_data:/var/lib/mysql这一条无论如何都要有否则容器一删数据库数据全没。需要导入已有数据时先把SQL文件拷进去再执行docker cp backup.sql mysql8:/tmp/backup.sql docker exec -it mysql8 bash -c mysql -uroot -proot123 -e source /tmp/backup.sql7.2 Redis主从集群的容器化方案用Docker跑Redis主从不像MySQL那么复杂但要注意网络模式和配置文件的挂载。先建一个自定义网络然后启动主节点docker network create redis-net docker run -d \ --name redis-master \ --network redis-net \ -p 6379:6379 \ -v /etc/redis/redis-master.conf:/usr/local/etc/redis/redis.conf \ redis:7.2 redis-server /usr/local/etc/redis/redis.conf从节点默认要复制主节点数据所以需要配置replicaof。如果你的redis.conf里写的是replicaof redis-master 6379那么从节点可以这样启动docker run -d \ --name redis-slave \ --network redis-net \ -p 6380:6379 \ -v /etc/redis/redis-slave.conf:/usr/local/etc/redis/redis.conf \ redis:7.2 redis-server /usr/local/etc/redis/redis.conf主从节点在同一个自定义网络里天然可以通过容器名通信这正是我前面强调自定义网络的实用场景。验证主从状态用docker exec -it redis-slave redis-cli info replication关注role:slave和master_link_status:up这两行看到master_link_status:up才说明主从已经建立。7.3 GitLab等重量级镜像的部署注意事项GitLab这类镜像动辄好几个GB启动占用内存很高部署时要特别注意挂载和内存限制。一个简化版的命令是这样docker run -d \ --name gitlab \ -p 8022:22 \ -p 8080:80 \ -v gitlab_config:/etc/gitlab \ -v gitlab_logs:/var/log/gitlab \ -v gitlab_data:/var/opt/gitlab \ --restart always \ gitlab/gitlab-ce:16.6.2-ce.0注意端口不要和宿主机已有的SSH冲突所以我映射成8022。GitLab首次启动要等几分钟先跑docker logs -f gitlab确认初始化过程完成再访问页面别在启动一半的时候就急着刷新。如果发现容器不断重启多半是内存不够看dmesg | tail有没有OOM记录有的话加内存或者限制GitLab的unicorn worker数量。这类重量级镜像的管理节奏和轻量容器完全不同启动慢、资源占用高、日志量大所以docker logs --tail这类命令在实际排查里会频繁用到。8. 高频故障排查权限、虚拟化与网络不通的命令诊断链路8.1 permission denied与docker group的坑刚装完Docker第一次跑docker ps最常看到的就是这个Got permission denied while trying to connect to the Docker daemon socket原因是你当前用户不在docker用户组里。解决方式有两种一个是命令行用sudo另一个是把用户加入docker组sudo usermod -aG docker $USER newgrp docker加入docker组之后要重新登录终端才生效建议执行一下newgrp docker刷新当前会话不然你会怀疑命令没生效。这里顺带提醒一句加入docker组意味着这个用户有等同于root的Docker操作权限生产环境里给哪些人加入docker组要注意不是随便谁都能加。也有人会图省事直接chmod 666 /var/run/docker.sock我不建议这么做。这是把Docker的socket完全放开等于任何人拿到这个文件都能操作你的Docker风险不小。8.2 Docker Desktop的virtualization support not detected问题热词里出现很频繁的Docker Desktop启动失败问题报错一般是“virtualization support wasnt detected”在Windows上尤其常见。这个问题不是Docker Desktop本身坏了而是底层的虚拟化能力没满足要求。排查路径有三条第一步确认Windows的虚拟化功能是否开启运行systeminfo看最后一段的Hyper-V要求第二步检查BIOS里的Intel VT-x或AMD-V有没有被关闭很多品牌机默认关了虚拟化第三步确认Windows的“虚拟机平台”和“适用于Linux的Windows子系统”这两个功能是否已启用Docker Desktop新版依赖WSL2光有Hyper-V不够。命令行里可以用这个命令看WSL状态wsl --status如果你看到WSL版本是1说明需要升级到WSL2执行wsl --update再重启Docker Desktop。大部分“virtualization support not detected”的报错最终都能在这三个步骤里找到原因。8.3 容器网络不通的排查顺序网络不通是Docker场景里最磨人的问题。我总结了一套固定排查链路按照从内到外的顺序走一遍基本能定位第一步确认容器本身网络是否正常docker exec -it webapp ping mysql8如果名字解析不了多半是没在同一个自定义网络里。第二步检查容器网络详情docker network inspect mynet看容器的IP和所属网络是否和预期一致。第三步检查宿主机端口转发ss -tlnp | grep 容器映射端口确认有进程在监听。如果监听的是127.0.0.1而不是0.0.0.0外部访问会失败。第四步检查防火墙和安全组。iptables -L -n看是不是有DROP规则拦截了Docker的流量。很多云服务器的安全组默认只放行22和808080这种自定义端口没放行外部就是连不上。这套流程走下来绝大多数网络问题都能定位到具体环节。最怕的就是上来就重启容器重启确实能解决一部分问题但如果你根本不知道根因同一个坑下次还会再踩。我在实际使用中的体会是Docker命令其实不需要背多少条核心就那些关键是每一条命令在什么时机用、背后的对象关系是什么。你把这个逻辑想清楚了哪怕换一套编排工具也能很快迁移过去。最后再分享一个小技巧拿不准参数时不要急着翻文档先执行docker run --help或docker compose help官方自带的帮助信息比网上大多数教程都完整而且版本一定和你的环境对得上。