
1. 先搞明白Docker命令的核心逻辑很多朋友一上来就背命令docker run、docker ps背得滚瓜烂熟遇到实际问题照样抓瞎。原因很简单——你没有理解Docker命令的设计思路。Docker这套命令体系本质上是围绕五个核心对象展开的镜像、容器、数据卷、网络、编排。你日常所有操作无非就是对这些对象进行增删改查。一旦理解了这套逻辑你会发现命令根本不用背顺着思路推理就能推出来。我拿生活里的例子类比一下。镜像就是一个安装包它只读、不可变、里面打包好了运行环境容器就是你双击安装包之后跑起来的那个应用程序实例一个镜像可以同时跑出多个互不干扰的容器。容器可以被启动、停止、删除而你删掉容器并不会影响镜像本身——就像卸载软件不会删掉安装包一样。还有一个关键概念叫数据卷。容器默认是用完即弃的你删掉容器容器里产生的数据也跟着没了。数据卷就是把你宿主机上的一个目录映射进容器里让数据持久化存在宿主机上。这个设计非常巧妙容器保持轻量和可重建性数据保持独立和可迁移性。网络这块更简单。Docker默认会创建几个网络比如bridge桥接网络就是最常见的模式。你启动容器时可以指定-p 8080:80这种参数意思就是把宿主机的8080端口转发到容器的80端口上外面访问宿主机8080端口就能到达容器内的服务。最后是编排工具。当你的应用需要好几个容器配合比如前端、后端、数据库挨个docker run会非常痛苦。这时候用docker compose写一个YAML文件把所有容器的配置都声明好一条命令搞定整套环境的启动、停止、重建。明白了这个框架接下来我按照对象维度逐个拆解命令每一组命令我都会讲清楚适用场景、常用参数和容易踩的坑。2. 镜像操作命令拉取、查看与删除2.1 镜像拉取与查看先看最常用的镜像操作基本围绕pull、images、rmi三个命令展开。# 从仓库拉取镜像 docker pull nginx docker pull nginx:1.25 # 列出本地已有的镜像 docker images # 删除镜像 docker rmi nginxdocker pull如果不加标签默认拉取latest最新版。我强烈建议实际项目里给镜像打上明确的版本号比如nginx:1.25而不是nginx:latest。原因很简单latest指向的镜像是会变的可能你昨天拉的和今天拉的不是同一个版本。哪天服务器上莫名其妙出现一个诡异问题排查半天发现是镜像版本悄悄变了这种教训我经历过不止一次。看过我上篇文章的朋友可能还有印象我把一台生产服务器上MySQL的镜像从mysql:latest更新后配置文件的默认行为发生了变化导致整个应用起不来。所以这里再强调一遍能用具体标签就别用latest。docker images输出里有个SIZE列如果你发现某个镜像体积异常大可以用docker history 镜像名看看每一层做了什么操作定位是哪一层撑大了体积。2.2 镜像标签与导入导出有时候你需要给镜像打标签或者在没有网络的环境下传输镜像这时候需要用到tag、save、load。# 给镜像重新打标签 docker tag nginx:1.25 myregistry.example.com/nginx:1.25 # 把镜像保存成tar文件 docker save -o nginx.tar nginx:1.25 # 从tar文件加载镜像 docker load -i nginx.tardocker save和docker load真的是救命工具。有一次我在机房部署服务内网环境完全拉不了外部镜像我就是在自己电脑上把需要的镜像save成tar包然后用U盘拷进去再load进内网服务器。整个过程干净利落。命令输出的信息也要学会看。比如docker pull的时候你会看到类似Pull complete、Digest: sha256:xxx这样的输出。那个Digest是镜像内容的哈希值相当于镜像的身份证号。你要是怀疑镜像被篡改过对比一下pull出来的Digest和官方Markdown文档里公布的Digest就知道真伪了。2.3 镜像加速配置与国内拉取技巧国内环境拉取Docker Hub镜像慢甚至超时是一个绕不开的痛点。这里说的不是绕过限制而是配置镜像加速源。原理很简单镜像是通过HTTPS从Registry拉取的你配置一个国内可访问的加速地址Docker就会优先从那儿下载。各大云厂商都提供过加速服务但现在很多已经调整策略了这个你得根据自己的实际情况去搜索当前可用的加速源。配置位置在Docker的守护进程配置文件里。Linux上通常是/etc/docker/daemon.json{ registry-mirrors: [https://你的加速地址] }配置完重启Docker服务生效。macOS和Windows的Docker Desktop则在设置界面里直接填加速地址就行。需要提醒的是优先从可信渠道获取加速源地址不要随便用网上流传的来路不明的地址防人之心不可无。2.4 镜像清理与体积诊断镜像占空间的坑我专门拿出来说一说。用docker images看到的是每个镜像单独的体积但Docker镜像是分层存储的多个镜像可能共享底层的只读层所以实际占用的磁盘空间可能比docker images显示的总数要少。要看真实占用用这个命令docker system df它会显示镜像、容器、数据卷、构建缓存各自的占用空间。你会发现构建缓存占的空间往往超乎想象尤其是频繁构建镜像的时候。清理镜像用docker rmi但如果镜像被某个容器引用了直接删会报错。这时候要么先删掉容器要么用docker rmi -f强制删除。我一般不用-f因为强制删除可能导致一些依赖关系悬空还是先把引用它的容器处理干净更稳妥。删除所有不再使用的镜像可以用docker image prune -a这个命令会把所有没有被容器引用的镜像全删了。删之前确认一下自己是不是真的要清理这么多东西。3. 容器生命周期管理命令从run到rm3.1 docker run启动容器的完整参数解析docker run是用的最多的命令没有之一。它的参数组合非常多我会按实际使用频率逐个拆解。docker run -d \ --name my-nginx \ -p 8080:80 \ -e ENV_NAMEproduction \ -v /host/data:/container/data \ --restart unless-stopped \ nginx:1.25每个参数的含义-d后台运行容器不占用当前终端。不加这个参数容器会以前台模式跑日志直接打到终端上CtrlC之后容器就停了。日常调试时可以不加-d这样能直接看到容器的输出排错方便。--name给容器起名字。没名字的话Docker会随机分配一个比如angry_heisenberg后续管理起来非常痛苦。-p端口映射格式是宿主机端口:容器端口。一个容器可以加多个-p参数。这里有个高频问题如果启动时报port is already allocated说明这个宿主机的端口已经被别的程序占了用netstat -tunlp | grep 8080可以查出来是谁占用的。-e传入环境变量。很多镜像的行为是靠环境变量控制的比如MySQL的MYSQL_ROOT_PASSWORD、Redis的REDIS_PASSWORD。-v数据卷挂载把宿主机的目录或文件挂载到容器里。--restart重启策略常用的是unless-stopped意思是只要不是手动stop的容器挂了都会自动重启。服务器重启之后这个容器也会自动拉起生产环境强烈建议加上。我用一个实际案例来说明。假设我要部署一个Nginx同时要改它的配置docker run -d \ --name web-nginx \ -p 80:80 \ -v /home/user/myweb:/usr/share/nginx/html:ro \ -v /home/user/nginx.conf:/etc/nginx/nginx.conf:ro \ --restart unless-stopped \ nginx:1.25第一个-v把网站目录挂进去第二个-v把自定义的Nginx配置文件挂进去。这里:ro表示只读挂载容器里改不了宿主机的文件这是一个安全习惯。这样部署之后我改了宿主机上的网页代码刷新页面就生效了根本不用进容器里操作文件。3.2 容器查看、启停与删除容器已经跑起来了后续的管理命令围绕这几个# 查看正在运行的容器 docker ps # 查看所有容器包括已停止的 docker ps -a # 停止容器 docker stop my-nginx # 启动已停止的容器 docker start my-nginx # 重启容器 docker restart my-nginx # 删除容器必须先停止 docker rm my-nginx # 强制删除正在运行的容器 docker rm -f my-nginxdocker ps的输出有几个关键信息要会看STATUS列显示容器的状态Up 2 hours表示正常Exited (0)表示正常退出Exited (1)之类的非零码就是异常退出PORTS列显示端口映射情况0.0.0.0:8080-80/tcp表示宿主机的8080端口映射到了容器的80端口。我这儿有个统计我参与过的项目中大概有70%的容器故障第一眼是从docker ps -a的STATUS列发现异常迹象的所以docker ps -a真的是排查第一利器尤其要看那些状态异常的容器。3.3 进入容器内部的两个方法容器跑起来之后有时候你需要在容器里执行命令、查看文件、调试问题。进入容器有两种方式网上很多人混用我讲清楚区别# 方式一在容器内执行一条命令 docker exec -it my-nginx bash # 方式二查看容器主进程的输出日志 docker logs my-nginx注意了docker exec是在运行中的容器里执行命令-it这两个参数要连在一起用-i是交互模式-t是分配一个终端。不加-it你执行bash命令是进不了交互shell的很多人第一次在这里卡住。执行docker exec -it my-nginx bash之后你会进入容器的shell看到类似root容器ID:/#这样的提示符。这时候你就在容器内部了可以执行ls、cat、ps等命令不过容器的文件系统通常是精简过的很多命令可能没有比如没有vim、没有ping。这也提醒你有事没事别老进容器里改东西容器是尽量保持不可变才最安全。那怎么查看容器日志呢用docker logs。这个命令后面再详解这里先记住一行docker logs --tail 100 -f my-nginx看最后100行日志并且持续跟踪输出。3.4 容器与宿主机之间的文件拷贝有时你需要把宿主机上的文件拷进容器或者把容器里的文件拿回来。用docker cp# 宿主机文件拷入容器 docker cp ./my.conf my-nginx:/etc/nginx/conf.d/ # 容器文件拷到宿主机 docker cp my-nginx:/etc/nginx/nginx.conf ./nginx.conf.bak说句实话docker cp只是临时救急用的不是常规操作手段。真正规范的做法是宿主机上用-v挂载目录配置文件放宿主机上直接改宿主机文件、重启容器或reload即可。这样容器可以随时重建而不丢配置。4. 容器日志、监控与排障实战4.1 docker logs的正确使用方式日志排障是Docker使用中最常见的场景绕不开也躲不掉。先看基础用法# 查看全部日志 docker logs my-nginx # 查看最近100条日志 docker logs --tail 100 my-nginx # 持续跟踪日志输出类似tail -f docker logs -f my-nginx # 查看带时间戳的日志 docker logs -t my-nginx # 只看最近5分钟的日志 docker logs --since 5m my-nginxdocker logs默认输出的是容器的标准输出stdout和标准错误stderr。这意味着你在容器里跑的应用程序只要它往控制台打日志都能被docker logs捕获到。反过来如果应用日志只写到文件里不往控制台打docker logs就看不到。这种情况就得进容器看日志文件或者更推荐的做法启动应用时把日志同时输出到控制台。--since参数配合时间排查很好用。比如线上的服务凌晨3点出问题了你可以docker logs --since 2025-01-15T03:00:00 my-app这样能精确拿到那个时间点之后的日志不用在大堆日志里捞。排查完别忘了加--until截断范围docker logs --since 2025-01-15T03:00:00 --until 2025-01-15T03:30:00 my-app这比直接打开日志文件CtrlF高效得多。还有一个实际经验容器运行时间长了日志文件会变得非常大。Docker默认的日志驱动是json-file日志文件默认存放在/var/lib/docker/containers/容器ID/目录下。如果不限制日志大小硬盘会被撑爆。建议在daemon.json里限制日志大小和数量{ log-driver: json-file, log-opts: { max-size: 10m, max-file: 3 } }这样每个容器的日志上限就是30MB轮转3个文件写满了自动清理老的。我接手过的一个项目因为没配这个三个月后硬盘爆满整个服务器都卡死了这个配置建议每个使用Docker的人都加上。4.2 查看容器进程与资源占用排查性能问题时会用到这两个命令# 查看容器里正在运行的进程 docker top my-nginx # 查看所有容器的CPU、内存、网络、磁盘IO docker statsdocker top相当于在宿主机上执行ps但展示的是容器内的进程。docker stats非常直观能看到每个容器的实时资源占用。如果某个容器CPU飙到100%大概率是应用出问题了进去看看是不是死循环或者GC线程在疯狂工作。注意两个点docker stats显示的CPU%是相对宿主机所有CPU核数的百分比比如你的服务器是4核一个容器显示CPU%是200%说明它占用了2个核心的算力。如果是排查历史资源占用情况docker stats不够用得靠容器监控平台如Prometheus cAdvisor采集历史数据。Docker命令行本身不提供历史回溯。4.3 容器状态异常排查思路我整理一下自己平时排查容器异常的一套步骤先看docker ps -a确认容器状态是Exited还是Restarting。docker logs --tail 50 容器名看退出前最后打的日志。docker inspect 容器名看容器配置和状态信息后面会讲。如果日志里没线索把容器删掉用前台模式重新跑一次比如去掉-d直接让日志打到终端上。如果依然查不出来检查宿主机资源free -h看内存df -h看磁盘。第三步的docker inspect是排查利器。它的输出是JSON格式信息量很大我平时主要用它查这几个字段# 查看容器重启次数 docker inspect -f {{.RestartCount}} my-nginx # 查看容器主进程PID docker inspect -f {{.State.Pid}} my-nginx # 查看容器IP地址 docker inspect -f {{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}} my-nginx # 查看容器的挂载情况 docker inspect -f {{json .Mounts}} my-nginx-f是Go模板语法用来提取JSON里的某个字段。这个技巧特别好用在脚本里做自动化判断时非常管用。5. 数据卷与网络配置容器内外沟通的桥梁5.1 数据卷的创建、挂载与备份前面提到数据卷是持久化数据的关键详细展开讲讲。Docker里数据有几种存法匿名卷启动容器时不指定宿主机目录只写容器内路径比如-v /var/lib/mysql。Docker会随机创建一个目录来对应这个挂载点。命名卷用docker volume create明确创建的卷比如-v mysql-data:/var/lib/mysql。绑定挂载直接指定宿主机路径比如-v /home/user/data:/var/lib/mysql。我强烈建议在正式环境使用命名卷或绑定挂载尽量别用匿名卷。匿名卷的目录是随机生成的没人知道它在哪儿容器删了之后数据找起来非常麻烦。命名卷的好处是可以用docker volume命令管理# 创建数据卷 docker volume create mysql-data # 查看所有数据卷 docker volume ls # 查看数据卷详情 docker volume inspect mysql-data # 删除数据卷 docker volume rm mysql-data举个例子我用MySQL容器数据目录一定要用命名卷docker run -d \ --name mysql8 \ -e MYSQL_ROOT_PASSWORDmysecret \ -v mysql-data:/var/lib/mysql \ -p 3306:3306 \ mysql:8.0这样无论容器怎么删怎么重建数据都在mysql-data这个卷里新的容器挂载同一个卷数据就回来了。数据卷的迁移备份# 备份命名卷数据 docker run --rm -v mysql-data:/source -v /backup:/target ubuntu tar czf /target/mysql-backup.tar.gz -C /source . # 恢复命名卷数据 docker run --rm -v mysql-data:/target -v /backup:/source ubuntu tar xzf /source/mysql-backup.tar.gz -C /target这两条命令看起来有点绕其实思路是借用ubuntu镜像临时起一个容器同时挂载数据卷和备份目然后在容器里执行tar打包或解包。--rm表示容器用完了就自动删除非常干净。5.2 容器网络的常用操作Docker网络这块日常会用到的场景大概是这几个端口映射、自定义网络、容器间通信。先看端口映射排查。我们经常遇到明明-p 8080:80了为什么外部还是访问不了这类问题。排查思路如下# 1. 确认端口映射是否生效 docker port my-nginx # 2. 确认容器内服务是否正常监听80端口 docker exec my-nginx curl http://127.0.0.1:80 # 3. 确认宿主机防火墙是否放行8080端口 firewall-cmd --list-portsdocker port会显示容器端口的映射关系比如80/tcp - 0.0.0.0:8080。如果显示为空说明容器启动时没加-p参数这时候要么重建容器要么直接改宿主机防火墙做转发但改防火墙不如重建容器干净。再说自定义网络。默认的bridge网络下容器之间靠IP地址通信但容器重建后IP会变这就很麻烦。解决办法是创建自定义网络在自定义网络里Docker内置DNS解析容器之间可以直接用容器名当域名互相访问# 创建自定义网络 docker network create my-net # 启动两个容器并加入同一网络 docker run -d --name app --network my-net myapp:latest docker run -d --name mysql8 --network my-net -e MYSQL_ROOT_PASSWORDsecret mysql:8.0 # 在app容器里通过容器名访问mysql docker exec app ping mysql8这样app容器里配置数据库地址时直接写mysql8:3306就行了不管MySQL容器怎么重建只要容器名不变、网络不变这个地址就永远有效。这个方法在微服务架构里几乎是标配。还有些不常用的网络命令了解即可# 查看所有网络 docker network ls # 查看网络详情 docker network inspect my-net # 把已启动的容器加入网络 docker network connect my-net my-nginx # 把容器从网络断开 docker network disconnect my-net my-nginx5.3 跨主机容器通信的提示跨主机场景下容器通信就不能靠Docker默认网络了通常需要借助overlay网络配合Swarm或Kubernetes使用或者直接发布端口。这套东西内容很大只是提个醒如果你有多台服务器的容器需要互相访问别试图把两个Docker主机的容器放进同一个默认网络这不可行。要么用Swarm/K8s做集群要么就通过宿主机的端口映射来互相访问后者运维成本低很多。6. 容器编排入门用Docker Compose管理复杂应用6.1 为什么需要Compose当你需要部署的应用变复杂之后比如一个Web项目包含前端、后端、数据库、Redis、消息队列五个组件挨个docker run大概得敲几十条指令还要记住每个容器的参数、网络、依赖关系。人记这些东西是记不牢的忘掉一个参数就环境起不来。Docker Compose就是为了解决这个问题。它用一份YAML文件把所有容器的配置声明好一条docker compose up -d就能把整套环境拉起来。而且这份YAML文件是可以提交到Git仓库里做版本管理的环境配置变成代码团队协作太方便了。6.2 compose文件怎么写以最常见的MySQL Redis组合为例。很多应用依赖MySQL和Redis通常的做法就是在compose文件里把这两个服务定义好。version: 3.8 services: mysql8: image: mysql:8.0 container_name: my-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: myapp MYSQL_USER: myapp MYSQL_PASSWORD: myapp123 ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql networks: - app-net redis: image: redis:7.0 container_name: my-redis restart: unless-stopped ports: - 6379:6379 volumes: - redis-data:/data command: redis-server --appendonly yes networks: - app-net volumes: mysql-data: redis-data: networks: app-net: driver: bridge这份文件做的事情和前面一堆docker run命令是一样的。services定义了两个容器volumes声明了两个命名卷networks创建了一个自定义网络两个容器在这个网络里可以互相用容器名访问。command字段可以覆盖镜像默认的启动命令。上面Redis例子里的--appendonly yes是开启AOF持久化保证Redis重启数据不丢。6.3 compose常用命令写好了YAML文件后续操作非常顺畅# 启动全部服务-d 后台运行 docker compose up -d # 查看服务状态 docker compose ps # 查看所有服务的日志 docker compose logs -f # 查看单个服务的日志 docker compose logs -f mysql8 # 停止全部服务容器不删除 docker compose stop # 停止并删除容器、网络 docker compose down # 停止并删除容器、网络、数据卷 docker compose down -v关键提醒docker compose down不会删除数据卷所以数据是安全的。但docker compose down -v会连数据卷一起删用之前必须确认里面的数据确实不要了。我见过有人跑了这条命令MySQL数据全没了那个表情我一辈子忘不了。修改了compose文件之后重新执行docker compose up -dCompose会智能对比配置差异只重建有变更的容器没有变更的容器不会动。新版本Docker已经内置了docker compose子命令带空格旧版本的docker-compose带横线需要单独安装。两者语法基本一致但优先用新版内置的。6.4 实际项目中的Compose使用建议我平时部署微服务项目compose文件会按环境拆分成多份docker-compose.yml公共配置、docker-compose.dev.yml开发环境覆盖、docker-compose.prod.yml生产环境覆盖。启动时指定文件docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d这样公共部分只写一遍不同环境的差异用覆盖文件解决比维护三份完整文件省心很多。还有一点compose文件里的container_name尽量写成有辨识度的名字。docker ps的时候如果看到一堆自动生成的名字你根本分不清哪个是哪个。但注意container_name在同一个宿主机上全局唯一不能有两个容器重名。7. 构建镜像与清理打造极简Docker镜像7.1 docker build与Dockerfile基础虽然标题是常用docker命令但docker build必须提因为它是把应用变成镜像的核心门槛。我用一个最简单的Node.js应用举例先写一个DockerfileFROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . EXPOSE 3000 CMD [node, app.js]然后在项目根目录执行docker build -t myapp:1.0.0 .-t指定镜像名和标签最后的.是构建上下文路径也就是Dockerfile所在的目录。构建过程会逐行执行Dockerfile里的指令每一条指令生成一个镜像层。构建过程中有个参数很常用docker build -t myapp:1.0.0 --no-cache .--no-cache会忽略之前构建的缓存从头构建。有时候改了package.json但镜像没生效大概率是缓存问题用这个参数强制重建就好了。7.2 镜像瘦身的常用技巧镜像瘦身这件事直接影响部署速度和占用的存储空间。我分享几个实际收益很大的策略基础镜像尽量选alpine版本。同样一个Nginxnginx:latest体积大概190MBnginx:alpine只有60多MB。三倍差距而且alpine的包管理器是apk换源也方便。用多阶段构建。前端或者编译型语言的项目构建环境和运行环境分开。第一阶段装全量依赖、编译代码第二阶段只拷贝编译产物和必要依赖。# 第一阶段构建 FROM golang:1.20 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED0 go build -o myapp # 第二阶段运行 FROM alpine:latest WORKDIR /root/ COPY --frombuilder /app/myapp . EXPOSE 8080 CMD [./myapp]这样最终镜像只包含编译好的二进制和最小的运行环境体积能缩小好几个数量级。清理apk/apt缓存。安装依赖之后顺手清掉包管理器缓存镜像能小几十MB。RUN apt-get update apt-get install -y --no-install-recommends curl \ rm -rf /var/lib/apt/lists/*用.dockerignore排除无关文件。项目里的node_modules、.git、tests目录如果没排除它们会被发送到构建上下文里不仅构建慢还可能被误打包进镜像。.dockerignore的写法和.gitignore基本一样。7.3 构建缓存的重要说明Docker构建是有缓存机制的如果Dockerfile的某条指令和之前完全一样并且涉及的上下文文件没变Docker就直接复用之前的缓存层构建速度会快很多。但缓存有时候会坑人。比如你执行了RUN git clone xxx如果远端仓库有更新Docker不会自动告诉你它认为这条指令没变就用缓存了。解决办法就是加--no-cache或者把依赖版本固定到具体commit上避免代码变了但镜像没变的灵异事件。8. 日常维护清理、监控与一键接入容器8.1 一键清理Docker环境时间长了宿主机上会堆积大量停止的容器、无用的镜像、悬空的数据卷、构建缓存。手动一个个删太痛苦Docker提供了批量清理的命令# 清理所有已停止的容器 docker container prune # 清理所有未被使用的镜像 docker image prune -a # 清理所有未被使用没被容器引用的数据卷 docker volume prune # 清理构建缓存 docker builder prune # 一键清理所有未使用的资源 docker system prune -a --volumesdocker system prune -a --volumes是核弹级别的清理命令它会删掉所有没被运行中容器使用的镜像、所有停止的容器、所有数据卷。执行之前一定要仔细确认尤其注意--volumes会把数据卷一起删掉。建议平时只跑不带--volumes的docker system prune -a数据卷单独审着删。如果空间还是不够还需要检查一下docker的根目录路径日志和容器存储都在/var/lib/docker下如果系统盘分区小、数据盘大可以考虑把docker根目录迁移到数据盘或者确认是否有过多大日志文件堆积。8.2 宿主机直接操作容器docker run --rm临时容器另一个很实用的技巧是用--rm参数跑一次性容器。比如你想在不污染环境的情况下测试某个工具# 临时起一个带curl的容器测试接口用完自动删除 docker run --rm curlimages/curl curl -I http://example.com # 临时用mysql客户端连接数据库 docker run --rm -it mysql:8.0 mysql -h172.17.0.2 -uroot -p这样不会在你宿主机上装任何多余的东西容器跑完命令就自动删了非常适合做一次性环境验证。8.3 Windows环境安装Docker Desktop的常见问题与排查最后专门说说Windows上安装和使用Docker Desktop的常见问题因为社区里问的人太多了。问题一Docker Desktop启动失败提示虚拟化未检测到virtualization support not detected这个提示翻译成人话就是你的电脑没有开启硬件虚拟化功能。排查路径如下打开任务管理器点击性能选项卡看右下角虚拟化是否显示已启用。如果显示已禁用就需要进BIOS开启Intel VT-x或AMD-V。确认Windows的Hyper-V和适用于Linux的Windows子系统WSL功能有没有开启。以管理员身份运行PowerShelldism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart开启之后重启电脑再装WSL2wsl --install装完把默认版本设置为2wsl --set-default-version 2问题二Docker Desktop提示Windows版本不兼容incompatible version of windowsDocker Desktop对Windows版本有要求一般是Windows 10 64位专业版/企业版/教育版或者Windows 11。如果你是Windows家庭版大概率会碰到问题因为Hyper-V这个特性在家庭版上默认不带。解决办法是装Docker Desktop的时候选择WSL 2后端现在默认就是并且把WSL2配置好。如果你用的系统版本确实过旧那就要么升级系统要么换用Docker Toolbox这类老方案——但我建议能升级就升级别折腾老方案了。问题三Docker Desktop启动后一直卡在Starting状态这种一般跟WSL2有关。进PowerShell执行wsl --shutdown强制关闭WSL再重新打开Docker Desktop。如果还不行检查BIOS里虚拟化是否被安全软件误关了。问题四Docker Desktop里的容器和宿主机怎么互通Windows上容器里的服务通过localhost访问宿主机的服务通常是不行的反过来宿主机访问容器映射端口一般可以直接用localhost。要在容器里访问宿主机的服务需要用host.docker.internal这个特殊域名Docker Desktop会自动把它解析到宿主机IP。9. 把这些命令串起来一个可落地的部署示例光列命令不给一个完整的实战串联总觉得少了最后一环。我拿一个真实的部署场景——Nginx MySQL 8.0 Redis用Compose整体拉起来——把前文讲过的知识点串一遍你可以照着这个模板改造自己的项目。假设项目目录结构myproject/ ├── docker-compose.yml ├── myweb/ │ └── index.html └── nginx/ └── default.confnginx/default.conf一个反向代理配置把请求转发到后端的容器服务server { listen 80; server_name _; location / { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }docker-compose.ymlversion: 3.8 services: mysql8: image: mysql:8.0 container_name: my-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: mydb ports: - 3306:3306 volumes: - mysql-data:/var/lib/mysql networks: - my-net healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 10s timeout: 5s retries: 5 redis: image: redis:7.0-alpine container_name: my-redis restart: unless-stopped command: redis-server --appendonly yes ports: - 6379:6379 volumes: - redis-data:/data networks: - my-net nginx: image: nginx:1.25-alpine container_name: my-nginx restart: unless-stopped ports: - 8080:80 volumes: - ./myweb:/usr/share/nginx/html:ro - ./nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: mysql8: condition: service_healthy redis: condition: service_started networks: - my-net volumes: mysql-data: redis-data: networks: my-net: driver: bridge这份compose文件里我加了一个healthcheck配置让MySQL容器启动后主动做健康检查Nginx等MySQL确认可连接之后才启动避免依赖服务还没就绪导致启动失败的经典问题。启动整套环境docker compose up -d查看状态docker compose ps打开浏览器访问http://localhost:8080能看到你自己的网页内容。访问http://localhost:8080时如果Nginx配置了代理也就能走到后端服务了。整个过程中你实际用到的命令docker compose up -d docker compose ps docker compose logs -f docker compose down就这么几条比手动跑三个docker run再去配置网络逻辑上清晰太多了。这也是Compose值得投入时间学习的原因——它能让你把精力从记命令参数中解放出来放到真正需要思考的服务如何编排上。我个人的体会是Docker命令的学习曲线其实很缓只要抓住镜像-容器-卷-网络-编排这五个关键字日常90%的操作都逃不出这个框架。剩下那10%的冷门命令等你真碰到那个场景的时候翻文档自然就学会了。不用一开始就试图背完所有命令把最常用的这几组练熟遇到问题时知道往哪个方向排查你就算是真正入门了。