在Ubuntu 24上折腾Docker,我见过太多人栽在第一步:先跑去下载Docker Desktop,装到一半弹出一句 virtualisation support wasn't detected,然后开始怀疑CPU、BIOS、虚拟机设置,折腾一晚上连个容器都没跑起来。其实问题不在于你操作不对,而是你选了不适合Linux的安装路径。Ubuntu 24.04 LTS(代号Noble Numbat)上真正稳定高效的方案,是直接安装Docker Engine,用命令行完成所有日常操作。这篇教程就围绕这一点展开:从零开始装好Docker Engine、配好镜像源、处理权限、跑通MySQL 8.0和Redis主从,最后聊聊怎么用Dockerfile部署自己的微服务。适合第一次接触Docker的初学者,也适合在24.04上反复踩坑、想一次性理清环境的老手。
1. 先把镜像、容器、仓库这三个概念塞进脑子里
1.1 镜像、容器、仓库,用"安装包、进程、应用商店"去理解
很多教程一上来就让你敲命令,敲完了还是一头雾水。我建议先花五分钟理解三个词,后面的每个操作你都会觉得顺理成章。
镜像(Image):相当于一个只读的安装包。它把程序运行所需的操作系统基础文件、依赖库、代码、配置文件全部打包在一起,类似Windows里的ISO镜像或安装程序。镜像本身不会"运行",它只是一个模板。
容器(Container):镜像被启动之后,就变成了一个正在运行中的容器。你可以把它理解成一台轻量级虚拟机,但它不会像虚拟机那样模拟一套完整硬件,而是直接共享宿主机内核。容器做到的是文件系统和进程空间的隔离,所以启动速度极快,通常几秒钟就能起来。
仓库(Repository):存镜像的地方,相当于应用商店。Docker Hub是默认的公共仓库,里面躺着MySQL、Redis、Nginx等海量官方镜像。你执行 docker pull mysql:8.0,就是从仓库把镜像拉到本地。
1.2 为什么我在文中反复强调"Ubuntu 24"这个版本
Ubuntu 24.04不是普通的版本号,它是一个LTS(长期支持)版本,官方支持周期长达五年以上。Docker官方仓库对它有专门的apt源支持,对应的版本代号是noble。如果你复制网上的老教程,用的是Ubuntu 20.04或22.04的docker源,安装过程大概率会报"仓库没有Release文件"之类的错误。这一点在后面的安装小节里会再强调——装Docker之前,先确认系统版本和codename,不要想当然。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Docker Desktop的坑:为什么你在Ubuntu 24上装它总是失败
2.1 先搞清楚Docker Desktop是什么
Docker Desktop是Docker官方出的图形化客户端,主要面向Windows和macOS用户,因为这两个系统的内核跟Linux容器不兼容,必须借助虚拟机管理程序(如Hyper-V、Virtualization Framework)来跑Linux容器。在Windows上,它还需要依赖WSL 2后端。
而在Linux上,Docker本身就跑在Linux内核上,完全不需要中间那层虚拟机。你直接安装Docker Engine + containerd + CLI,就能获得完整的容器运行时能力。在Linux上装Docker Desktop,相当于给汽车又套了一匹马,多此一举,还容易出问题。
2.2 virtualisation support wasn't detected 的真实原因
热词里那句 "docker desktop failed to start because virtualisation support wasn't detected" 是很多新手卡住的地方。
如果你是在虚拟机(比如VMware)里安装的Ubuntu 24,这种情况尤其常见。Docker Desktop在启动Linux虚拟机时需要硬件虚拟化支持(VT-x/AMD-V),但在VMware默认配置里,嵌套虚拟化通常是关闭的,所以它检测不到虚拟化支持,直接罢工。
而Docker Engine根本不会碰这个需求。容器的隔离依赖Linux内核自带的namespace和cgroups,不需要硬件虚拟化指令。所以你在VMware里装Ubuntu 24,直接装Engine反而一路畅通。这也解释了为什么很多人换了Engine之后,之前所有启动崩溃的问题都消失了。
2.3 那么Windows和macOS用户怎么办
如果你是在Windows上开发,确实只有Docker Desktop或WSL内手动安装Engine两条路。热词里还有 "we've detected that you have an incompatible version of windows" 这条,说明Windows版本太旧(比如Windows 10家庭版没有完整Hyper-V)也会让Desktop装不上。但如果你有WSL环境,直接在WSL里的Ubuntu 24中按本教程操作,是更省心的路子——不需要Desktop,也不需要图形界面。
3. 安装前检查:内核、系统版本、软件源一个都不能漏
3.1 用三条命令确认你的基础环境
不管你是全新安装的Ubuntu 24.04,还是从22.04升级上来的,先执行以下命令,确保环境符合预期:
bash复制cat /etc/os-release
uname -r
uname -m
- /etc/os-release 会输出系统版本,重点看 VERSION_CODENAME 是不是 noble。
- uname -r 查看内核版本,3.10以上即可,24.04默认内核是6.8,完全没问题。
- uname -m 查看CPU架构,普通PC是x86_64,ARM开发板是aarch64。后面添加Docker仓库时需要用到这个值,别填错。
3.2 Ubuntu Desktop还是Server:做AI开发、跑服务该怎么选
热词里有一个经典问题:Ubuntu 24 Desktop和Server哪个版本适合做AI开发。
我的结论很直接:如果你把机器当服务器用,选Server。没有图形桌面的开销,内存占用低,远程SSH管理方便,GPU驱动、CUDA环境的配置也相对干净。Docker本来就不需要图形界面,Server版装完直接开机自启,非常省心。
如果你需要在这台机器上写代码、看浏览器、用IDE、甚至调搜狗输入法这类桌面软件,那就选Desktop。Desktop版完全可以装Docker Engine,容器性能和Server版没有任何区别,只是多占用一点桌面资源。
3.3 Ubuntu 24的apt源文件格式变了,别用老方法
Ubuntu 24.04把旧的 /etc/apt/sources.list 换成了deb822格式,主源配置文件在 /etc/apt/sources.list.d/ubuntu.sources。这里提醒一点:如果你照着老教程去改 /etc/apt/sources.list,会发现改了没效果。
实际操作很简单,先备份再改:
bash复制sudo cp /etc/apt/sources.list.d/ubuntu.sources /etc/apt/sources.list.d/ubuntu.sources.bak
sudo nano /etc/apt/sources.list.d/ubuntu.sources
把文件里的 URIs 行替换成你所在区域访问更快的镜像地址即可。格式大致如下:
code复制Types: deb
URIs: http://cn.archive.ubuntu.com/ubuntu/
Suites: noble noble-updates noble-backports
Components: main universe restricted multiverse
Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg
改完以后执行 sudo apt update,确认没有报错再继续。这一步不是必须的,但对于没有国际网络优化的环境来说,换源后 apt update 的速度差别非常大,尤其是后面要安装一堆依赖包的时候。
4. 用apt仓库方式安装Docker Engine:官方推荐且可控
4.1 先清理可能存在的旧版本
如果你的Ubuntu 24是全新安装,可以跳过这一步。但如果之前用 sudo apt install docker.io 装过Ubuntu自带版本的Docker,或者装过一半失败的文件,建议先清理干净,避免后面命令冲突:
bash复制sudo apt remove docker docker-engine docker.io containerd runc
这一条不会删除你的镜像和容器数据,只是卸载程序本体。镜像默认存放在 /var/lib/docker,如果之后想彻底清空再单独删目录。
4.2 添加Docker官方GPG密钥和apt仓库
很多教程推荐直接用 get.docker.com 的脚本一键安装,好处是省事,坏处是版本不可控,脚本更新也快到你不知道它实际做了什么。我更推荐手动添加apt仓库的方式,升级方便,路径清楚:
bash复制sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
然后添加仓库。注意下面这行的 $(. /etc/os-release && echo "$VERSION_CODENAME") 会自动读取成 Ubuntu 24.04 对应的 noble,不需要手写:
bash复制echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] 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 update 之后,如果看到 docker.list 对应的条目被正常读取,就说明仓库配置成功了。
4.3 安装docker-ce全家桶
这里我建议直接把整套工具链装齐,省得之后再补:
bash复制sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- docker-ce:Docker引擎本体,提供 docker daemon
- docker-ce-cli:docker命令行工具
- containerd.io:容器运行时,负责实际拉起容器
- docker-buildx-plugin:构建镜像的BuildKit插件
- docker-compose-plugin:Compose V2插件,后面编排服务会用到
装完后把服务设为开机自启并手动启动一次:
bash复制sudo systemctl enable --now docker
sudo systemctl status docker
看到 active (running) 就说明daemon已经起来了。验证一下版本:
bash复制docker version
Client和Server两端都有版本号输出,才算真正安装成功。如果只看到Client没有Server,多半是daemon没起来,先看 systemctl status docker 再往下排。
4.4 用hello-world镜像验证完整链路
bash复制sudo docker run hello-world
这步会从Docker Hub拉取一个不到10KB的测试镜像,并运行它。如果看到 "Hello from Docker!" 字样,说明pull、run、容器执行全链路都没问题。这同时也是第一次拉公网镜像,接下来就该处理镜像下载慢的问题了。
5. 镜像下载慢的解法:daemon.json换源一次配好
5.1 先搞清楚拉镜像的网络链路
镜像文件本质上是分层文件系统,从Docker Hub拉取时默认走海外节点,在国内经常出现几十KB/s的龟速,甚至超时中断。Docker为用户预留了 registry-mirrors 配置项,你可以把拉取请求先导到国内可访问的镜像仓库,同时保持 Docker Hub 的正常读写。
这个配置写在 /etc/docker/daemon.json 里,是Docker守护进程的核心配置文件。很多配置项都在这一个文件里集中管理,比如日志大小、存储驱动、数据根目录等。
5.2 写入镜像源配置并重启docker
新建或修改 /etc/docker/daemon.json:
bash复制sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://dockerproxy.com",
"https://docker.nju.edu.cn"
]
}
EOF
执行完一定要重载配置并重启docker,让daemon重新读取:
bash复制sudo systemctl daemon-reload
sudo systemctl restart docker
然后验证镜像源是否生效:
bash复制docker info | grep -A 2 "Registry Mirrors"
能看到你配置的那几个地址就是生效了。
提示:镜像源可用性变化比较快,某个源失效是常态,不要死守一个地址。配置多个源,docker会自动逐一尝试。如果你发现某个源完全拉不动,优先去对应镜像站官网看最新可用地址替换就行。
5.3 换源后的实测效果
配置之前,直接拉 mysql:8.0 可能耗时几分钟甚至超时;换源后我本地实测拉取 mysql:8.0 镜像,总共不到30秒。这个过程感受最明显的就是,之前 docker pull 卡在 "Waiting" 状态不动,现在进度条走得很稳。
注意,镜像源只影响 docker pull 的加速,不影响容器运行时的网络。容器内部访问外网还是走宿主机的网络栈,这是两回事,别混淆。
6. 告别sudo:docker组与权限错误的根因
6.1 为什么每次执行docker命令都要sudo
你在终端执行 docker ps 出现下面这个错误时,说明当前用户不在docker用户组里:
code复制docker: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: dial unix /var/run/docker.sock: connect: permission denied.
docker daemon 默认以root身份运行,它对外通信的socket文件 /var/run/docker.sock 属主是 root:docker。普通用户对这个socket没有读写权限,所以docker CLI连接不上。解决办法不是每次加sudo,而是把当前用户加入docker组。
6.2 加入docker组的完整操作
bash复制sudo usermod -aG docker $USER
执行完必须重新登录或执行 newgrp docker 刷新会话,否则当前终端不会生效。建议直接注销重登,一劳永逸:
bash复制newgrp docker
docker ps
能正常输出容器列表就说明权限搞定了。
6.3 一个必须说的风险:docker组等于root权限
docker组权限非常大,加入docker组的用户可以通过挂载宿主机目录到容器,然后利用容器里的root权限直接读写宿主机文件,本质上等同于root。所以在生产服务器上,不要随手把普通用户加进docker组。如果是个人开发机、测试环境,加进去没问题;如果有多用户共用的服务器,建议还是让运维统一管理权限,或者用sudo授权。
7. 十个最常用的Docker命令,串起一个Redis实例
7.1 镜像管理三件套:pull、images、rmi
bash复制docker pull redis:7
docker images
docker images 会列出本地所有镜像。如果看到 redis 镜像对应 132MB 左右,说明拉取成功。删除镜像用 docker rmi redis:7,但注意要先停掉依赖它的容器。
7.2 容器生命周期命令,用Redis实战走一遍
跑一个带持久化、端口映射、自动重启的Redis容器:
bash复制docker run -d \
--name redis7 \
-p 6379:6379 \
-v redis-data:/data \
--restart unless-stopped \
redis:7 --appendonly yes
这里每个参数都有讲究:
- -d:后台运行容器
- --name redis7:给容器起名字,之后所有操作都可以用名字代替容器ID
- -p 6379:6379:宿主机6379端口映射到容器6379端口,格式是"宿主机端口:容器端口"
- -v redis-data:/data:把名为redis-data的数据卷挂载到容器里的/data目录,持久化AOF和RDB文件。用卷而不是目录,好处是不用关心文件具体在哪,docker自动管理
- --restart unless-stopped:只要不是手动stop,容器退出后会自动重启,非常适合开机自启的服务
- --appendonly yes:这是传给容器内redis-server的启动参数,开启AOF持久化
容器跑起来后,用 docker ps 查看:
bash复制docker ps
能看到容器状态为 Up 且端口映射正常。
测试Redis是否可用,从宿主机执行:
bash复制redis-cli -h 127.0.0.1 -p 6379 ping
如果你宿主机没装redis-cli,也可以进入容器内部操作:
bash复制docker exec -it redis7 redis-cli
localhost:6379> set name docker
localhost:6379> get name
7.3 日志、进入容器、停止删除
bash复制docker logs -f redis7 # 实时查看日志
docker exec -it redis7 bash # 进入容器内部的shell
docker stop redis7 # 停止容器
docker start redis7 # 启动已停止的容器
docker restart redis7 # 重启容器
docker rm -f redis7 # 强制删除容器(会同时删掉容器数据,卷数据还在)
7.4 一键清理垃圾的prune命令
bash复制docker system prune -a
这个命令会把所有已停止的容器、无用网络、悬空镜像、构建缓存都清掉,非常解气。但如果你有大量临时容器还想留着,慎用。
8. Compose实战:一条命令拉起MySQL 8.0加Redis主从
8.1 为什么需要Compose
当服务多了以后,每次 docker run 一行命令可能要写几百字符,而且服务之间的启动顺序、网络连通性都得自己管。Compose的作用是把一组容器编排写进一个YAML文件,用一条 docker compose up -d 全部拉起。
Ubuntu 24上安装docker-ce全家桶时已经包含了 docker-compose-plugin,不需要单独安装python版的docker-compose。验证一下:
bash复制docker compose version
V2版本输出类似 Docker Compose version v2.27.1。
8.2 用Compose部署MySQL 8.0并初始化用户
热词里"docker安装mysql8.0并使用"是高频需求。新建一个目录,比如 mkdir mysql8 && cd mysql8,在里面创建 docker-compose.yml:
yaml复制services:
mysql8:
image: mysql:8.0
container_name: mysql8
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: app_pass
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql8_data:/var/lib/mysql
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
volumes:
mysql8_data:
启动:
bash复制docker compose up -d
等待镜像拉取并初始化,大约十几秒后查看:
bash复制docker ps
docker exec -it mysql8 mysql -uroot -p
输入密码 root123 后能进入MySQL命令行,说明容器正常。这里说一下为什么不用docker run直接写:Compose把配置固化下来,下次换台机器也能复现;万一容器被误删,一条命令就能恢复到完整环境。
验证一下部署的MySQL能用:
bash复制mysql> SHOW DATABASES;
mysql> USE app_db;
mysql> SELECT 1;
使用完毕后如果想彻底清掉容器和卷:
bash复制docker compose down -v
8.3 用Compose部署Redis主从
热词里还有 "docker安装redis主从"。主从配置的关键是让从节点知道主节点的地址,在Compose网络里,直接使用服务名 redis-master 即可。创建 redis-master-slave 目录,写入 docker-compose.yml:
yaml复制services:
redis-master:
image: redis:7
container_name: redis-master
restart: always
ports:
- "6379:6379"
volumes:
- master-data:/data
command: redis-server --appendonly yes
redis-slave:
image: redis:7
container_name: redis-slave
restart: always
depends_on:
- redis-master
ports:
- "6380:6379"
volumes:
- slave-data:/data
command: redis-server --replicaof redis-master 6379
volumes:
master-data:
slave-data:
启动:
bash复制docker compose up -d
验证主从关系。先进入从节点执行:
bash复制docker exec -it redis-slave redis-cli INFO replication
输出里 role:slave 并且 master_link_status:up 就说明主从已经连上了。再往主节点写数据,从节点能同步到:
bash复制docker exec -it redis-master redis-cli SET foo bar
docker exec -it redis-slave redis-cli GET foo
如果 GET 返回 bar,主从同步正常。
8.4 Compose的进阶注意点
Compose文件里,image 尽量指定精确tag,不要用 latest,否则升级后可能出现不兼容。volumes 声明用具名卷,容器删了数据还在;如果希望数据保留在当前项目目录,可以改成 ./data:/var/lib/mysql 这种bind mount形式。生产环境建议把 environment 里的密码改成通过宿主机环境变量或docker secret注入,不要硬编码进YAML。
9. 服务启动失败排错:从dockerd到容器退出的完整链路
9.1 排查原则:先daemon后容器,先系统后业务
遇到Docker相关问题,我习惯按这条链路走:
- docker命令本身是否存在
- daemon是否在运行
- 容器是否在运行
- 看daemon日志
- 看容器日志
以下三个高频坑按这个链路解决。
9.2 场景一:docker: command not found
执行 docker ps 提示找不到命令,说明docker根本没装上。多半是之前只在系统里安装了 docker.io,但没有 /usr/bin/docker 这个可执行文件的链接。解决方式回到第4章,完整装一遍 docker-ce docker-ce-cli 即可。
9.3 场景二:Failed to connect to the Docker daemon / docker.service一直starting
错误信息通常是:
code复制Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
先看daemon状态:
bash复制systemctl status docker
如果显示正在启动但一直卡住,或者 active (failed),抓日志:
bash复制journalctl -u docker -f --no-pager
我遇到过的情况包括:
- iptables版本冲突。Ubuntu 24默认使用nftables后端,如果之前装了老版本iptables,dockerd启动时会报错。解决: sudo update-alternatives --set iptables /usr/sbin/iptables-nft
- 存储驱动异常。如果系统磁盘满了,或者 /var/lib/docker 所在分区只读,dockerd也会启动失败。 df -h 看一眼磁盘,必要时 docker system prune 清理。
- 网络命名空间问题。在虚拟机或WSL环境里,桥接网络未初始化也可能让dockerd启动失败。可以先尝试完全重启 docker 服务,再不行就重启虚拟机/宿主机。
9.4 场景三:容器启动后立刻退出
这是新手最常见的"容器怎么秒退"问题。容器跟虚拟机不一样,它的生命周期由主进程决定:主进程退出,容器就退出。
比如你执行:
bash复制docker run -d ubuntu:24.04
Ubuntu镜像默认没有常驻进程,启动后shell立即退出,容器自然就 Exited (0) 了。你真正要看的是为什么要跑这个容器,以及它的主进程状态。排查方法:
bash复制docker ps -a # 找到容器的退出码
docker logs 容器名 # 看日志输出
如果是Exited (1),基本是应用启动失败,日志会给出具体原因。如果是Exited (137),多半是内存耗尽被系统杀掉,检查 --memory 限制或宿主机内存。如果是Exited (0),说明主进程主动结束,通常是镜像本身的CMD就是这样。
9.5 场景四:端口被占用
启动容器时报:
code复制Bind for 0.0.0.0:3306 failed: port is already allocated
说明宿主机的3306端口已经被其他进程或容器占用了。用 ss -lntp | grep 3306 看看是谁在占用,然后换一个宿主机端口映射,比如 -p 3307:3306,或者停掉占用端口的进程。
10. 进阶方向:用Dockerfile打包自己的微服务镜像
10.1 写一个最小的Dockerfile
跑到这里,你对容器的核心能力已经有概念了。下一步是把自己的项目也打包成镜像。
以PHP项目为例,一个最简单的Dockerfile:
dockerfile复制FROM php:8.2-apache
COPY . /var/www/html/
EXPOSE 80
构建:
bash复制docker build -t my-php-app:1.0 .
docker run -d --name php-app -p 8080:80 my-php-app:1.0
10.2 多阶段构建:把镜像从800MB降到100MB
部署微服务时,镜像体积直接影响拉取和启动速度。多阶段构建的思路是:第一阶段用完整的编译环境把二进制编译出来,第二阶段只拷贝编译产物到精简运行镜像。
以Go服务为例:
dockerfile复制FROM golang:1.22 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:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/app .
EXPOSE 8080
ENTRYPOINT ["./app"]
cgo关闭、静态编译,让最终镜像不依赖宿主机的glibc,alpine镜像本身只有几MB。最终打包出来的Go服务镜像通常不到20MB,而一个包含编译环境的镜像随随便便800MB以上。
10.3 部署微服务时的三个实用建议
第一,构建时尽量使用 .dockerignore 文件,把 .git、node_modules、target、pycache 这类目录排除掉,否则它们会被当作构建上下文发送给daemon,拖慢构建速度,甚至打爆镜像。
第二,环境变量通过 -e 或compose文件注入,不要写死在镜像里。镜像要保证不同环境可复用,密钥、数据库地址都应该是运行时配置。
第三,给服务加上健康检查。Compose文件里可以这样:
yaml复制services:
app:
image: my-app:1.0
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"]
interval: 30s
timeout: 5s
retries: 3
有了健康检查,编排平台也好、Compose自身也好,才能知道你服务是不是真的处于可用状态,而不是容器进程活着就万事大吉。
最后说点我自己的体会。Docker这种东西,会者不难,难的是环境问题——尤其在你刚换到Ubuntu 24、还没摸清楚新版apt源格式和deb822配置的时候。我自己踩过几次坑之后形成的习惯是:装Docker之前,先花两分钟确认系统版本和内核架构;装完之后第一件事不是跑Hello World,而是先配好镜像源;然后才谈Compose和Dockerfile。按这个顺序走,基本不会有意外。希望这篇教程能帮你少走几步弯路,接下来就靠你多跑几个容器、多写几个镜像,把手上这些命令变成肌肉记忆。
