
Docker这几年在开发和运维圈子里几乎成了默认技能。不管是本地想跑一套MySQL和Redis还是把微服务项目拆开部署或者是给模型推理准备一个干净环境大家第一反应基本都是“去拉个镜像docker run一把梭”。我第一次在Windows环境装Docker Desktop时被“virtualization support not detected”这个报错卡了整整一个下午排查完又遇到镜像拉不下来、容器间网络不通、挂载目录没有写权限这些经典问题每一步都在交学费。这篇文章是我学习Docker过程中陆续攒下来的完整笔记覆盖了安装、常用命令、Compose编排和故障排查几个主要内容适合刚接触容器技术、准备在本地或服务器上部署应用的同学参考。我会尽量把每个操作背后的原因说清楚而不是丢一堆命令让你硬抄。1. 先搞清楚Docker到底是什么以及为什么大家都在学1.1 镜像、容器、仓库三个核心概念一次讲清刚开始接触Docker的人最容易被镜像Image、容器Container、仓库Repository这三个词绕晕。我当时的理解方式是这样的镜像是一套打包好的、只读的模板里面装了操作系统底层库、应用代码和运行环境容器是镜像跑起来之后的实例相当于电脑上正在运行的一个进程仓库则是存放和分发镜像的地方最常用的就是Docker Hub。打个比方镜像像一张光盘里的安装程序容器是把它运行起来后打开的软件窗口仓库则是应用商店。你在商店里下载安装包拉镜像运行起来后软件就带着自己的配置和数据一起工作容器这套逻辑和传统软件安装非常像但底层的隔离方式完全不同。Docker镜像还有一个重要特性它由多层只读文件系统叠加而成。每次修改镜像内容比如运行RUN命令都会产生新的一层只有容器运行时才在最上面加一层可读写层。这个设计带来一个很大的好处——多个容器可以共享底层只读层大大节省磁盘空间启动速度也比亚虚拟机快很多。1.2 Docker解决的真实问题环境一致性、资源隔离、交付效率传统部署方式的痛点几乎所有开发都经历过代码在自己电脑上跑得好好的一到测试服务器就报“缺少依赖”“版本不对”。Docker把应用、运行时、依赖全部打包进镜像让“构建一次、到处运行”变成可能。我可以负责任地说Docker在环境一致性上的贡献是革命性的。资源隔离方面Docker容器比虚拟机轻得多。虚拟机需要模拟完整硬件每个虚拟机都要跑一个完整的操作系统启动动辄几十秒占用几个GB内存容器则直接共享宿主机内核只隔离进程、文件系统和网络启动以毫秒计一个普通服务器跑几十个容器很常见。下面这张表是我常用的对比维度对比项Docker容器传统虚拟机启动速度秒级甚至毫秒级数十秒到分钟级资源占用只占应用本身开销操作系统占大头隔离程度进程级隔离硬件级隔离镜像大小通常几十MB到几百MB几个GB起步适用场景快速交付、弹性伸缩需要强隔离的复杂系统交付效率上DevOps团队最依赖Docker的一点是“流水线标准化”。开发提交代码后CI/CD流水线编译、测试、构建镜像、推送仓库再到生产环境拉取镜像滚动部署全流程几乎一摸一样。这个能力不管是公司还是个人项目都值得早点用起来。2. Docker安装Windows、Ubuntu、CentOS都怎么搞2.1 Windows下Docker Desktop安装的正确姿势Windows上最主流的方案是安装Docker Desktop它自带图形界面还能直接管理镜像、容器和Compose任务。很多朋友遇到的第一个坑就是安装后启动报错“virtualization support not detected”或者“failed to start because virtualisation support wasn’t detected”。这个问题的本质是Docker Desktop依赖Windows的虚拟化能力WSL2或Hyper-V它检测不到就会拒绝启动。我的排查顺序是这样先进BIOS确认CPU虚拟化已经开启Intel的VT-x或AMD的SVM/V这两项不同叫法但作用一样然后到“控制面板→程序→启用或关闭Windows功能”里勾选“适用于Linux的Windows子系统”和“虚拟机平台”确定后重启。重启后在PowerShell里执行wsl --status确认WSL2是默认版本如果还在WSL1就运行wsl --set-default-version 2。最后重装或重新启动Docker Desktop绝大多数情况下都能解决。如果你想把Docker安装到D盘而不是占用C盘可以在Docker Desktop的Settings→Resources→Advanced里修改Disk image location或者在WSL2环境中通过wsl --export docker-desktop-data和wsl --import将数据迁移到D盘。Win11上安装后想用中文界面到Settings→General里改语言重启应用即可。2.2 Ubuntu与CentOS安装Docker Engine的操作记录Linux服务器上一般安装原生Docker Engine不装Desktop。以Ubuntu 22.04/24.04为例官方推荐用apt源安装。核心命令如下sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg lsb-release 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.io docker-compose-plugin sudo systemctl enable --now docker安装完成后用docker version验证看到Server就说明守护进程正常工作。如果你想在Ubuntu上快速跑一个Python环境一条docker run -it python:3.12 bash就能直接进入带Python的交互环境不需要在宿主机上安装任何Python。CentOS 7的操作类似但用yumsudo 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.io sudo systemctl start docker sudo systemctl enable dockerCentOS 7如果原本装过老版本Docker升级前先systemctl stop docker再用yum remove旧包防止版本冲突。还有一个容易忽略的地方CentOS 7默认防火墙是firewalld如果不放行端口容器端口映射得再好也会被宿主机防火墙拦下来。2.3 镜像下载慢配置镜像加速器的通用方案镜像下载慢是Docker入门者的第一大痛点。原因很简单Docker Hub的服务器在国外网络传输速度不稳定。最直接的办法不是换镜像源硬拉而是配置registry mirror镜像加速器。Linux下修改/etc/docker/daemon.json如果没有就新建{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }然后执行sudo systemctl daemon-reload sudo systemctl restart docker。如果你用的是阿里云等云厂商服务登录容器镜像服务控制台可以拿到个人专属的加速地址格式通常是https://xxx.mirror.aliyuncs.com把它放进上面的列表里拉取速度会有非常明显的提升。有一点要注意daemon.json的语法要求非常严格漏个逗号就会导致Docker服务启动失败。改完之后先执行docker info看Registry Mirrors那一段是否生效再去拉镜像。Windows上的Docker Desktop则是在Settings→Docker Engine里直接编辑同一份JSON界面友好很多。3. 镜像与容器的核心操作从拉取到清理3.1 拉取、运行、进入容器的常用命令镜像和容器的操作其实就围绕几个动词拉、跑、看、进、删、退。我把最常用的一套命令放在这里docker pull nginx:alpine # 拉取镜像 docker run -d -p 80:80 --name nginx-demo nginx:alpine # 运行容器 docker ps # 查看运行中的容器 docker ps -a # 查看所有容器包括已停止 docker exec -it nginx-demo sh # 进入正在运行的容器 docker logs -f nginx-demo # 实时查看容器日志 docker stop nginx-demo # 停止容器 docker start nginx-demo # 启动已停止的容器 docker rm nginx-demo # 删除容器 docker rmi nginx:alpine # 删除镜像 docker system prune # 清理所有停止的容器和悬空镜像实际使用中有一条值得养成习惯的纪律尽量不要在运行的容器内部做任何配置修改。容器是临时的任何修改都会在容器重建后丢失。软改环境变量、命令参数放运行命令里硬改自定义配置放到Dockerfile或挂载文件里这是容器应用的通用设计模式。3.2 用Dockerfile和IDE构建自定义镜像写Dockerfile是把应用容器化的核心动作。一个最基础的Go服务多阶段构建模板是这样的FROM golang:1.22-alpine AS builder WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED0 GOOSlinux go build -o app . FROM alpine:3.19 WORKDIR /root/ COPY --frombuilder /app/app . EXPOSE 8080 CMD [./app]多阶段构建的意义在于最终镜像只保留运行所需的二进制文件和基础系统层构建工具、源码、缓存全部丢弃镜像大小可以从几百MB缩到几十MB。记住FROM、WORKDIR、COPY、RUN、EXPOSE、CMD这几个指令的顺序和含义就能应对大部分场景。如果你用IntelliJ IDEA开发不需要手动敲docker build。IDEA自带Docker插件在Settings→Build, Execution, Deployment→Docker里配置好Docker Desktop连接后可以直接对Dockerfile右键运行。Java项目还可以用Jib插件一键打包./mvnw compile jib:build -Dimagemyregistry/myapp:1.0连Docker守护进程都不需要。PHP项目思路一样官方php镜像加自己需要的扩展就行核心就是RUN docker-php-ext-install那几条命令。3.3 数据卷与端口映射容易丢数据的几个坑容器很轻但也很“薄”。如果不挂载数据卷容器一旦删除里面产生的数据就会跟着消失。数据卷有两种常用方式一种是匿名卷-v /容器内路径一种是把宿主机目录映射进去-v /宿主机路径:/容器内路径。我强烈建议重要数据都用宿主机目录映射这样不仅安全还能方便你在宿主机上直接备份和查看日志。端口映射的格式是-p 宿主机端口:容器内端口。比如nginx镜像默认监听80你用-p 8080:80外部访问宿主机的8080就能命中容器内的80。常见错误有两个第一个是宿主机端口被占用docker run会直接报端口冲突第二个是容器内应用监听了127.0.0.1导致宿主机访问哨不通这个问题在MySQL和Redis这类服务里尤其容易出现。还有个Windows使用者很容易踩的坑挂载目录路径的写法。Docker Desktop虽然是Windows里的应用但它内部运行的其实是WSL2虚拟机挂载路径要写WSL路径而不是C:\比如-v /c/data:/app。用不习惯的人确实会迷糊但这是宿主路径转换层面的问题原理理解后就绕得开了。4. Docker Compose与多容器编排从单机到微服务4.1 为什么需要Compose以及它到底管了什么单容器部署用docker run就够了可一旦涉及多个容器比如典型的Web应用要有前端、后端、MySQL、Redis、Nginx如果你靠手敲十几个docker run命令去启动不但麻烦而且很难保证每次参数一致。这时候Docker Compose就派上用场了它用一个YAML文件定义整个应用的所有容器、网络、卷和依赖关系一条docker compose up -d全部搞定。Compose配置里最核心的几个字段是services定义各个容器、image/build指定镜像或构建上下文、ports端口映射、volumes数据卷、environment环境变量、depends_on容器启动顺序控制、networks容器间通信网络。模块化程度极高适合把整个项目的容器关系沉淀成代码进行版本管理。自行安装Docker Compose时现代Docker官方推荐直接安装docker-compose-pluginUbuntu那一段已经包含。你也可以用独立二进制文件安装但插件方式更省心命令就是docker compose中间有空格不是旧版的docker-compose写脚本时注意区分。4.2 实战MySQL 8.0 Redis主从一套带走以一个我反复推荐给新手的练手项目为例用Compose一次性拉起MySQL 8.0和Redis一主一从。这也是我最早觉得Docker“真香”的场景。version: 3.8 services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 TZ: Asia/Shanghai command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci volumes: - ./mysql-data:/var/lib/mysql - ./initdb:/docker-entrypoint-initdb.d networks: - app-net redis-master: image: redis:7.0-alpine container_name: redis-master restart: always ports: - 6379:6379 command: redis-server --appendonly yes volumes: - ./redis-master-data:/data networks: - app-net redis-node: image: redis:7.0-alpine container_name: redis-node restart: always depends_on: - redis-master ports: - 6380:6379 command: redis-server --replicaof redis-master 6379 volumes: - ./redis-node-data:/data networks: - app-net networks: app-net: driver: bridge启动后Redis从节点会通过自定义网络里的服务名 redis-master 找到主节点。与使用IP地址相比服务名方式的一个巨大优势是容器重建后IP变化也不影响配置。MySQL这边我特意加了utf8mb4字符集参数避免中文乱码宿主机上的initdb目录可以放init.sql容器首次启动时会自动执行里面的SQL初始化脚本。这套配置只要保证宿主机至少开放3306、6379和6380端口基本就是开箱即用。4.3 常见业务系统与微服务部署的参考思路Docker让我在处理各种业务软件时轻松了不少。GitLab、Metabase、Joplin、KodBox这类工具都有官方镜像部署思路高度统一拉镜像、挂载数据卷、设置环境变量、映射端口。以GitLab为例sudo docker run --detach \ --hostname gitlab.example.com \ --publish 443:443 --publish 80:80 --publish 22:22 \ --name gitlab \ --restart always \ --volume /srv/gitlab/config:/etc/gitlab \ --volume /srv/gitlab/logs:/var/log/gitlab \ --volume /srv/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:latestGitLab很吃内存建议至少4GB可用内存否则安装后反复502。这是很多人跑到一半才发现的问题。微服务项目的部署方式也类似不过更强调网络设计所有服务加入同一个自定义网络服务间通信直接用容器名而不是IP对外只暴露网关端口。举个常见架构Nginx容器暴露80端口后端服务不映射端口、只在内部网络被Nginx代理这样比把每个服务都映射出来更安全。数据库容器同样只在内部网络可见。各类特殊业务系统的部署比如人大金仓数据库、SQL Server、OpenObserve、RAGFlow、Hadoop集群核心都是把官方文档里的启动命令或compose文件拿过来按需调整路径和端口。有一个基本原则优先用官方镜像尤其是数据库和基础组件第三方镜像必须看完整Dockerfile避免来路不明的镜像夹带私货。5. 网络、权限与服务启动高频故障排查实录5.1 Docker网络不通、容器间无法通信怎么排查Docker网络问题几乎是每个使用者都会遇到的。我总结过一套快速排查顺序按这个顺序走能省一半时间先确认是“宿主机访问容器不通”还是“容器访问容器不通”。看docker network ls列出的网络检查相关容器是否加入了同一个网络。用docker inspect 容器ID查看容器的网络配置确认IP和网关。在容器里执行ip addr、ping 网关或curl 容器内端口缩小问题范围。宿主机访问容器不通常见原因是端口映射参数写错或者宿主机防火墙拦截。容器间通信不通最常见的是两个容器没有加入同一个自定义网络。Docker默认的bridge网络里虽然也可以互通但自定义网络才支持DNS解析也就是用容器名访问。还有一种隐蔽问题容器内的服务监听了127.0.0.1而不是0.0.0.0。这种情况容器管理层面看起来一切正常但外部怎么都访问不了需要用docker exec进入容器看应用进程的监听地址很多时候把配置文件里的绑订地址从127.0.0.1改成0.0.0.0就通了。5.2 权限错误和服务启动失败的处理思路权限问题排在初学者遇到的报错第二位。最典型的是那种docker: permission denied这是因为当前用户不在docker用户组里。执行sudo usermod -aG docker $USER然后重新登录终端或者执行newgrp docker就能解决。这不是安装出问题而是Linux用户组权限的常规操作。服务启动失败要复杂一些。我在Ubuntu上遇到最多的情况就是daemon.json写坏了格式错误会让Docker服务直接起不来。遇到这类问题先别慌用journalctl -u docker或docker daemon --debug看日志日志里基本会明确提示错误原因。我的实际经验排序配置语法错误 磁盘空间不足 存储驱动与内核不兼容 端口冲突 SELinux拦截。磁盘空间不足在长期运行Docker的服务器上非常常见/var/lib/docker会被镜像和容器日志迅速填满平时养成用docker system prune定期清理的习惯能省很多麻烦。如果你在使用IDEA这类工具时看到cannot run program docker: createprocess error2, 系统找不到指定的文件原因通常是Docker Desktop没启动、命令行里没有Docker或IDE配置里的docker路径不对。Windows下默认Docker CLI路径是C:\Program Files\Docker\Docker\resources\bin\docker.exe在IDE的Docker设置里手动指过去或者重启Docker Desktop后再试一次问题基本就解了。5.3 Windows端virtualization support not detected核心解决过程这个问题在热词里出现两遍说明踩坑的人非常多。我的解决过程整理成了一份可执行的checklist重启进入BIOS/UEFI找到Intel VT-x、Intel Virtualization Technology或AMD SVM Mode设置为Enabled。打开“启用或关闭Windows功能”确保“虚拟机平台”“适用于Linux的Windows子系统”两项都已勾选如果是Hyper-V方案还需要勾选Hyper-V。以管理员身份运行PowerShell执行wsl --status检查内核版本看到“默认版本2”才算正常。如果WSL状态不对执行wsl --update或者wsl --set-default-version 2。在“Windows安全中心→设备安全性→内核隔离”里如果开启了内存完整性某些老机器上会和Docker冲突可先关闭试一次。如果你本身是在虚拟机里再用Docker Desktop那需要确认你的虚拟化软件对嵌套虚拟化的支持。嵌套虚拟化没有开启时容器系统里检测不到CPU虚拟化Docker Desktop同样会报这个错。这类场景下直接换Linux原生Docker可能更省心。5.4 GPU容器的配置Ubuntu安装NVIDIA Container ToolkitAI相关的Docker使用越来越普及GPU容器化避不开NVIDIA Container Toolkit。在Ubuntu上的安装流程大致如下distribution$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/libnvidia-container.list | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart docker配置完成后运行GPU镜像前先确认宿主机驱动正常nvidia-smi能看到显卡信息。然后测试docker run --rm --gpus all nvidia/cuda:12.0.0-base-ubuntu22.04 nvidia-smi一个常见问题是宿主机NVIDIA驱动版本太老容器内CUDA版本太高导致兼容失败。遇到这种情况看不到具体报错容器直接退出。解法不是乱升级宿主机驱动而是选CUDA版本比宿主机驱动版本旧一个档次的镜像稳定优先。6. 进阶玩法与我的长期踩坑心得6.1 大模型与机器人相关镜像怎么跑vLLM、ROS2、Hadoop、RAGFlow现在跑大模型推理也变得靠Docker。以vLLM为例它提供OpenAI兼容接口加载本地模型非常方便docker run --gpus all \ -v ~/.cache/huggingface:/root/.cache/huggingface \ -p 8000:8000 \ --ipchost \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-Embedding-0.6B \ --task embedding \ --port 8000需要提醒的是vLLM对模型类型的支持是分版本的embedding类模型和生成类模型在不同版本的差异很大。看到类似v0.27.1这种版本号时先确认是官方镜像还是第三方打包再看模型是否在该版本支持列表里。GPU推理镜像普遍很大下载前确认磁盘空间运行的时候记得加--shm-size1g或--ipchost否则多进程推理容易崩。ROS2的容器化比较特殊尤其是micro-ROS agent这类需要直连串口的场景。容器里访问宿主机串口要在docker run时加--device /dev/ttyUSB0或使用--privileged网络模式建议用--network host因为ROS2的DDS默认发现机制在bridge网络里会有组播问题。这类开发环境用容器确实能隔离依赖版本但设备直通的兼容性一定要先做小实验。Hadoop这类大数据模拟环境镜像拉下来后内存需求很夸张一个集群可能吃掉8GB以上。我建议先用--memory限制内存避免把开发机拖死。RAGFlow这类知识库系统的Compose文件里通常带多个服务部署前仔细看README注意向量数据库和模型服务的依赖关系。6.2 特殊应用部署注意点青龙依赖、DVWA靶场与Photopea有一些项目的部署不能用简单docker run搞定因为它们需要“依赖管理”。比如青龙面板这类定时任务管理工具本身是一个运行管理Web界面但容器内部还需要安装Node.js、Python、Rust等不同语言的依赖才能跑各种任务脚本。你在容器里直接pip install可能根本不生效因为容器重建后就没了。正确的做法是在青龙的“依赖管理”后台模块里去安装或者把依赖声明写进自定义构建Dockerfile里。这类容器化应用的关键思路是依赖要么在镜像构建时固化要么通过管理工具持久化到挂载卷。用Docker部署DVWA这类Web安全靶场适合在本地搭一个教学和测试环境docker run -d -p 80:80 vulnerables/web-dvwa就能跑起来。需要注意靶场环境存在的初衷是训练Web安全测试技能只能在本地或授权环境中使用不能拿它去对任何非授权系统操作。Photopea这类在线修图工具也有自托管方案跑起来后就能在自己的网速条件下使用部署思路和普通Web应用没有区别。6.3 给不同阶段玩家的实用建议回想我刚学Docker的那段时间最大的遗憾不是命令没背熟而是没有尽早建立“容器是基础设施”的思维。我后来形成了一套我自认为比较稳妥的学习路径先只学docker run、docker ps、docker exec、docker logs这几个命令用它们把nginx和MySQL跑起来理解端口、数据卷、日志这几个概念然后学Dockerfile把自己做的一个小服务打包成镜像之后再进入Compose因为现实中的应用几乎都是多容器协作最后才是网络和编排这部分遇到问题就按我前面写的排查顺序一步步来。生产环境与学习环境的差别极大这里有几点我攒了很久的经验不要在任何生产环境使用latest标签镜像必须锁定具体版本号所有容器都要加--restart策略日志要配置轮转否则宿主机磁盘会被json.log文件一点点吃掉数据卷和容器生命周期要分开管理数据安全永远优先。每次操作前想清楚“容器删了数据丢不丢”这句话能帮你规避80%的严重事故。Docker真正的价值不是“把应用装进一个个盒子”而是让你用标准化的方式描述和交付服务。顺着这条主线走下去你会发现后面学习Kubernetes、容器编排、可观测性的时候之前扎下的概念基础都还在帮你省力。