1. 为什么我推荐在虚拟机里装Docker,而不是在Windows上硬啃Docker Desktop
如果你正在Windows上折腾Docker Desktop,被那个"virtualization support not detected"的报错折磨得想把电脑扔出窗外,那这篇文章就是给你的。我把话先说在前面:与其在宿主机上跟虚拟机监视程序、WSL2、Hyper-V这几个东西较劲,不如直接在VMware里装一台Ubuntu虚拟机,然后在Ubuntu里装Docker。这套方案我用了好几年,省心、干净、出问题还能随时回滚。
1.1 Docker Desktop反复报错的那台Windows机器
我见过太多人卡在同一个地方:Docker Desktop在Windows上启动失败,报错提示"Virtualization support not detected"或者"VMware Workstation cannot connect to the virtual machine"。这个问题的根源在于Docker Desktop的Windows版依赖两块底层能力——WSL2或者Hyper-V。你一旦在Windows功能里启用了"虚拟机监控程序平台"或Hyper-V,VMware Workstation会和它抢占同一套虚拟化资源,结果就是Docker Desktop没起来,VMware里的虚拟机也连不上了。
我去年在某台笔记本上实测过,开Hyper-V之后VMware直接报错"无法连接到虚拟机。请确保您有权运行该程序",最后只能关掉Hyper-V才恢复正常。你要知道,Windows上跑Docker不是不可以,但它牵扯太多系统层改动:BIOS里开启VT-x、Windows功能里勾选WSL2、还要处理和VMware/VirtualBox的共存冲突。对于只想学Docker、想尽快把容器跑起来的人来说,这条路实在太绕了。
1.2 虚拟机方案真正的价值在哪
用虚拟机装Ubuntu再装Docker,好处有三点:第一,环境干净,Linux下跑Docker是原生支持,不需要任何翻译层;第二,快照功能太好用,装坏了直接恢复快照,几秒钟回到之前的状态,比在物理机上折腾安全得多;第三,和真实服务器环境对齐,你以后往云服务器上部署,操作方式一模一样,都是Linux命令行,都是systemd管理服务,不会有"Windows上可以,Linux上不行"的割裂感。
特别适合这个方案的人群有三类:刚接触Linux和Docker的新手、需要在干净环境里做各种实验的开发者、以及想在一台Windows电脑里模拟多台服务器做联调测试的运维学习者。这篇文章后面就按我的实操路线走一遍,从虚拟机配置到Docker落地,每一步都附带为什么这样做的解释。
2. 装虚拟机之前的配置:镜像版本、虚拟化开关、资源分配
别急着开机装系统,先把虚拟机平台的配置调好,后面能省掉很多麻烦。特别是VMware里那个"虚拟化引擎"的选项,不少人都没注意到它。
2.1 Ubuntu镜像选服务器版还是桌面版
Ubuntu的长期支持版本现在主流是22.04 LTS和24.04 LTS,选哪个都行,LTS版本有五年维护周期,不会用着用着仓库源就失效。版本不用追新,22.04和24.04对Docker来说完全没有体验差距。
关键分歧在于选桌面版还是服务器版。我的建议:如果你主要是学Docker,选Ubuntu Server版,没有图形界面,资源占用更少,而且命令行的操作习惯和真实服务器完全一致。如果你对Linux完全陌生,离开图形界面心里发慌,那选桌面版也行,反正现在Ubuntu桌面版装了Docker之后一切照常,只是多占几百MB内存。从官网下载iso镜像时注意文件名里的版本标识,arm架构的机器就下arm64的包,x86就下amd64,别下错。
2.2 VMware里被忽略的"虚拟化引擎"选项
创建虚拟机时,在"处理器"配置页面里,有一个"虚拟化引擎"区域,里面有一个选项写着"虚拟化Intel VT-x/EPT或AMD-V/RVI"。这个选项很多教程根本不会提,但对你后面在虚拟机里跑Docker很重要。
勾上它的含义是把宿主机的CPU虚拟化能力再透传给虚拟机,让虚拟机里的系统也认为自己运行在支持虚拟化的硬件上。Docker本身不强制要求嵌套虚拟化,因为它只是利用Linux内核的namespace和cgroup,但如果你后面想在Docker里再跑需要虚拟化支持的软件,或者想在虚拟机里用带硬件加速的工具,这个选项没勾会导致一系列莫名其妙的问题。我的做法很直接:只要是在虚拟机里跑Linux,这个选项永远勾上,无一例外。
还有一个经常出现的坑:如果你在创建虚拟机时提示"此主机支持Intel VT-x,但Intel VT-x处于禁用状态",那是BIOS/固件层面的虚拟化开关没打开。重启电脑进BIOS,找到Intel Virtualization Technology之类字样,改成Enabled,再进系统就好了。搜"vmware无法启用虚拟机平台"能搜到一堆求助帖,基本都是这个问题。
2.3 磁盘和内存怎么分配不后悔
内存方面,Docker本身的守护进程占不了多少资源,但容器镜像多起来、跑起来之后消耗就上来了。虚拟机我建议至少给4GB,8GB更好。如果你的宿主机内存够大,给Ubuntu虚拟机分8GB,同时运行两三个容器不会卡。我有一个跑着Nginx加MySQL加Redis的开发环境虚拟机,内存峰值也就3.5GB左右,所以4GB是底线。
磁盘分配要稍微放开一点。一套完整的Docker环境,光镜像就可能占掉5GB以上,mysql、nginx、redis、python这些常用镜像每个几百MB。加上虚拟机系统本身和日常日志增长,60GB是起步建议,给80GB也不嫌多。VMware的磁盘默认用动态扩展模式,实际占用按需增长,所以"提到80GB"不会真的马上占用你宿主机80GB,放心给。
网络模式这一项,新手先不用纠结太多,默认NAT模式就行,后面我专门有一节讲它的坑在哪。现在,创建虚拟机、挂载Ubuntu iso、装系统,一路默认操作,装完系统后先执行一遍sudo apt update && sudo apt upgrade -y,把系统基础包更新到最新,然后就可以进入Docker的安装环节了。
3. Docker安装:官方源、apt源、自动脚本,我选了哪条路
Ubuntu下装Docker的方式大致有三条:直接用apt装docker.io、配置Docker官方仓库后装docker-ce、或者用Docker官方的一键安装脚本。三条路我全都实际走过,下面直接说结论。
3.1 三种安装方式的对比
先看一张表,把三种方式的特点列清楚:
| 安装方式 | 版本新鲜度 | 与系统包管理的关系 | 升级维护 | 适合场景 |
|---|---|---|---|---|
| apt install docker.io | 滞后,跟随Ubuntu发布节奏 | 完全走系统apt体系 | 随系统升级 | 只想随便试试,不关心新特性 |
| 官方仓库安装docker-ce | 最新,Docker发布后同步更新 | 需要额外配置源和GPG密钥 | 独立升级,稳定可控 | 学习、开发、生产都推荐 |
| get.docker.com一键脚本 | 最新 | 自动配置源并完成安装 | 升级仍需处理源 | 快速初始化新机器 |
很多人会直接执行sudo apt install docker.io,图省事。这个命令确实能装上Docker,但装出来的是Ubuntu仓库里打包的版本,版本号往往比官方版本落后不少。这里特别叮嘱一下:生产环境或者长期学习环境,别用docker.io这个包。有一次我在一台Ubuntu 22.04上贪图省事用了docker.io,之后想用docker compose的新语法,发现Compose插件要么不存在要么版本太老,只能卸载重装,折腾的功夫比一开始配官方源多好几倍。
3.2 官方仓库安装的完整步骤
在Ubuntu上配置Docker官方仓库,核心是添加GPG密钥和软件源描述文件。我按下面这个顺序执行,每一步都验证过:
# 第一步:安装依赖工具 sudo apt update sudo apt install -y ca-certificates curl gnupg # 第二步:创建密钥管理目录(新版Debian/Ubuntu的规范做法) sudo install -m 0755 -d /etc/apt/keyrings # 第三步:下载Docker官方GPG密钥并转为apt使用的格式 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg # 第四步:写入软件源配置 echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # 第五步:更新软件源并安装 sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里解释几个容易困惑的点。第二步创建/etc/apt/keyrings目录,是为了把第三方软件源的GPG密钥统一存放,Ubuntu 22.04之后的版本都建议用这个路径。第三步里的gpg --dearmor作用是把下载的GPG密钥从ASCII格式转换为二进制格式,apt只认这种格式。第四步里的$(dpkg --print-architecture)会自动输出当前系统架构,比如amd64,$(. /etc/os-release && echo "$VERSION_CODENAME")会自动获取系统版本代号,像是jammy或noble,这样就不用手动改文件内容了。
安装的这几个包各有用处:docker-ce是Docker守护进程本体,docker-ce-cli是命令行工具,containerd.io是容器运行时(Docker依赖它来管理容器生命周期),docker-buildx-plugin是构建镜像用的插件,docker-compose-plugin就是现在官方推荐的新版Compose(命令是docker compose,中间有个空格,不是老版本的docker-compose)。
3.3 安装完成后根据系统状态做验证
装完先别急着跑容器,确认一下守护进程的状态。
# 查看docker服务状态 sudo systemctl status docker # 如果没启动,手动启动并设置开机自启 sudo systemctl enable --now docker # 查看版本信息 sudo docker versionsystemctl status docker如果显示active (running),说明守护进程已经起来了。enable --now这条命令把开机自启动和立即启动一步做完,虚拟机关机再开机后Docker会自动运行,不需要每次手动启动。docker version会同时显示客户端和服务端版本,如果只显示Client没显示Server,多半是守护进程没起来,先回去看status。
如果你实在不想一步步配置源,可以用一行脚本安装:
curl -fsSL https://get.docker.com | bash这个脚本会自动识别Ubuntu版本,配置Docker官方源并完成安装。我建议初次学习还是手动走一遍源配置流程,因为你能搞清楚每一层在干什么,出问题也知道去哪排查。脚本方式适合你已经懂了原理、但想在新环境快速重建的时候用。
4. 安装只算完成一半:镜像加速、免sudo、基础验证
Docker装好了,如果直接开始拉镜像,大概率会遇到镜像下载慢或者超时的问题。这也是为什么我说安装只算一半——后面这三件事不做,你的体验会非常劝退。
4.1 镜像加速源:安装后第一件事
Docker默认从官方仓库拉取镜像,在国内网络环境下速度很不稳定。解决方法是配置镜像加速源。编辑(如果没有就新建)/etc/docker/daemon.json文件:
sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-'EOF' { "registry-mirrors": [ "https://docker.m.daocloud.io" ] } EOF sudo systemctl daemon-reload sudo systemctl restart docker配置完成后,docker pull就会走加速源拉取镜像。你也可以在阿里云容器镜像服务控制台里申请一个专属加速地址,格式类似https://<你的ID>.mirror.aliyuncs.com,把自己的地址填进registry-mirrors数组里也行。这个文件是JSON格式,多个加速地址用逗号分隔,格式错了Docker守护进程会启动失败,所以每次改完都要执行systemctl daemon-reload再restart docker。
4.2 sudo不用每次都敲:用户组和权限
默认情况下,执行docker ps、docker run这些命令需要root权限,每次都要敲sudo很烦。原因是Docker的守护进程监听在/var/run/docker.sock这个Unix套接字上,默认只有root和docker组的用户才能访问。
把自己加入docker组,重新登录后就不用再敲sudo了:
sudo usermod -aG docker $USER # 关键:需要重新登录或执行newgrp命令才能生效 newgrp docker这里有个安全细节要提醒你:加入docker组等于拥有了和root等同的权限,因为你可以通过docker run -v /:/host ...把宿主机目录挂载进容器,从而以root身份读写宿主机文件。这在个人学习虚拟机里无所谓,但如果你在真实的多用户服务器上,给用户分配docker组权限要慎重。我是建议在虚拟机里直接加组,图省事,因为这台机器本身就是你的试验场。
4.3 开机自启和基础验证
再确认一遍开机自启已经设置好:
sudo systemctl enable docker前面已经执行过enable --now的话,这里会提示已经存在,不用管。接下来用最经典的镜像做验证:
docker run hello-world正常情况下会拉取镜像并打印一段欢迎信息,告诉你Docker已经能正常工作。如果这一步卡住很久,多数还是镜像加速的问题,回到4.1检查daemon.json配置和网络连通性。如果提示permission denied,说明当前用户不在docker组,回到4.2重新登录再试。
做完这一步,一套"能用的Docker环境"就算彻底就绪了。但虚拟机环境里跑容器,后面还有几个高频坑等着你踩。
5. 虚拟机里跑容器最常踩的三个坑:端口映射、挂载权限、防火墙
这一节的内容是我在虚拟机里跑容器反复踩过、花了大量时间排查才搞明白的地方。提前看完,你至少能少走两天弯路。
5.1 NAT模式下端口映射:外部访问不到容器的解法
VMware默认的网络模式是NAT,虚拟机通过共享宿主机IP来访问外网,但反过来,从宿主机或者局域网其他机器访问虚拟机里的服务,就没那么顺畅了。
场景是这样的:你在Ubuntu虚拟机的Docker里跑了一个Nginx容器,做了-p 8080:80端口映射。在虚拟机里用curl http://localhost:8080访问完全正常,但在宿主机的Windows浏览器里输入http://localhost:8080却连不上。
原因在于NAT模式下,宿主机访问虚拟机IP需要经过VMware的虚拟网卡转发,而容器端口映射到的是虚拟机的网络命名空间,外面的流量进不来。解决办法有三个:一是把虚拟机网络改成桥接模式,让虚拟机拿到和宿主机同一网段的独立IP,然后在Windows浏览器直接访问那个IP加端口;二是在VMware的"虚拟网络编辑器"里给NAT模式添加端口转发规则,把宿主机的某个端口转发到虚拟机的对应端口;三是用SSH端口转发,ssh -L 8080:localhost:8080 user@虚拟机IP,把宿主机端口映射到虚拟机本地端口。
我最常用的还是桥接模式。改桥接只需要在虚拟机设置里把网络连接从NAT改成桥接,然后进入Ubuntu重新获取IP即可。桥接模式的好处不仅是端口访问方便,虚拟机之间的通信也走真实局域网,更接近真实服务器环境。要注意的是,桥接模式下虚拟机的IP是局域网动态分配的,如果用固定IP习惯,建议在Ubuntu的网络配置里绑定静态IP,免得重启后IP变了还要到处找。
5.2 文件挂载权限错乱:宿主机目录和容器目录的所有权冲突
挂载目录是Docker的高频操作,出现问题也最高频。最常见的是你把宿主机的某个目录挂载进容器后,容器里读写这个目录时权限错乱。
举个例子,我在虚拟机里做了这样的挂载:
docker run -d -p 8080:80 \ -v /home/ubuntu/nginx-html:/usr/share/nginx/html \ nginx然后在宿主机里往nginx-html目录放网页文件,发现Nginx显示403 Forbidden。排查后发现,宿主机的nginx-html目录属主是ubuntu用户(uid=1000),但容器里的Nginx以root身份运行,读取文件没问题,写入会有冲突;反过来如果容器里创建的文件的属主是root(uid=0),在宿主机上就会显示成root所有,普通用户想改文件还得加sudo。
解决办法是保持宿主机的目录权限和容器内进程的用户一致。Nginx容器提供了环境变量NGINX_UID和NGINX_GID,可以直接指定运行用户,不过我更喜欢简单粗暴的chown:
sudo chown -R 1000:1000 /home/ubuntu/nginx-html这样容器和宿主机都用uid 1000的文件属主,冲突就没了。所以每次做目录挂载前,先想想:容器内进程以什么用户运行?宿主机的目录权限要让谁方便读写?想清楚这两点,权限问题基本不会出现。
5.3 防火墙和Docker的"私人恩怨"
在Ubuntu虚拟机里跑容器,如果发现端口映射怎么都不生效,检查一下是不是开启了防火墙。
Docker的端口映射原理是直接操作iptables规则,把宿主机端口转发到容器的IP。但如果你在Ubuntu里启用了ufw(Uncomplicated Firewall),ufw默认策略会挡住转发链的流量,Docker发布的端口就会从外部无法访问。这个问题在物理机和云服务器上很常见,在虚拟机里同样会遇到。
我的建议是在虚拟机学习环境里,直接不要开启ufw:
sudo systemctl disable --now ufw如果你确实需要防火墙,那就得理解Docker和iptables的协作方式,不盲目禁用,但这属于进阶话题,新手阶段建议先把防火墙关掉,把环境跑通再考虑安全加固。这里特别提示:不要随便在daemon.json里加"iptables": false,那会导致容器网络彻底乱掉,我试过一次,最后重启Docker才恢复。
还有一个容易忽略的坑是容器时区。默认容器是UTC时间,date命令显示的时间和宿主机差了8小时。解决方式很简单,在docker run时加环境变量:
docker run -d -e TZ=Asia/Shanghai nginx或者用docker-compose时在服务配置里写:
environment: - TZ=Asia/Shanghai这些坑看着不大,但在实际使用中每一个都能让人排查半天。
6. 用docker compose把Nginx和MySQL跑起来:一次完整实践
光谈理论没意思,最后我用一个实际项目来收尾——用docker compose一次性启动Nginx和MySQL,这也是热搜词里出现频率很高的组合。
6.1 定义compose文件
在工作目录创建docker-compose.yml:
services: nginx: image: nginx:1.26 container_name: web-nginx ports: - "80:80" volumes: - ./html:/usr/share/nginx/html - ./nginx/conf.d:/etc/nginx/conf.d environment: - TZ=Asia/Shanghai restart: unless-stopped mysql: image: mysql:8.0 container_name: web-mysql ports: - "3306:3306" environment: MYSQL_ROOT_PASSWORD: root123456 TZ: Asia/Shanghai volumes: - mysql-data:/var/lib/mysql restart: unless-stopped volumes: mysql-data:这个文件有几个值得说明的设计。nginx服务的./html目录挂载进去,意味着你在宿主机上放网页文件,容器里立即生效。./nginx/conf.d目录挂载进去,可以在宿主机上添加站点配置文件,实现多站点自定义域名配置的效果,这正好对应热搜词里"本地+虚拟机多端口Nginx开发环境多站点自定义域名配置"的场景。mysql的数据卷用命名卷mysql-data,数据持久化在虚拟机磁盘上,容器删了重建数据还在。
restart: unless-stopped的意思是容器意外退出时自动重启,但手动stop掉的不会强行拉起。这个策略最适合开发环境。
6.2 启动、验证、清理
执行启动命令:
docker compose up -d第一次启动会拉取Nginx和MySQL镜像,等待时间取决于网络和镜像加速效果。启动完成后:
docker compose ps docker ps看到两个容器状态为Up,就说明跑起来了。验证服务:
curl http://localhost curl http://localhost:3306第一个命令会返回Nginx的默认页面内容。第二个命令如果端口通,会收到MySQL协议的错误信息,这也算验证通过。需要看容器日志排查问题就用:
docker logs web-nginx docker logs web-mysqlMySQL容器第一次启动时,日志里有Temporary password is generated之类的提示。另外要提醒一句:MySQL 8.0在容器里的初始化和认证方式有些细节和5.7不同,如果遇到客户端连接不上,多半是认证插件问题,在创建用户时用mysql_native_password插件可以解决。
用完可以随时docker compose down,容器停了但数据卷还在;连数据一起清理才用docker compose down -v,这个命令会删掉mysql-data卷里所有数据,执行前一定确认没有重要数据。
最后说一个我一直沿用的实操心得:在虚拟机上装Docker,最值钱的功能就是"快照"和"克隆"。每次配置好一套干净环境后,拍一个快照再继续折腾,一旦把系统搞挂,恢复快照分分钟回到配置完美的状态。你可以把装上Docker的这台虚拟机保存为一个模板,以后想新开环境时直接克隆,几分钟就得到一台新机器,比从零安装Ubuntu再装Docker快得多。这套"虚拟机加Docker"的开发方式,我在本地用了很久,它最大的优势是:无论你怎么折腾,宿主机Windows永远安全,而且错了随时推倒重来。