
前几天帮朋友收拾一台Ubuntu服务器发现他还停留在“一条apt命令装完就开跑”的阶段装Docker Desktop时又弹出virtualization support not detected折腾一晚上没起来。Ubuntu上跑Docker这件事很多坑其实都出在没理清安装路径和运行机制。这篇文章我就按自己常用的路线从头顺一遍覆盖Ubuntu 22.04 LTS下的Docker安装、镜像容器和数据卷的核心概念、MySQL与Redis这些高频实战场景还有网络不通的排查思路。不管你是刚接触Linux的新手还是在服务器上批量部署服务的老手里面都有可以直接照做的部分。1. 安装方式选型为什么先别急着装Docker Desktop1.1 三种主流安装路径的取舍在Ubuntu上装Docker常见路径其实就三条直接用apt安装发行版自带的docker.io添加Docker官方软件源之后安装docker-ce以及在桌面环境下安装Docker Desktop。我通常的建议是服务器上正经跑容器果断选docker-ce开发机上想要图形界面和跨平台一致的体验再考虑Docker Desktop而docker.io这条路径能不用就不用。docker.io的优势只有一条命令的简单sudo apt install docker.io就能装完不需要维护GPG密钥和软件源。但代价是版本滞后在22.04上它对应的Docker引擎版本往往比官方新版落后好几个大版本一些新特性、安全补丁和兼容性修复都享受不到。docker-ce是Docker官方维护的稳定分支版本新、更新及时官方源里同时提供了containerd和runc的配套版本整体可靠度明显更高。Docker Desktop的定位又是另一回事它本质上是“Docker引擎图形界面虚拟化/WSL集成”的组合体。在Ubuntu桌面版上安装之后你可以通过界面查看容器列表、镜像列表和日志面板点几下鼠标就能完成启停操作对不习惯命令行的用户非常友好。但它的体积大还依赖systemd、GNOME等桌面组件适合本机开发调试不适合服务器。服务器的要求是轻量、稳定、可控一个dockerd守护进程加上命令行工具就够了。1.2 官方源安装docker-ce的完整步骤以Ubuntu 22.04 LTS为例24.04 LTS同样适用。第一步先更新APT索引并安装后续需要的基础工具sudo apt update sudo apt install -y ca-certificates curl gnupg第二步添加Docker官方的GPG密钥这一步的目的是让APT能够验证软件包来源的合法性。很多教程把密钥直接放到旧路径在22.04上容易出警告建议使用新的keyrings目录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 sudo chmod ar /etc/apt/keyrings/docker.gpg第三步写入软件源。这里用$(dpkg --print-architecture)自动获取本机架构用$(lsb_release -cs)自动匹配发行版代号这样换发行版版本时不用改命令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第四步更新索引并安装Docker组件。我习惯把buildx和compose插件一起装掉后面构建镜像和编排服务都省事sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完之后启动服务并验证。sudo systemctl enable --now docker这条命令同时完成开机自启和启动然后执行sudo docker run hello-world它会先检查本地有没有hello-world镜像没有就从Docker Hub拉取。能正常打印Hello from Docker!说明引擎没问题。如果拉取超时多半是网络或镜像源问题后面会专门讲。1.3 装完必做的三个配置装好只是开始我每次都会立刻做三件小事能省掉后面一大堆的权限和磁盘问题。第一件把当前用户加入docker组避免每次敲docker命令都要sudosudo usermod -aG docker $USER改完用户组之后要重新登录会话或者执行newgrp docker刷新组权限否则docker命令会一直提示permission denied。这个细节我见太多人栽过其实不是docker没装好是用户组没生效。第二件确认服务开机自启。用systemctl enable --now docker之后一般会创建好符号链接。如果在桌面版环境里同时装了Docker Desktop它有自己的自启机制这时要注意别让两个引擎抢同一个socket我后面会细说。第三件提前规划数据目录。我习惯在/etc/docker/daemon.json里指定data-root把镜像和容器数据迁到大分区避免根分区被撑爆同时顺手把日志轮转限制加上{ data-root: /opt/docker/data, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }不少服务器的根分区默认只有几十G容器日志不限制的话跑几天就能吃掉几个GB。限制单个日志文件50MB、最多保留3个是生产环境维护期最有用的配置之一。改完配置别忘了sudo systemctl restart docker而且这个配置最好在刚装完、还没拉多少镜像的时候设置数据目录迁移起来最省事。2. 镜像、容器、数据卷搞懂这三个东西就赢了一半2.1 镜像和容器到底什么关系很多人一开始会被镜像和容器这两个词绕晕我用一个生活化的类比镜像是一张光盘里的系统安装包容器是光盘启动起来之后正在运行的电脑。镜像只读、不可变容器则是镜像运行时的实例有独立文件系统、可以从镜像启动但运行时的修改不会写回镜像。所以容器里删文件、改配置都不会污染镜像这也是为什么容器适合跑“一次性”任务。但反过来容器删掉之后你在里面产生的所有数据也会一起消失。如果只是跑一个临时环境无所谓如果跑的是数据库、日志服务这类有状态应用就必须把数据放到容器外面这就引出数据卷。这种机制带来一个思维方式上的转变容器应该被当作牛羊而不是宠物。牛羊养大了可以杀掉换新的宠物死了会心疼。Docker的使用习惯应该是“随时可以删掉容器重建”环境全部声明式地写在镜像和启动命令里而不是登录进容器里手动改来改去。2.2 数据卷把数据留在容器外面数据卷的核心作用是把容器内的目录映射到宿主机上让数据不依赖容器的生命周期。最常用的写法是-v或者--mount参数。docker run -d --name nginx -p 80:80 -v /opt/nginx/html:/usr/share/nginx/html nginx左边/opt/nginx/html是宿主机目录右边/usr/share/nginx/html是容器内目录。这样你在宿主机上改网页文件容器里立刻就能看到就算容器被删掉数据还在。对于数据库这类应用我强烈建议把所有配置和数据目录都挂出来否则容器一删数据全没。MySQL的/var/lib/mysql、Redis的/data、PostgreSQL的/var/lib/postgresql/data都是需要重点挂载的目录。挂载目录时还有一个容易踩的坑宿主机目录的属主和权限要提前想好容器里的进程通常以特定UID运行挂载目录权限不对会报Permission denied这也是后面MySQL实例中常见的启动失败原因。2.3 常用命令速记会这些就能干活了日常使用中我不喜欢背一大堆手册真正高频的就下面这些。选镜像用docker pull看本地镜像用docker images起容器用docker run看运行状态用docker ps看日志用docker logs -f进容器用docker exec -it。以下几个参数最常用-d表示后台运行-p做端口映射-v做数据挂载-e设置环境变量--name给容器命名--restartalways设置重启策略。对于有依赖关系的多个容器推荐用docker compose文件统一管理把端口、环境变量、挂载、网络写成一份YAML项目重建时一条docker compose up -d就搞定不用记一长串参数。我还习惯用docker inspect和docker logs这两个排查命令遇到容器起不来的情况先看docker logs -f 容器名拿到确切报错再用docker inspect 容器名查状态、挂载和网络配置比瞎猜高效得多。说白了Docker的命令体系并不复杂掌握十几条就能覆盖日常80%的需求。3. 三个高频实战场景MySQL、Redis、Python3.1 用容器部署MySQL 8.0并持久化数据以MySQL 8.0为例这是网上问得最多的一个。先准备目录sudo mkdir -p /opt/mysql/{data,conf}然后启动容器注意这串参数里每个都不能少docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStr0ngPass \ -v /opt/mysql/data:/var/lib/mysql \ -v /opt/mysql/conf:/etc/mysql/conf.d \ --restartalways \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci这里有几个关键点MYSQL_ROOT_PASSWORD是首次初始化时设置root密码的环境变量挂载两个目录分别对应数据文件目录和自定义配置目录最后的两个参数是追加给mysqld的启动参数强制使用utf8mb4字符集避免中文乱码。启动后用docker logs -f mysql8观察初始化日志出现ready for connections说明就绪。接着进容器验证docker exec -it mysql8 mysql -uroot -p输入密码后执行show variables like character%;可以看到字符集相关变量基本都是utf8mb4就说明配置生效。常见失败原因有两个一是宿主机3306端口被占用改映射端口即可二是挂载目录权限不对导致mysqld无法写临时文件日志里会明确报错。遇到权限问题可以先用docker run --rm -it mysql:8.0 bash进一个临时容器查一下镜像里mysql用户的UID再用chown把宿主机目录属主改成一样的。3.2 Redis主从复制一主两从怎么搭主从复制常用于读写分离和数据备份。先用docker network create redis-net创建一个自定义桥接网络让三个容器用容器名互访而不是依赖每次变化的IP地址。docker run -d --name redis-master --network redis-net -p 6379:6379 --restartalways redis:7 redis-server --appendonly yes docker run -d --name redis-slave1 --network redis-net --restartalways redis:7 redis-server --replicaof redis-master 6379 docker run -d --name redis-slave2 --network redis-net --restartalways redis:7 redis-server --replicaof redis-master 6379重点在于--replicaof后面跟的是容器名redis-master而不是IP。在自定义网络里Docker内置DNS会将容器名解析成对应地址这样主节点重启换IP也不影响从节点连接这是很多人容易忽略的一点。验证主从状态docker exec -it redis-slave1 redis-cli -p 6379 info replication看输出里role:slave、master_link_status:up即可。如果状态是down大多数情况下是从节点连不上主节点可以用docker logs -f redis-slave1看日志或者用docker network inspect redis-net检查容器是否都在同一网络里。从节点默认是只读的配置文件里的replica-read-only yes保证数据不能被写入。如果业务需要从节点接受读请求客户端也要做读写分离配置。这个场景很适合用来理解Docker网络的作用同网络内容器之间用名字通信跨网络则需要端口映射或路由。3.3 运行Python环境与部署微服务跑Python临时环境是容器最直接的福利之一一条命令就能获得一个干净、可复现的Python工作区docker run -it --rm -v $(pwd):/app -w /app python:3.11-slim bash这句话的意思是以交互模式启动一个python:3.11-slim容器把当前目录挂载到容器内/app工作目录设为/app进去就是bash代码在宿主机改容器里直接跑。--rm表示退出即删除容器很适合日常临时调试不会堆积一堆停止状态的容器。如果要部署微服务我会写一个简洁的DockerfileFROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple COPY . . CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 8000]镜像构建使用docker build -t my-service .启动用docker run -d --name my-service -p 8000:8000 --restartalways my-service。这里想提醒一个新手最容易踩的坑CMD里写的地址必须是0.0.0.0而不是127.0.0.1。容器内部是一个隔离的网络命名空间如果服务监听127.0.0.1映射出去的端口永远连不通。这个坑在各类应用都有不只是Python。微服务之间的调用则建议放在同一个自定义网络里用服务名互相访问记得把--network参数加上。另外很多slim镜像默认不带编译器凡是需要pip编译C扩展的项目构建阶段必须apt-get update apt-get install -y build-essential否则pip install会报缺少gcc。这个错误在热词里也经常出现成因基本都是镜像太精简。4. Docker网络不通怎么办一份排查实录4.1 先定位是哪一层不通网络问题千奇百怪但归纳下来就三种容器访问外网不通、宿主机访问容器端口不通、容器之间互访不通。这三种问题的排查方向完全不同先别急着改防火墙第一步应该确认现象到底属于哪一种。容器访问外网不通典型表现是容器里apt update或curl外网地址超时但宿主机本身网络正常。这种情况优先检查宿主机上是否开启IP转发因为容器对外访问要经过NAT而NAT依赖内核的net.ipv4.ip_forward开关。可以用sysctl net.ipv4.ip_forward检查输出0就打开它写入/etc/sysctl.conf后执行sysctl -p生效。宿主机访问容器端口不通需要按链路拆开看端口映射有没有绑定正确、容器内服务有没有监听、docker-proxy或iptables规则有没有生效。我先用docker port 容器名看映射结果再用curl -v测试最后进容器用ss -lnt看服务是否真的在监听。定位到具体环点再动手往往比盲目重启快得多。容器之间互访不通一般集中在两点两个容器不在同一个网络或者自定义网络模式下防火墙规则有残留。用docker network inspect 网络名看容器IP和归属基本就能判断是不是网络选错了。4.2 我常用的一套排查命令排查的时候我有一套固定的动作按顺序执行能快速缩小范围。先把所有网络和容器状态看一遍docker network ls docker ps -a docker inspect 容器名 | grep -A10 NetworkSettings然后查看iptables的NAT规则Docker的端口映射本质上就是DNAT规则如果规则缺失映射就不生效sudo iptables -L -n -t nat sudo iptables -L -n -t filter再检查IP转发和网卡状态sysctl net.ipv4.ip_forward ip addr show docker0如果docker0网卡不存在说明docker服务本身没正常初始化这时候要去看dockerd日志journalctl -u docker -n 50。一个容易被忽略的点是宿主机人为改了防火墙策略比如用了ufw并启用了默认拒绝会让容器流量被拦。Ubuntu上常见做法是把/etc/default/ufw里的DEFAULT_FORWARD_POLICY改成ACCEPT或者干脆在部署Docker的机器上不启用ufw让Docker自己管理iptables规则。两种策略都有讲究重点是别让两套防火墙规则互相打架。4.3 几个我踩过的网络坑第一个坑是改/etc/docker/daemon.json里的bip字段后容器网络异常。bip用于自定义docker0网桥地址如果设置成和局域网网段重叠路由和DNS都会出问题。个人经验是bip只在你明确需要变更内网网段时才去动默认172.17网段几乎不需要改。第二个坑是systemd环境下手动用iptables-restore覆盖了规则导致所有容器网络瞬间断开。因为Docker会在iptables里维护自己的规则链你手动恢复规则时很容易把FORWARD链的规则覆盖掉。相关操作一定要先备份现有规则再增量修改。第三个坑是热词里经常出现的“docker网络不通”很多其实不是网络问题而是容器没起来。一些镜像在启动时会把健康检查的端口绑错或者entrypoint脚本需要等待依赖日志里反复重启外部看起来就像“网络不通”。所以每次排查网络问题我都会顺手看一眼docker ps和docker logs把服务状态先确认一遍再聊网络。5. Docker Desktop的坑5.1 virtualization support not detected这个报错多出现在Windows上安装Docker Desktop或是在VMware里装Ubuntu后又想跑Docker Desktop的场景。Docker Desktop在Windows上依赖Hyper-V或者WSL2虚拟化功能没有启用时启动就会直接弹virtualization support not detected。如果是Windows物理机通常解决办法是去BIOS开启CPU虚拟化然后到“启用或关闭Windows功能”里勾选Hyper-V和“适用于Linux的Windows子系统”重启后重新启动Docker Desktop。注意Windows 10家庭版没有Hyper-V一般走WSL2方案更顺。进入系统后执行bcdedit检查hypervisorlaunchtype状态如果是off也会导致检测不到虚拟化。在VMware虚拟机里跑Ubuntu再在Ubuntu里装Docker Desktop就涉及“虚拟机套虚拟机”了。VMware的虚拟机里嵌套使用Hyper-V或KVM需要开启虚拟化引擎的“向客户机操作系统公开硬件辅助虚拟化”选项同时客户机CPU要确认能看到vmx或svm标志。简单判断方法是在Ubuntu里执行lscpu | grep -i vmx有输出说明嵌套虚拟化可用没有的话就老老实实回到命令行docker-ce方案完全不影响使用容器。5.2 failed to connect to the docker api另一个高频报错是failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine几乎每个新装Docker Desktop的用户都会遇到一次。这个错误的字面意思是Docker客户端连不上Docker引擎常见于引擎还没启动完、引擎启动失败或者客户端与引擎的版本不匹配。处理思路很简单先确认Docker Desktop右下角的状态图标是否变成运行的鲸鱼再在终端执行docker version分别看client和server两段是否都有输出。如果只有client没有server说明引擎没起来去设置里看日志或者执行wsl --shutdown后重新启动Docker Desktop。如果server有了但执行命令仍然报npipe连接失败多半是DOCKER_HOST环境变量被设置成旧的地址了Ubuntu下一般是/var/run/docker.sockWindows下是npipe。检查一下当前终端echo $DOCKER_HOST有值就先unset或者改成docker-desktop对应的URL。这个报错还经常在系统重启后出现因为Docker Desktop的引擎需要用户登录后才启动而脚本里提前执行了docker命令。5.3 在虚拟机里跑Docker的路线选择在VMware里装Ubuntu然后想用Docker我建议直接装docker-ce而不是Docker Desktop。虚拟机本身已经有了一层虚拟化再做嵌套虚拟化不仅配置麻烦性能也有额外开销除非你的目标就是想体验Docker Desktop的图形界面。虚拟机里跑docker-ce需要注意VMware的网卡设置为NAT模式或桥接模式都可以只要宿主机能上网虚拟机里的容器就能上网。有时候容器外网不通反而是因为VMware虚拟网卡的DNS配置有问题在/etc/resolv.conf里临时加上可用DNS即可测试。另外虚拟机的内存建议分配4GB以上docker跑几个容器加上构建任务2GB明显捉襟见肘。至于热词里提到的“docker desktop 汉化包”个人不建议装Docker Desktop界面本身路径固定、词条不多装第三方汉化包反倒可能因为版本不匹配出现界面异常。把接口和命令行工具用顺手效率可能更高。6. 常见问题速查与长期使用心得6.1 高频问题速查表这里整理一张表把标题相关的最常见问题、原因和解决办法列出来直接收藏照着排查就行。问题现象常见原因解决办法docker命令提示permission denied用户不在docker组sudo usermod -aG docker $USER后重新登录docker run hello-world拉取超时默认Docker Hub源不稳定在daemon.json配置registry-mirrors或更换可达镜像源容器启动一会就退出前台进程退出或入口脚本报错用docker logs -f查看错误日志确认CMD是否以前台方式运行宿主机连不上容器映射端口服务监听127.0.0.1或iptables规则异常服务内绑定0.0.0.0检查docker port映射和NAT规则MySQL容器启动失败挂载目录权限不对或端口占用查看日志确认报错调整目录属主或换端口重新映射容器之间互访不通不在同一自定义网络创建自定义网络并让容器加入用容器名通信virtualization support not detectedBIOS未开虚拟化或权限不足开启硬件虚拟化Windows启用WSL2/Hyper-Vfailed to connect to the docker api引擎未启动或DOCKER_HOST错误等待引擎启动检查docker version清理环境变量安装Ubuntu子系统报0x80070424Windows Update服务异常重启wuauserv服务并重新安装WSL更新这张表是我实际维护多台机器时的浓缩版基本覆盖了网络热词里那些常见报错。遇到问题先对号入座能省下大量搜索时间。6.2 我在长期使用中沉淀的几点心得装Docker不是目的把容器用起来才是。我自己的使用习惯是凡是跑在服务器上的中间件一律用Docker Compose维护一份compose文件同时定义镜像版本、端口、挂载、重启策略和环境变量换机器时复制文件跑起来就能恢复。凡是临时调试一律用docker run --rm用完即焚不留下垃圾容器。日志管理方面尽早配置daemon.json里的日志轮转是对所有容器一视同仁的兜底策略。生产环境里我看过太多因为日志文件写到上百GB导致磁盘满的案例容器本身没问题纯粹是日志没限制这个配置一定要优先落好。最后想提醒的是不要因为容器方便就把服务器上的传统服务全部容器化。有些系统服务、内核模块相关的任务并不适合塞进容器遇到这类需求时先想清楚隔离边界在哪里。Docker的价值在于让应用交付和运行环境变得可复用该用的时候用的彻底不该用的时候别硬上这是我在Ubuntu和Docker打交道这几年最深的一点体会。