在 Ubuntu 24.04 上配置 Docker,是我最近被问得最多的一件事。前阵子刚给一台全新安装的 Ubuntu 24.04 服务端做完 Docker 环境部署,顺手把 MySQL、Redis 这些常用中间件也全部容器化跑了几天,整体体验非常稳。这篇就把从零开始到实战部署的完整过程记录下来,包括安装方式怎么选、镜像加速怎么配、常用命令怎么用、MySQL 和 Redis 主从怎么搭,还有踩坑之后的排查思路。整个过程不需要你之前有任何容器经验,照着做就能完整跑通。
先说一句大实话:Ubuntu 24.04 的 Docker 安装难度极低,真正拉开差距的是后面那些细节——仓库选错会导致版本老旧,镜像不加速会让你拉到怀疑人生,数据卷不挂载一升级就丢数据。这篇文章的价值集中在后面几块,建议从头顺到尾,不要跳着看。
1. 动手之前:安装前的环境准备与几个底层概念
1.1 为什么选 Ubuntu 24.04 而不是旧版本
Ubuntu 24.04 属于长期支持版本(LTS),官方支持周期能到 2029 年,这一点对服务器环境尤其重要。系统自带的 Linux 内核是 6.8 版本,对容器相关的 cgroups、iptables、overlayfs 这些底层特性支持非常完整,跑 Docker 几乎不需要额外适配。
我早期在 Ubuntu 20.04 上做过同样的部署,那时还需要手动处理一些旧内核和 Docker 的兼容问题,尤其涉及 nftables 和 iptables 的切换时容易出幺蛾子。到了 24.04,这类问题基本消失,安装完就能直接用,对新人来说非常友好。
如果你准备做 AI 开发,Ubuntu 24.04 的另一个优势是显卡驱动和 CUDA 生态适配完整,Docker 做环境隔离,GPU 资源可以通过 nvidia-container-toolkit 透传进容器,宿主机本身还能保持干净。这个组合后面可以单独写一篇,但至少起点选对了。
1.2 先确认内核和系统基础环境
打开终端,先跑两条命令确认系统版本和内核:
bash复制lsb_release -a
uname -r
正常输出应该是 Ubuntu 24.04 LTS(Noble Numbat)和 6.8.0-xxxx-generic 之类的内核版本。如果内核版本太低,建议先升级系统再装 Docker。
另外确认系统时间是准确的。之前遇到过一台机器时间偏移严重,导致和镜像仓库通信时 TLS 证书校验失败,拉镜像直接报 x509: certificate has expired or is not yet valid,折腾了很久才定位到是系统时间问题。用 timedatectl 看一眼,必要时开启 NTP 同步。
1.3 先卸载旧版 Docker 包避免版本冲突
如果你不是全新系统,先检查一下有没有装过旧版 Docker。Ubuntu 自带的软件源里有一个 docker.io 包,但版本通常比较老。另外有些一键脚本会把 Docker 装到 /usr/local/bin,这些路径残留会导致后续命令优先级冲突。
bash复制sudo apt remove docker docker-engine docker.io containerd runc
执行完这步,再用 which docker 确认没有遗留的二进制文件。如果有,手动删掉或者备份都行。建议在干净环境下继续,避免装完之后出现"命令存在但版本不对"这种诡异问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 安装方式怎么选:官方仓库、apt自带还是离线包
2.1 三种安装方式对比
我把常见的三种安装路径整理成表格,方便你按场景选择:
| 安装方式 | 推荐度 | 适用场景 | 主要风险 |
|---|---|---|---|
| Docker 官方 apt 仓库 | 最推荐 | 绝大多数服务器/开发机 | 无 |
| Ubuntu 自带 docker.io | 不推荐 | 仅临时验证、不方便加源的环境 | 版本滞后,功能缺失 |
| 下载 deb 离线包 | 按需 | 内网、完全无外网环境 | 依赖关系要手动处理 |
apt install docker.io 这条命令虽然省事,但装出来的 Docker 版本通常落后官方好几个版本,一些新特性和安全修复都没法及时拿到。更关键的是,Ubuntu 自带的包往往不会配套最新的 docker compose 插件,后面写多容器编排时会比较被动。
官方仓库虽然多几步配置,但装出来的 docker-ce 是最稳定的生产级版本,建议直接一步到位。离线安装的 deb 包适合内网环境,但依赖链条复杂,非必要不建议折腾。
2.2 使用官方仓库安装的完整步骤
我这里给出完整命令。先安装依赖工具,并添加 Docker 的 GPG 密钥:
bash复制sudo apt update
sudo apt install -y ca-certificates curl gnupg
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 a+r /etc/apt/keyrings/docker.gpg
然后添加软件源。注意 Ubuntu 24.04 的代号是 noble,这个参数一定不能写错,写成旧版本代号会导致 apt 找不到对应的包:
bash复制echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
最后安装 Docker 引擎和配套插件:
bash复制sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
这里装的是五个组件:docker-ce 是主程序,docker-ce-cli 是命令行工具,containerd.io 是容器运行时,docker-buildx-plugin 用于增强镜像构建,docker-compose-plugin 提供 docker compose 编排能力。全都装好是最省心的组合,后续不用再回头补装。
2.3 启动服务并添加当前用户到 docker 组
装完之后立刻启动服务并设置开机自启:
bash复制sudo systemctl enable docker --now
执行 sudo docker version 确认客户端和服务端都正常。
到这里,用 sudo 已经能跑所有命令了。但每次敲命令都加 sudo 很烦,而且会带来一个隐患:镜像构建过程中如果目录权限不对,生成的产物会属于 root 用户,对后续部署造成干扰。正确做法是把当前用户加入 docker 用户组:
bash复制sudo usermod -aG docker $USER
然后退出终端重新登录,或者执行 newgrp docker 让用户组即时生效。之后直接跑 docker ps,不再需要 sudo 才说明配置成功。
注意:将用户加入 docker 用户组相当于授予了该用户 root 级别的权限,因为 Docker API 本身没有细粒度鉴权。单用户开发机无所谓,多人共享服务器时建议谨慎。
2.4 验证安装是否正常
运行最经典的测试容器:
bash复制docker run hello-world
正常情况会拉取镜像并打印一段说明文字。如果这段跑通了,说明整个 Docker 引擎、镜像拉取、容器执行链路都没问题,接下来可以进入实际使用环节。
3. 镜像下载慢的解决办法:镜像加速配置思路与操作
3.1 为什么会碰到镜像下载慢的问题
Docker 默认从官方镜像仓库拉取镜像,这个服务在国内访问速度极不稳定,拉一个上百兆的镜像可能要等很久,甚至中途超时失败。这不是你的本地网络问题,也不建议为此去调整全局网络设置,更稳妥的做法是通过 Docker 守护进程的配置文件,把镜像拉取请求转发到可用的公共镜像加速器。
Ubuntu 24.04 下 Docker 的配置文件路径是 /etc/docker/daemon.json,注册表镜像加速就在这个文件里配置。
3.2 daemon.json 配置文件写法
先创建或编辑配置文件:
bash复制sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
写入如下 JSON 格式内容:
json复制{
"registry-mirrors": ["https://your-registry-mirror.example.com"]
}
写完后保存退出,检查一下 JSON 格式是否正确,推荐用 python3 -m json.tool 或 jq 来校验,格式错误会导致 Docker 服务启动失败。确认无误后重启守护进程:
bash复制sudo systemctl daemon-reload
sudo systemctl restart docker
3.3 配置生效验证与常见误区
重启后用 docker info 查看 Registry Mirrors 一栏:
bash复制docker info | grep -A 5 "Registry Mirrors"
能看到刚才配置的镜像地址就说明生效了。这时候再 docker pull 一个大镜像,速度通常会有明显改善。
需要提醒的是:镜像加速只对 docker pull 这类拉取操作有效,不影响构建镜像、登录私有仓库这些场景。加速地址选择上没有统一标准,我测试过不同地址效果差异很大,和网络环境有关,建议自己多试一两个,选速度稳定的用。另外镜像加速不稳定、失效也很常见,遇到推送失败或者 404 错误,优先检查加速地址状态。
4. Docker核心命令实战:镜像、容器、日志一网打尽
4.1 镜像管理常用命令
学好 Docker 最重要的是建立"镜像和容器是两个不同实体"的心智模型。镜像是只读模板,类似安装包;容器是镜像的运行实例,类似正在运行的程序。
常用的镜像操作:
bash复制# 搜索远程镜像
docker search mysql
# 拉取镜像,默认拉 latest 标签
docker pull mysql:8.0
# 查看本机镜像列表
docker images
# 删除镜像
docker rmi mysql:8.0
# 用 Dockerfile 构建镜像
docker build -t myapp:v1.0 .
拉取镜像时建议始终指定具体版本号,不要依赖 latest。latest 是一个会移动的标签,今天拉的和下个月拉的可能不是同一个版本。生产环境里不锁定版本,后面升级时就容易"惊喜"不断。
4.2 容器生命周期操作
容器是镜像的运行实例,生命周期操作是使用频率最高的命令集合:
bash复制# 以后台模式运行容器
docker run -d --name mynginx -p 8080:80 nginx:stable
# 查看正在运行的容器
docker ps
# 查看所有容器,包括已停止的
docker ps -a
# 停止/启动/重启容器
docker stop mynginx
docker start mynginx
docker restart mynginx
# 删除容器,先停止再删除
docker rm mynginx
# 强制删除容器
docker rm -f mynginx
docker run 里的 -d 表示后台运行,--name 给容器起名字,-p 8080:80 把宿主机的 8080 端口映射到容器内部的 80 端口。这里第一个端口是外部访问用的,第二个是容器内服务监听的端口,两个写反是最常见的低级错误。
4.3 进入容器内部与查看日志
需要调试容器内部环境时,用 exec 进入正在运行的容器:
bash复制# 进入容器的 bash shell
docker exec -it mynginx bash
# 以 root 用户进入容器
docker exec -it -u root mynginx bash
# 在容器内部直接执行命令
docker exec mynginx ls /etc/nginx
-i 是保持标准输入打开,-t 是分配一个伪终端,两个参数一起用才能获得交互式终端。如果容器里没有 bash(一些精简镜像只有 sh),可以改用 sh。
查看日志也是高频操作:
bash复制# 查看全部日志
docker logs mynginx
# 实时跟踪日志
docker logs -f mynginx
# 只看最后 100 行
docker logs --tail 100 mynginx
排查异常时我习惯先 docker logs 看应用日志,再看 docker inspect 看容器配置和环境变量,最后才考虑进容器手工检查。日志是应用的第一现场,进容器到处翻文件反而是低效路径。
5. 实战案例一:用 Docker 部署 MySQL 8.0 并完成数据持久化
5.1 数据目录规划和运行参数解析
第一个实战案例选 MySQL,因为这是最能体现 Docker 优势也最能暴露问题的场景。没有数据卷的 MySQL 容器,一旦容器被删除,所有数据都会随之消失;而挂载了宿主机目录之后,容器随便删,数据安然无恙。
先创建数据目录:
bash复制sudo mkdir -p /opt/mysql/{data,conf,logs}
sudo chown -R 1000:1000 /opt/mysql
chown 1000:1000 这步很多人会漏掉。MySQL 官方镜像内部使用 mysql 用户运行,其 UID 是 1000,宿主机上的挂载目录如果不属于这个权限位,容器创建表时会报权限错误。给目录授权到用户组并不代表授权给容器用户,这是初学者最容易踩的坑。
然后拉取 MySQL 8.0 镜像并启动容器:
bash复制docker pull mysql:8.0
启动命令带了一组参数,逐个解释:
bash复制docker run -d \
--name mysql8 \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=YourPassword123 \
-e TZ=Asia/Shanghai \
-v /opt/mysql/data:/var/lib/mysql \
-v /opt/mysql/conf:/etc/mysql/conf.d \
-v /opt/mysql/logs:/var/log/mysql \
--restart=always \
mysql:8.0
-e MYSQL_ROOT_PASSWORD 是第一次初始化数据库时设置 root 密码的环境变量,只在数据目录为空时生效。如果后来发现密码不对想重置,光改这个环境变量没用,需要重命名数据目录重新初始化,或者通过其他方式改密码。--restart=always 让容器在宿主重启或容器崩溃时自动拉起,这个参数对生产环境几乎是标配。
5.2 验证连接与外部客户端访问
启动后先确认容器状态:
bash复制docker ps | grep mysql8
然后进入容器内部验证 MySQL 连接:
bash复制docker exec -it mysql8 mysql -uroot -p
输密码后能进入 MySQL 命令行就说明服务正常。此时可以顺手建一个测试库:
sql复制CREATE DATABASE testdb;
SHOW DATABASES;
从宿主机连接时,如果知道 root 密码也没用,因为 MySQL 8.0 默认的 root 用户只允许 localhost 登录。需要额外创建一个远程访问账号:
sql复制CREATE USER 'appuser'@'%' IDENTIFIED BY 'AppUser123';
GRANT ALL PRIVILEGES ON testdb.* TO 'appuser'@'%';
FLUSH PRIVILEGES;
然后用宿主机上的客户端工具测试:
bash复制mysql -h 127.0.0.1 -P 3306 -u appuser -p
能连上就说明端口映射和账号权限都没问题。用可视化工具连接时的服务器地址是宿主机的 IP,端口是 -p 参数里左边的那个。
5.3 数据持久化验证、备份与清理思路
验证数据持久化最直接的方式:往数据库里写一条数据,然后删除容器,重新用相同参数创建容器,数据应该还在。
bash复制docker rm -f mysql8
docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD=YourPassword123 -e TZ=Asia/Shanghai -v /opt/mysql/data:/var/lib/mysql -v /opt/mysql/conf:/etc/mysql/conf.d -v /opt/mysql/logs:/var/log/mysql --restart=always mysql:8.0
docker exec -it mysql8 mysql -uroot -p -e "SHOW DATABASES;"
看到 testdb 还在,说明数据持久化成功。
备份使用官方提供的 mysqldump,可以直接在容器内执行并重定向到宿主机:
bash复制docker exec mysql8 mysqldump -uroot -pYourPassword123 testdb > /opt/backup/testdb_$(date +%F).sql
定期备份是个好习惯,容器化之后反而比物理机更方便,因为数据都在明确定义的目录里,备份脚本的编写和维护都变得很直接。
6. 实战案例二:Redis 主从、青龙容器与 Compose 编排
6.1 用 docker compose 搭建 Redis 一主一从
Redis 主从部署是容器编排的经典入门场景。两个容器之间需要网络通信,直接 docker run 会比较啰嗦,用 Compose 可以一次性拉起整个服务组。
先创建一个工作目录:
bash复制mkdir -p ~/redis-cluster && cd ~/redis-cluster
然后新建 docker-compose.yml:
yaml复制services:
redis-master:
image: redis:7
container_name: redis-master
ports:
- "6379:6379"
volumes:
- ./master-data:/data
command: redis-server --appendonly yes
redis-slave:
image: redis:7
container_name: redis-slave
depends_on:
- redis-master
ports:
- "6380:6379"
volumes:
- ./slave-data:/data
command: redis-server --appendonly yes --slaveof redis-master 6379
启动整个集群:
bash复制docker compose up -d
用 docker ps 查看两个容器状态,然后验证主从关系:
bash复制docker exec redis-master redis-cli info replication
输出里的 connected_slaves:1 说明从节点连接成功。再验证从节点数据同步能力:在主节点写入数据,在从节点读取。
bash复制docker exec redis-master redis-cli set foo bar
docker exec redis-slave redis-cli get foo
能读到 "bar" 就说明主从同步生效。这里用到了 Compose 的核心能力:同一个 Compose 文件定义的容器默认共享一个网络,通过服务名可以直接相互访问,不需要额外建网桥。
关于数据卷,./master-data:/data 会创建 master-data 目录存放 Redis 的持久化文件。如果哪天数据丢了,优先检查持久化配置(--appendonly yes)和挂载目录权限。
6.2 青龙容器安装与依赖管理
青龙面板是目前很常见的定时任务管理工具,很多人在 Dart、Python、JavaScript 脚本场景下用它做任务调度,容器化部署同样很简单:
bash复制mkdir -p /opt/qinglong && cd /opt/qinglong
docker run -d \
--name qinglong \
-p 5700:5700 \
-v /opt/qinglong/config:/ql/config \
-v /opt/qinglong/logs:/ql/log \
-v /opt/qinglong/data:/ql/data \
--restart=always \
whyour/qinglong:latest
浏览器访问 http://宿主机IP:5700 即可打开面板。
依赖管理是青龙使用中容易踩坑的环节。很多脚本依赖 Python 包、Node 包或 Linux 系统库,如果直接装在日常系统里,会导致环境混乱。正确做法是直接进入容器内部安装依赖,或者把依赖声明写在容器启动脚本里。
bash复制docker exec -it qinglong bash -c "pip3 install requests"
docker exec -it qinglong bash -c "npm install -g axios"
容器重启后这些依赖仍然会保留在容器可写层中,但一旦容器被删除重建,这些依赖就会跟着消失。这就是为什么很多人重装完青龙后发现脚本报错——因为依赖没有固化到镜像或挂载目录里。要解决这个问题,更好的做法是编写自定义 Dockerfile,把依赖写进镜像构建阶段,或者把依赖目录挂载出来。
6.3 容器自动重启与实际运行时扩展思路
--restart=always 是生产环境几乎必选的配置,无论是容器崩溃、服务器重启,Docker 都会自动拉起容器。
bash复制docker update --restart=always qinglong
对于已经存在的容器可以这样动态更新重启策略。如果希望容器随 Docker 服务启动而自动拉起,这个参数已经是默认行为。
实际使用中,我会把所有业务组件的 Compose 文件统一放到 /opt/compose 目录,按项目分子目录管理。这样无论是升级、备份还是迁移到新服务器,都只需要复制 Compose 文件和挂载数据目录,比逐个 docker run 管理容器要省心得多。
7. 常见问题与排查技巧实录
7.1 报 "Cannot connect to the Docker daemon" 怎么办
这个报错是我见过频率最高的错误,几乎每天都能在群里看到一次。它不是一个具体故障,而是一类问题的统称,需要分情况排查。
第一种,Docker 服务没启动:
bash复制sudo systemctl status docker
如果显示未启动,直接启动并设置自启:
bash复制sudo systemctl enable docker --now
第二种,当前用户没有加入 docker 用户组,或者加入后没有重新登录。执行 id 看当前用户是否在 docker 组里,不在就执行 sudo usermod -aG docker $USER 然后退出重登。
第三种,服务启动失败,通过查看服务日志定位:
bash复制sudo journalctl -u docker --no-pager -n 50
sudo systemctl status docker
常见原因是中文输入法修改系统环境变量导致挂载异常,以及 /etc/docker/daemon.json 格式错误。我在新装的 Ubuntu 24.04 上遇到过 daemon.json 用记事本编辑后带 BOM 头导致解析失败的场景,用 sudo dos2unix /etc/docker/daemon.json 处理后恢复正常。
7.2 端口被占用导致容器启动失败
启动容器时提示 bind: address already in use,说明宿主机端口被占用了。用 ss -lntp | grep <端口> 找出占用进程,或者换个映射端口:
bash复制docker run -d --name mysql8 -p 3307:3306 ...
把宿主机端口从 3306 改为 3307,容器内依然监听 3306,外部用 3307 访问。这是一个经常被卡住的点:修改映射端口不影响容器内部服务端口,只改变外部访问入口。
还有一种更隐蔽的端口问题:多个容器实例想同时绑定同一个宿主机端口时,也会报同样的错误,即使容器内部端口不同。比如同时部署两个 Redis 实例,都要映射 6379,就要把第二个映射为 6380:6379。
7.3 MySQL/Redis 容器无法启动时的排查顺序
容器启动后频繁退出,先用 docker logs 看日志:
bash复制docker logs mysql8
常见原因前三名是:
一是数据卷权限问题。目录的属主 UID 与容器内用户 UID 不匹配,MySQL 报 [ERROR] chown: operation not permitted,解决办法是 chown -R 1000:1000 /opt/mysql/data。
二是持久化配置与数据文件不匹配。比如先用 redis-server 启动了一次容器,之后再用 --appendonly yes 启动第二次,会出现数据目录里既存在 RDB 文件又需要加载 AOF 文件的情况,Redis 会直接报错退出。此时需要清空数据目录重新初始化,或停掉持久化选项。
三是系统内核参数与容器需求冲突,常见于 MySQL 大量并发连接时触发 fatal error: pthread_create failed。如果只是在测试阶段,可以先降低连接数来绕开。
7.4 Docker 彻底卸载与重装的方法
如果你装坏了需要彻底清理重装,按这个顺序执行:
bash复制sudo systemctl stop docker
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker
sudo rm -rf /var/lib/containerd
最后一步删除 /var/lib/docker 是彻底清理容器数据和镜像的关键。如果确定不再使用旧数据再执行,这一步不可逆。
如果你只是想保留镜像数据,只卸载程序不删 /var/lib/docker,重装后镜像和容器都还在,只是容器需要重新启动。这一点对很多人是有用的:升级 Docker 版本时,数据目录千万不能动。
写在最后的一些体会
我在 Ubuntu 24.04 上跑 Docker 这段时间,最大的感受是:相比几年前,容器化这件事的门槛已经低了很多,大部分基础操作都能靠官方文档和社区经验解决,真正影响效率的往往是一些小习惯。
比如我习惯把每个容器的数据目录统一放在 /opt 下按应用命名,容器名也固定带后缀,避免后面多了记不清谁的日志是谁的;还有所有需要持久化的服务一律先把挂载目录和权限准备好再启动容器,绝不先起容器再补目录,因为补挂载只能在删除容器重建时完成,多绕一次弯路。
最后再分享一个非常实用的小技巧:把常用的 Docker 命令写成 shell 别名,比如 alias dps='docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"',查看运行状态时一眼就能看清所有容器的端口映射和健康状态,比默认输出的体验好太多。这些小改动叠加起来,日常运维会轻松很多。
如果后续你还想深入,建议把 Dockerfile 编写、镜像构建优化、多容器编排这几个方向作为下一阶段重点。容器的核心价值不只是"跑起来",而是把整个环境定义成代码,随时能复制、迁移和回滚,这条路越往后走越会觉得值得。
