Docker常用命令实战:镜像、容器与数据持久化
发布时间:2026/10/3 18:10:29 锦皓数字建站

搞Docker这事我见过太多人卡在同一个地方装完了、跑通了hello-world然后对着命令行发呆不知道下一步该干嘛。其实Docker本身的命令体系并不复杂真正让人头疼的是它背后的几个核心概念没理顺导致每敲一个命令都要去查一遍文档。这篇我基于自己的实操经验把Docker最常用、最高频的命令按用途重新梳理了一遍不是为了列一个命令大全而是想把“为什么用这个命令、什么时候用、参数怎么选”讲清楚。内容适合刚入门的新手也适合用过一段时间但总觉得体系混乱的朋友。看完你会发现平时部署MySQL、Redis、微服务那一大堆操作其实来来去去就那么几个命令在反复组合。1. 环境准备把Docker装到能用的状态1.1 先建立正确的脑内模型镜像和容器先说个最容易绕晕的点镜像和容器到底是什么关系。我自己的类比是——镜像像是一个“光盘安装包”里面已经装好了操作系统、运行环境、应用代码和依赖容器则是用这个安装包“装好并启动起来”的一个独立进程沙箱。同一个镜像可以启动多个容器每个容器相互隔离互不干扰。这个理解特别重要。因为后面几乎所有命令都围绕这两者展开docker pull下载镜像docker run用镜像启动容器docker build自己构建镜像docker commit把容器固化成镜像。如果脑子里清楚“镜像是静态的文件集合容器是运行中的实例”后面命令再多也不会乱。很多人最初会混淆docker ps和docker images前者查运行中的容器后者查本地已有的镜像。这两者完全是两个维度的东西别搞混。1.2 各平台安装与Virtualization Support问题安装这块Linux和Windows/macOS走的是完全不同的路子。Linux直接通过包管理器装比如Ubuntu上sudo apt update sudo apt install docker.io sudo systemctl enable --now docker装完验证一下docker version docker infosudo systemctl enable --now docker这步很多人会漏掉它的作用是让Docker服务开机自启并立即启动。如果你只执行了安装没有启动守护进程输任何docker命令都会报 “Cannot connect to the Docker daemon”。Windows和macOS一般用Docker Desktop。这里有一个非常高频的启动报错正好是热搜词里出现的那条Docker Desktop failed to start because virtualisation support wasnt detected看到这句话第一反应别急着重装软件先检查三件事BIOS/UEFI里虚拟化有没有开启。Intel叫VT-xAMD叫SVM开机进BIOS找到对应开关改成Enabled。Windows功能里“虚拟机平台”和“Hyper-V”有没有勾选。在“启用或关闭Windows功能”里把这两个勾上重启再试。WSL2是否安装且版本正确。Docker Desktop依赖WSL2内核执行wsl --status看一下版本不对就去更新内核包。我遇到过一种情况是电脑开了VT-x但Windows功能里Hyper-V没启用Docker Desktop照样报这个错。所以三步都要排查缺一不可。Linux下另一个高频坑是权限报错。刚装完Docker直接执行docker ps会收到docker: permission denied while trying to connect to the Docker daemon socket这是当前用户不在docker组里解决办法sudo usermod -aG docker $USER newgrp dockerusermod把用户加入docker组newgrp让新组立即生效或者退出重新登录。这是官方推荐做法别图省事把文件权限改成777那样后患无穷。1.3 镜像加速器解决下载慢的根方案装好之后第一个障碍就是镜像下载慢甚至超时。容器镜像大多托管在海外仓库国内直连经常抽风。这个问题的标准解法是配置镜像加速器。做法是写/etc/docker/daemon.jsonWindows/macOS在Docker Desktop的设置里配置内容大概长这样{ registry-mirrors: [https://docker.xxxxxx.com] }然后重启Docker使配置生效sudo systemctl daemon-reload sudo systemctl restart docker加速器的地址变化很快今天能用明天可能就失效建议把它当成一次性配置来对待失效了就换新的。云厂商提供的加速器通常最稳定但很多也需要实名或内网环境选的时候留个心眼。配置成功之后随便拉一个镜像试试下载速度应该有肉眼可见的提升docker pull nginx:latest2. 镜像管理拉取、查看、清理的正确姿势2.1 拉取镜像的细节标签、仓库、平台镜像下载的核心命令是docker pull但它有几个细节值得展开。镜像名称由三部分组成仓库地址/仓库名:标签。比如mysql:8.0省略了仓库地址时默认从官方仓库拉取8.0是标签指定版本号。我刚接触时犯过一个错直接docker pull mysql不带标签结果拉下来一个最新版latest跟系统里已有的配置不兼容折腾了半天才反应过来版本不对。所以线上部署时永远不要省略标签必须明确版本。还有一些场景需要从特定仓库拉取比如公司内网自建的私有仓库命令格式是docker pull 192.168.1.100:5000/team-project:v1.0。这里192.168.1.100:5000是仓库地址team-project是项目名v1.0是标签。如果私有仓库没有配置HTTPS证书可能需要在daemon.json里声明insecure-registries否则会报证书校验错误。另外提醒一下拉取时还可以指定平台。比如M系列芯片的Mac是ARM架构想跑x86镜像可以docker pull --platform linux/amd64 nginx:1.25这个参数在跨架构部署时是救命稻草不指定的话Docker会按宿主机的架构拉取。2.2 本地镜像的查看、打标签和删除看本地有哪些镜像用docker images docker image ls这两个命令等价输出仓库、标签、镜像ID、创建时间和大小。镜像ID是唯一标识后面删除、构建时都用得到。给镜像打标签用docker tag比如把本地某个镜像重命名方便推送到自己的仓库docker tag redis:7.0 myrepo/redis:v7-old注意docker tag不是重命名操作等于给同一个镜像复制了一个新“别名”两个标签指向同一个镜像ID删除其中一个不影响另一个。删除镜像是很多人踩坑的重灾区。先试docker rmi mysql:8.0如果这个镜像已经被某个容器使用了会报Error response from daemon: conflict: unable to delete repository reference ...意思是镜像被占用得先把依赖它的容器删掉或者用docker rmi -f强制删除但我不建议容易误杀。正确排查方式docker ps -a | grep mysql找到容器的CONTAINER ID先删容器再删镜像。记住这个逻辑容器优先于镜像删除是自下而上的。2.3 镜像的导出导入离线部署的救命工具内网环境拉不了镜像这是工作中很常见的情况。解决办法是在一台能联网的机器上先拉好镜像然后导出成文件拷到内网再导入。# 导出 docker save -o nginx.tar nginx:latest # 导入 docker load -i nginx.tar还有一个容易混淆的命令是docker export和docker import。它俩的作用是将容器导出为镜像和docker save、docker load处理镜像的机制不同。简单说docker save/load操作对象是镜像保留镜像的分层结构适合完整迁移。docker export/import操作对象是容器把容器运行时文件系统打成扁平归档会丢失分层历史和元数据适合只想要文件内容、不关心历史的场景。日常离线交付用save/load就够了别去折腾 export。3. 容器生命周期从启动到删除的完整操作3.1 最常用的docker run参数详解docker run是Docker最高频的命令没有之一。它负责把镜像变成容器并启动。先看一个典型的启动命令docker run -d --name my-web -p 8080:80 -v /home/user/html:/usr/share/nginx/html nginx:latest逐个拆解-d后台运行相当于在Linux里用nohup把进程挂后台。不加的话终端会直接黏在容器的日志输出上CtrlC退出容器就停了。--name my-web给容器起名字。起名字非常重要不然你只能通过随机生成的ID去操作它非常痛苦。-p 8080:80端口映射。宿主机8080端口转发到容器的80端口。容器默认是独立网络空间不映射端口的话外部根本访问不到容器里的服务。-v /home/user/html:/usr/share/nginx/html数据挂载后面单独说。还有一些高频率出现的参数docker run -it --name ubuntu-test ubuntu:22.04 bash-it是-i -t的组合保持标准输入打开并分配一个伪终端这样你才能进到容器里敲命令。容器内部进程是bash时-it才有效如果默认命令是启动web服务加-it意义不大反而会让日志刷屏。生产环境里我几乎每个容器都会加一个参数docker run -d --name my-app --restartalways my-app-image:1.0--restartalways的意思是容器退出或宿主机重启后自动拉起配合systemctl做开机自启的逻辑这也是容器跑在宿主机上的价值所在。不加这个参数宿主机一重启所有容器全部变Exited状态。3.2 查看、停止、删除和进入容器容器启动后查看状态用docker ps只显示运行中的容器。加-a显示所有容器包括已停止的docker ps -a这个命令在新手期的高频用途是容器启动失败了但明明执行了docker run怎么看都看不到容器——因为容器已经退出了不加-a就看不到。停止和启动docker stop my-web docker start my-web docker restart my-web停止容器发送的是SIGTERM信号让进程优雅退出而不是像docker kill那样直接发SIGKILL强杀。正常情况下用stop实在无响应再考虑kill。删除容器docker rm my-web运行中的容器不能直接删要么先stop要么用-f强制删除docker rm -f my-webrm -f会直接停掉容器并删除数据也会没。所以删除容器前必须想清楚特别是没有挂载数据卷的容器删了等于数据永久消失。进入运行中的容器操作用docker exec -it my-web bashexec是“在已有的容器里再执行一个进程”bash表示执行shell。有些精简镜像没有bash比如基于alpine的可以试sh再不行就用docker exec -it my-web /bin/sh。还有一个查看资源占用的命令我平时排查问题很依赖它docker stats输出容器的CPU、内存、网络、磁盘IO实时占用。之前有个朋友问我一台N100小主机能不能跑20个Docker容器我当时就说能不能跑不看容器数量看总资源占用。很多容器空载CPU几乎为0内存占用只有几十MB跑20个很正常但如果你跑的是GitLab这种内存大户四五个就能压垮整台机器。3.3 端口映射与网络模式端口映射是新手最容易出错的地方。-p 8080:80的格式严格来说是宿主机地址:宿主机端口:容器端口。如果你只写-p 8080:80Docker默认绑定0.0.0.0也就是所有网卡都可访问。如果想只允许本机访问写成docker run -d -p 127.0.0.1:8080:80 nginx:latest如果同一个宿主机端口被两个容器抢用会报Error response from daemon: driver failed programming external connectivity ... Bind for 0.0.0.0:8080 failed: port is already allocated解决方法是换端口或者先确认哪个容器占用了它。我曾在一个项目里排查了半天nginx服务起不来最后发现是另一个容器提前把80端口占了跟Docker本身一点关系没有。容器网络模式这块常用的是三种bridge默认容器通过一个虚拟网桥互相通信外部访问需要端口映射。适合大多数独立容器。host容器直接复用宿主机网络栈没有独立IP端口直接挂在宿主机上性能和延迟最好但隔离性差。none无网络。“docker网络不通”的排查思路我总结一句先docker exec -it 容器名 ping 宿主机IP再ping 另一个容器再ping 外网分段确定断在哪一层。涉及多个容器互相通信时最好的做法是创建一个自定义桥接网络docker network create my-net docker run -d --name redis --network my-net redis:7.0 docker run -d --name app --network my-net my-app:1.0同一个自定义网络里的容器可以直接用容器名作为主机名互相访问比如应用里连Redis地址直接写redis:6379而不用去查宿主机IP加映射端口。这个方案比默认bridge网络配合--link靠谱得多。4. 数据持久化让容器可以被“反复折腾”4.1 为什么容器删了数据就没了我必须要强调一个很多人吃亏的问题容器默认是“无状态”的所有写在容器可写层里的数据都会随容器删除而消失。docker rm把容器删掉后你在容器里写的文件、数据库里的记录、配置的账号全部烟消云散。如果没做数据持久化升级容器版本时想“先删旧容器再起新容器”等同于主动丢数据。正确思路是把需要持久化的数据全部放到宿主机上让容器只负责跑进程数据都存在容器外部。4.2 数据卷和挂载两种主要方式第一种是bind mount绑定挂载把宿主机的某个目录挂到容器目录里。比如docker run -d --name my-web -v /home/user/html:/usr/share/nginx/html:ro nginx宿主机/home/user/html目录里的文件会实时映射到容器 nginx 网页目录宿主机改文件容器立刻看到。:ro表示只读防止容器内进程改坏宿主机文件。第二种是volume数据卷由Docker管理存储位置在宿主机/var/lib/docker/volumes/xxx/_data用法docker run -d --name mysql8 -v mysql-data:/var/lib/mysql mysql:8.0mysql-data是卷名Docker会自动创建。好处是不用关心宿主机路径迁移时用docker volume相关命令统一管理而且多个容器可以共享同一个数据卷。生产环境我优先推荐用数据卷因为它不强制依赖宿主机目录结构命令也更简洁。如果你想备份MySQL的数据可以直接把卷目录打包tar -czf mysql-backup.tar.gz /var/lib/docker/volumes/mysql-data/_data恢复就是解压回去再起容器就这么简单。4.3 实战部署MySQL 8.0并持久化数据放一个完整的MySQL部署命令这是热搜词里出现频率最高的场景之一docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDmy-root-password \ -e MYSQL_DATABASEappdb \ -e MYSQL_USERapp \ -e MYSQL_PASSWORDapp-secret \ -v mysql-data:/var/lib/mysql \ mysql:8.0-e参数是环境变量MySQL官方镜像通过几个标准变量完成初始化MYSQL_ROOT_PASSWORD是root密码MYSQL_DATABASE会自动创建数据库MYSQL_USER和MYSQL_PASSWORD创建业务账号。第一次启动时MySQL会做初始化这时候容器日志会有大量输出用docker logs观察进度。启动完成后连接验证docker exec -it mysql8 mysql -u app -p appdb进入容器执行mysql客户端直接走真实生产路径而不是额外装一个客户端连远程端口。这个技巧可以减少一次网络连接排查的步骤。MySQL 8.0有个默认认证插件是caching_sha2_password部分老客户端连接会报错。你需要启动时加参数--default-authentication-pluginmysql_native_password或者通过配置文件处理。这个算是MySQL 8.0容器化部署的经典坑单独记一下。部署Redis主从道理也是相通的。主从两个容器分别挂载各自的持久化目录--name分开命名配置从库时在启动命令里加docker run -d --name redis-slave --network my-net -p 6380:6379 redis:7.0 redis-server --slaveof redis-master 6379这里redis-master就是主容器的名字跑在同一个自定义网络里Docker自动完成DNS解析。这种“容器名即域名”的用法大幅简化了多节点部署的配置量。5. 日志与故障排查解决日常翻车现场5.1 日志查看与实时跟踪容器日志是排查问题的第一入口。核心命令docker logs my-web docker logs -f my-web docker logs --tail 100 my-web-f表示follow实时跟踪输出平时追日志我常驻这个参数--tail 100表示只看最后100行避免启动日志太长刷屏。诊断启动失败场景我一般先docker logs --tail 50 容器名配合grep过滤关键字docker logs my-web 21 | grep -i error这个习惯跟你在Linux里用tail -f /var/log/nginx/error.log | grep error的体验是几乎一致的如果你熟悉Linux常用命令Docker日志这层几乎没有学习成本。有个细节容器主进程如果直接退出logs里通常已经给了原因。而如果容器一直处于Restarting状态说明进程在启动后反复崩溃此时docker logs会看到启动阶段报错这个输出就是你唯一能拿到的线索。5.2 高频报错排查速查表我整理一张自己平时排查用的速查表按出现频率排的报错关键字原因定位解决动作Cannot connect to the Docker daemonDocker守护进程没启动sudo systemctl start dockerpermission denied ... docker.sock用户不在docker组sudo usermod -aG docker $USER后重登port is already allocated宿主机端口冲突docker ps找占用进程换端口conflict: unable to delete repository reference镜像正被容器引用docker ps -a找依赖容器删容器后再删镜像virtualisation support wasnt detectedBIOS/Windows虚拟化未开启开启VT-x Hyper-V/WSL2Get https://registry-1.docker.io/v2/: net/http: request canceled外网仓库连接超时配置镜像加速器后重启Dockerno space left on device磁盘满 / Docker数据目录大docker system prune -a 扩容5.3 网络排查的固定套路“docker网络不通”是运维群里隔三差五就能看到的问题。我主张固定一套排查路径不慌不乱。第一步看内容docker exec -it 容器名 ping 宿主机IP docker exec -it 容器名 ping www.baidu.com如果内网能通外网不通大概率是宿主机防火墙或DNS问题如果从容器里连另一个容器不通观察两个容器是不是在同一个自定义网络里。第二步看网络信息docker network inspect bridge这个命令会列出该网络下所有容器的IP、网关、连接信息。遇到默认bridge网络下容器各自分配IP、互相通信还要记IP的窘境干脆新建自定义网络用容器名访问一劳永逸。第三步看桥接网络本身。极少数情况下默认docker0桥会被误删或者地址段冲突常见表现是任何人都能访问宿主机但宿主机和容器之间互相不通。这时候重建网络能解决sudo systemctl stop docker sudo ip link delete docker0 sudo systemctl start docker这个操作会重建docker0网桥但也会打断所有正在跑的容器网络所以只能在维护窗口执行。平时真没必要动它。6. 多容器编排从docker run到compose6.1 compose解决了什么问题当一个项目的容器数量从1个变成3个、5个之后docker run的弊端就暴露了参数太长记不住、容器之间的启动顺序靠手动保证、配置散落在shell历史记录里没法管理。Docker Compose的解法是写一个docker-compose.yml文件把服务、端口、卷、环境变量、依赖关系全部声明在里面之后只需要docker compose up -d就能把整套环境拉起来。这个文件本质上是一份“应用部署说明书”既可以在开发环境用也可以在生产环境跑还可以提交到Git仓库让别人一键启动。对比docker run一条条敲可维护性强太多了。6.2 compose常用命令清单Compose的常用命令我每天敲来敲去其实就几个docker compose up -d docker compose ps docker compose logs -f --tail 100 docker compose restart 服务名 docker compose down docker compose down -vup -d启动全部服务ps查看状态logs -f汇总所有容器的日志输出排查问题时比一个个docker logs高效得多。down停止并移除所有容器但默认保留数据卷down -v则会连数据卷一起删除等同于把持久化数据也删了。这里必须强调一下-v的危害。我自认为老手了也有过一次大意部署测试环境时直接docker compose down -v把整个数据库卷连根拔起所有测试数据清零只能从备份恢复。从那以后我锁死了一个习惯down -v是危险操作执行前必须确认这是不需要数据的测试环境。6.3 微服务项目部署示例看一个典型的三服务架构Nginx 后端API MySQL Rediscompose文件长这样version: 3.8 services: nginx: image: nginx:1.25 ports: - 80:80 volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - api api: build: ./app environment: - DB_HOSTmysql - REDIS_HOSTredis depends_on: - mysql - redis restart: always mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: appdb volumes: - mysql-data:/var/lib/mysql redis: image: redis:7.0 restart: always volumes: mysql-data:这里api服务里没有写ports说明后端API不直接对外暴露端口流量全部由Nginx反代进入这是微服务部署里常见的做法。服务之间通过服务名互相访问比如DB_HOSTmysqlCompose自动处理DNS解析。我不太建议在compose文件里写version字段docker compose新版本基本不需要它写了反而可能触发弃用警告。用docker compose up -d启动后用docker compose ps观察每个服务的状态重点看depends_on的服务是否都健康。Compose文件里还可以声明自定义网络默认每个compose项目会自动创建一个网络容器都在里面服务名直接可通。所以别在compose里再手工建网络默认行为已经够用。最后分享几个我自己的操作习惯。第一高频命令我会在shell里配好别名比如alias dcudocker compose up -dalias dlgdocker compose logs -f --tail 200省时省力。第二磁盘占用紧张时先跑docker system df看看再决定要不要docker system prune -a这个命令会删除所有未被容器使用的镜像和构建缓存操作前务必确认没有需要的镜像。第三容器里改了文件想保留现场可以用docker commit 容器名 新镜像名快速固化成镜像方便备份或分发虽然不建议当正式的版本管理手段但比自己重新构建省事得多。Docker这套东西说到底是“命令 概念”的组合拳。概念通了命令自然顺手概念没通背再多也没用。把上面这些命令反复用几轮部署基本不会再有阻隔感。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。