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的操作类似,但用yum:
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.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_ENABLED=0 GOOS=linux go build -o app . FROM alpine:3.19 WORKDIR /root/ COPY --from=builder /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 -Dimage=myregistry/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-plugin,Ubuntu那一段已经包含。你也可以用独立二进制文件安装,但插件方式更省心,命令就是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-server=utf8mb4 - --collation-server=utf8mb4_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 error=2, 系统找不到指定的文件,原因通常是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 Toolkit
AI相关的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 --runtime=docker 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 \ --ipc=host \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-Embedding-0.6B \ --task embedding \ --port 8000需要提醒的是:vLLM对模型类型的支持是分版本的,embedding类模型和生成类模型在不同版本的差异很大。看到类似v0.27.1这种版本号时,先确认是官方镜像还是第三方打包,再看模型是否在该版本支持列表里。GPU推理镜像普遍很大,下载前确认磁盘空间,运行的时候记得加--shm-size=1g或--ipc=host,否则多进程推理容易崩。
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、容器编排、可观测性的时候,之前扎下的概念基础都还在帮你省力。