Ubuntu下Docker安装与实战:从环境准备到容器部署全指南

发布时间:2026/10/10 3:22:30
Ubuntu下Docker安装与实战:从环境准备到容器部署全指南
1. 环境准备与整体思路拆解1.1 先搞清楚你要装的是什么在 Ubuntu 上装 Docker第一件事不是敲命令而是想清楚你需要的到底是哪个东西。这一点很容易被忽略但实际踩坑的人不在少数。Docker 官网现在分成两条线一条是 Docker Engine也就是我们常说的 docker 服务端加命令行工具适合在服务器上跑容器另一条是 Docker Desktop带图形界面、内置虚拟机主要面向桌面用户和 macOS、Windows 场景。Ubuntu 台式机装 Desktop 当然可以但如果你的目标是跑 MySQL、Redis、青龙面板这类服务Engine 反而更干净、更省资源。我用的是 Ubuntu 22.04 LTS 和 24.04 LTS 都实测过下面的步骤两条版本线基本通用只有少数细节差异。整体思路可以分成四步先把系统里可能残留的老版本 Docker 清干净再通过官方 apt 源安装 Docker Engine接着配置镜像加速和开机自启最后用一个测试容器验证环境可用。这个顺序看着简单但每一步都有讲究尤其不能上来就apt install docker.io——那是 Ubuntu 软件源里打包的旧版 Docker版本落后不说和官方源的升级路径也不同后面维护起来很别扭。我在实际工作中坚持用官方 apt 源而不是 Docker 的脚本安装原因有两个。第一apt 源的方式能让你之后用apt upgrade统一管理 Docker 的升级不用手动去下载 deb 包。第二Docker 官方提供的get.docker.com脚本在国内网络环境下经常下载超时而配置好的 apt 源往往能利用你本地的镜像加速稳定性好得多。1.2 版本选择和内核依赖的坑Docker 对内核版本有要求但现行版本的 Docker Engine 已经把这个门槛降得很低了。Ubuntu 18.04 以上的系统只要内核不低于 4.15基本都能跑。你可以在终端里用uname -r看一眼内核版本如果显示的是 5.15 或 6.8 这类数字那就完全没问题。真正需要关心的是两个底层组件iptables和overlayfs。Docker 的默认网络模式 bridge 依赖 iptables 做 NAT 转发而默认存储驱动 overlay2 依赖内核的 overlay 模块。Ubuntu 内核默认都带了这两个功能所以你不需要额外装东西但如果你的系统是精简版或是自己编译的内核就要注意检查一下。检查方式很简单ls /proc/filesystems | grep overlay sudo iptables -L -n第一条命令能看到 overlay 文件系统支持第二条命令能确认 iptables 规则可以正常列出。如果发现 iptables 报错大概率是系统里没装 iptables 或者被其他工具接管了后面创建容器的时候会莫名其妙连不上网到时候排查很费劲。另外Ubuntu 24.04 默认用了ufw防火墙如果你开启了 ufw需要额外放行 Docker 的网段否则容器外部访问会出现问题。这个细节我放在后面的常见问题章节再展开。2. 从零到一Ubuntu 安装 Docker 的完整实操2.1 清理旧版本与安装依赖包不管你是全新系统还是之前装过别的容器工具先做一遍清理总没错。很多人在升级或重装时遇到“docker 命令存在但无法启动”的怪问题八成就是新旧版本混在一起了。执行下面这些命令sudo apt update sudo apt remove docker docker-engine docker.io containerd runc注意这个卸载操作不会删除你已经下载的镜像和容器数据它们默认存放在/var/lib/docker目录下。如果你确定旧数据也不想要了可以把目录一并删掉sudo rm -rf /var/lib/docker然后安装 Docker 依赖的证书工具和软件源管理工具sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release这些包的作用分别是让 apt 支持 HTTPS 下载、提供 CA 证书校验、下载 Docker 的 GPG 密钥、生成系统版本代号。每个都有明确用途缺一个后面步骤都可能报错。比如没有lsb-release你就无法准确获取 Ubuntu 的版本代号写进源里的地址就会是错的。2.2 添加 Docker 官方 GPG 密钥和 apt 源这一步是全网教程里差异最大的地方因为涉及网络环境。Docker 官方的 GPG 密钥地址是https://download.docker.com/linux/ubuntu/gpg在某些网络条件下会连接失败。我的建议是优先用官方地址失败了再换镜像源。添加密钥的命令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这里解释一下为什么需要这一堆操作。Docker 的 apt 源是带签名校验的apt 在拉取软件包索引时会校验 Release 文件签名而这个签名就是由这个 GPG 密钥验证的。--dearmor参数将 ASC 格式的密钥转成二进制格式apt 只认这种格式。chmod ar是确保所有用户都能读取密钥文件否则后面apt update可能报权限错误。接下来把 Docker 源写入 apt 的 sources.list.d 目录。Ubuntu 22.04 和 24.04 的版本代号分别是 jammy 和 noble下面的命令会自动识别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这条命令乍一看有点长其实拆开很清晰$(dpkg --print-architecture)自动填充 amd64 或 arm64$(lsb_release -cs)自动填充 jammy 或 noble。如果你在终端里手动执行时不放心可以先跑一遍lsb_release -cs确认输出值防止系统版本识别错误导致源地址失效。2.3 安装 Docker Engine、CLI 和 Containerd源配置好之后记得把软件包索引更新一遍让 apt 识别到新加的 Docker 源sudo apt update如果你在apt update时看到404 Not Found或者The repository ... does not have a Release file的报错多半是源地址里的版本代号不对或者是系统版本太老已经停止维护。比如 Ubuntu 21.10 这类非 LTS 版本Docker 官方源可能已经不再提供对应仓库。解决办法是把源里的代号改成相邻的 LTS 版本代号比如 impish 改成 jammy具体可以看系统提示。然后安装 Dockersudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这里我建议把docker-buildx-plugin和docker-compose-plugin一起装上。前者是构建多平台镜像的插件后者是 Docker Compose v2 插件版可以直接用docker compose命令而不用去装独立的二进制文件。青龙面板、MySQL 主从这类多容器项目后面基本都会用到 Compose提前装好能省不少事。安装完成后先不要急着用按顺序执行这三条命令验证和启动sudo systemctl enable docker sudo systemctl start docker sudo docker versionenable是设置开机自启start是立即启动服务。docker version能看到 Client 和 Server 两部分信息如果都有版本号输出说明安装成功且 daemon 正常响应了。如果 Server 部分出现Cannot connect to the Docker daemon说明服务没起来先去查systemctl status docker。2.4 免 sudo 使用 Docker 的正确姿势安装完成后的第一件事我建议你立刻配置用户组。默认情况下docker 命令需要 root 权限每次敲sudo docker ...不仅麻烦还可能引发文件权限问题——比如你在宿主机挂载目录时容器里创建的文件属主会变成 root后续清理很痛苦。正确操作是把自己加入 docker 用户组sudo groupadd docker sudo usermod -aG docker $USER然后注销并重新登录或者直接重启一次。很多人在这步之后发现依然报permission denied while trying to connect to the Docker daemon socket就是因为没重新登录当前会话的组成员身份没有刷新。如果你不想退出登录可以用这条命令强制刷新newgrp dockernewgrp会开启一个子 shell子 shell 里的进程会带上新增的组权限。测试免 sudo 是否生效直接敲docker ps如果列出了空表而不是报权限错误就说明配置成功了。3. 镜像加速与下载慢的应对方案3.1 为什么要配置 registry mirror热词里“docker镜像下载慢”出现了好几次这确实是国内环境绕不开的问题。Docker 默认从 Docker Hub 拉镜像而 Docker Hub 的 CDN 节点在海外国内直连速度经常只有几十 KB/s拉一个几百 MB 的镜像能等半小时。解决办法是配置 registry mirror也就是镜像加速器。原理其实不复杂Docker daemon 在拉取镜像时会先访问你配置的镜像仓库如果镜像仓库里缓存了对应镜像它就从本地或就近的节点下载速度可以提升几十倍。你可以在/etc/docker/daemon.json里配置多个镜像地址Docker 会按顺序尝试。3.2 配置 daemon.json 的方法与注意事项编辑或创建/etc/docker/daemon.json加入以下内容{ registry-mirrors: [ https://docker.mirrors.ustc.edu.cn, https://hub-mirror.c.163.com ] }不同镜像加速器的可用性和速度会有变化我上面给的是我测试过相对稳定的两个。也可以根据自己所在的网络环境调整比如某些云服务器厂商会提供专属的内网镜像加速地址速度优于公共节点。配置完成后重启 Dockersudo systemctl daemon-reload sudo systemctl restart docker然后跑一次docker info在输出里找到Registry Mirrors字段如果列出了你配置的地址说明加载成功。这里有两个细节要提醒。第一daemon.json必须是合法的 JSON 格式少一个逗号、多一个花括号都会导致 Docker daemon 启动失败。写完建议先验证一下python3 -m json.tool /etc/daemon.json第二镜像加速器只对 Docker Hub 官方仓库的镜像有效也就是docker pull nginx、docker pull mysql这类不带自定义仓库前缀的镜像。如果你拉的是quay.io、ghcr.io上的镜像或者自己私有仓库里的镜像加速器是不生效的。遇到这种情况要么直接指定仓库地址要么换网络环境。配置加速后实际的下载速度提升非常明显。一个 Ubuntu 镜像大约 30MB 左右加速前可能要等一两分钟加速后几乎是秒拉。如果你要部署的是带多个依赖的镜像比如某些应用镜像动辄 1GB这步操作节省的时间就很可观了。3.3 给容器配置网络代理还有一种情况镜像加速器配了但下载依然不稳定——比如你在拉取一些需要访问海外资源的镜像时Docker 构建过程中的apt install或pip install可能依然很慢。这种场景下需要给 Docker daemon 配置 HTTP 代理。方法是在/etc/systemd/system/docker.service.d/目录下创建配置文件sudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf EOF [Service] EnvironmentHTTP_PROXYhttp://your-proxy-ip:port EnvironmentHTTPS_PROXYhttp://your-proxy-ip:port EOF修改后同样需要daemon-reload和重启 Docker。这里我不展开讨论代理服务器的部署细节只是提醒你有这个方案存在。实际使用中如果你发现自己拉镜像偶尔报EOF或连接重置可以先观察一下是不是代理失效导致的而不要盲目认为是 Docker 本身的问题。4. 容器运行实战与权限问题排查4.1 跑通第一个 Nginx 容器安装和加速都搞定后我建议先用一个简单的容器验证整个链路。Nginx 是最适合做冒烟测试的镜像体积小、启动快、几乎不会有兼容问题。docker run -d --name web-test -p 8080:80 nginx:alpine这条命令的含义拆开来理解-d是后台运行--name web-test给容器起名-p 8080:80把宿主机的 8080 端口映射到容器的 80 端口nginx:alpine指定镜像和标签。执行后访问http://你的服务器IP:8080能看到 Nginx 默认欢迎页就算成功。如果页面打不开先检查容器状态docker ps -a看到一个Exited状态的容器就去看日志docker logs web-testNginx 一般是端口被占用或者宿主机防火墙没放行。Ubuntu 下如果开了 ufw默认会阻止外部访问 8080 端口需要添加规则sudo ufw allow 8080/tcp这里要留意一个 Docker 和 ufw 交互的经典坑即使你把端口 publish 到宿主机ufw 的规则也可能不生效因为 Docker 会直接修改 iptables 的 NAT 表绕过 ufw 的过滤链。结果是容器端口对外暴露无论你 ufw 怎么设置。这个问题在 Docker 20.10 之后可以通过iptables: false配置规避但那样会同时影响容器间网络我不建议为了防火墙管理方便而这么做。如果你的服务器有安全要求更好的做法是在云厂商的安全组层面控制端口访问。4.2 MySQL 容器与数据持久化Nginx 验证通过后再看一个真实生产场景跑 MySQL。热词里“docker安装mysql”、“docker安装mysql8.0并使用”出现频率很高这里展开讲讲。MySQL 8.0 镜像拉取docker pull mysql:8.0注意MySQL 官方镜像有一个特点容器停止即数据丢失。如果直接docker run mysql不挂载数据目录容器一旦删除库表全没了。正确做法是把数据目录挂载到宿主机sudo mkdir -p /opt/mysql-data docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDYourStrongPass \ -e MYSQL_DATABASEtestdb \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0-v /opt/mysql-data:/var/lib/mysql的含义是将宿主机的目录映射到容器内 MySQL 存储数据的路径。这样容器怎么删都没关系数据还在宿主机上重新跑一个容器挂载同一个目录就能恢复。这里有一个实际操作中的经验点MySQL 容器首次启动时如果挂载目录不是空的会导致初始化失败。具体表现是容器反复重启日志里报[ERROR] [MY-010457]之类的错误。原因是你挂载的宿主机目录里恰好有隐藏文件或者目录权限不对mysqld判断这个目录已经有数据就不再初始化。解决方法是换一个空目录或者确认目录属主是 999MySQL 容器内默认用户 UID。4.3 Redis 主从部署演示热词里还有“docker安装redis主从”这里顺带演示一下 Compose 的用法。Redis 主从部署如果手动跑容器要敲好几条docker run用 Compose 一次性解决明显更清晰。新建一个项目目录并写入配置文件mkdir -p ~/redis-cluster cd ~/redis-cluster创建docker-compose.ymlservices: redis-master: image: redis:7-alpine container_name: redis-master command: redis-server --requirepass 123456 ports: - 6379:6379 volumes: - ./master-data:/data redis-slave: image: redis:7-alpine container_name: redis-slave command: redis-server --slaveof redis-master 6379 --masterauth 123456 ports: - 6380:6379 depends_on: - redis-master volumes: - ./slave-data:/data然后一条命令拉起整个集群docker compose up -d用docker compose ps查看状态两个容器都Up就算成功。验证主从同步进入主节点写入数据docker exec -it redis-master redis-cli -a 123456执行set name test后退出再进从节点读取docker exec -it redis-slave redis-cli -p 6379 -a 123456能读到test说明主从同步正常。这里要注意一个细节Redis 7 的slaveof命令虽然在主从模式下还可以用但官方已经不建议在运行期使用 CONFIG SET 动态修改因为新版趋势是推崇副本模式下的REPLICAOF。不过作为启动参数传入slaveof在 Redis 7 里依然是有效的我这里用它是为了兼容更多教程习惯实际你换成replicaof效果一样。4.4 青龙面板依赖管理的容器化实践青龙面板在热词里也出现了它本身就是一个典型的容器化应用通过 Docker 部署非常合适。青龙面板的依赖管理经常出问题这其实和容器环境关系很大。容器内默认是精简版的 Linux很多 Python 包编译依赖比如 gcc、python3-dev都没装你在面板后台安装依赖时就会因为缺编译工具而失败。解决方案是在容器创建时挂载一个包含预装依赖的层或者在宿主机上用 Dockerfile 自定义扩展镜像。我常用的方式是这样的docker run -d \ --name qinglong \ -p 5700:5700 \ -v ~/ql/data:/ql/data \ whyour/qinglong:latest进入容器后手动安装编译工具docker exec -it qinglong bash apt update apt install -y gcc python3-dev安装完再重启容器青龙面板后台的依赖管理就会稳定很多。如果你的主程序对 Python 版本有特殊要求还可以用pyenv在容器内管理多版本但那样偏折腾我一般只在极端场景用。4.5 宿主机与容器的时间同步还有一个经常被忽略的问题容器默认的时区是 UTC和宿主机差 8 小时。你在容器里看日志、定时任务触发时间全对不上。解决方式是在创建容器时指定时区环境变量docker run -d \ --name app \ -e TZAsia/Shanghai \ your-image如果你已经跑了一堆容器不想重新创建可以在容器运行期间直接改配置文件然后重启容器但最好还是从一开始就养成加TZ参数的习惯。青龙面板、数据库这类对时间敏感的应用尤其要注意。5. 常见问题速查与避坑经验5.1 最容易踩的六个坑把我在实际操作中遇到的问题整理成一个速查表按出现频率排个序方便你对着排查问题现象根本原因解决方案Cannot connect to the Docker daemondocker 服务未启动systemctl start docker并systemctl enable dockerpermission denied while trying to connect to the Docker daemon socket用户不在 docker 组sudo usermod -aG docker $USER并重新登录apt update报 404 或找不到 Release 文件Ubuntu 版本代号错误或 apt 源失效检查/etc/apt/sources.list.d/docker.list中的版本代号并修正镜像拉取极慢未配置 registry mirror 或加速器失效修改daemon.json配置多个加速地址并重启容器启动后立即退出启动命令错误或挂载目录有数据用docker logs 容器名查看具体报错容器内时间比宿主机快 8 小时容器默认 UTC 时区创建容器时加-e TZAsia/Shanghai这六个问题覆盖面很广你按照表格里的解决方案逐一排查能解决 90% 以上的安装和使用问题。5.2 关于 Docker 数据管理的一些心得Docker 方便但数据管理一直是新手最容易翻车的地方。我的经验总结下来就几条第一所有需要持久化的数据必须挂载到宿主机。数据库、配置文件、日志目录都要通过-v参数或 Compose 里的volumes挂载出来。不要让数据只存在于容器内否则容器一删数据全完。第二镜像和容器要定期清理。Docker 用久了docker images里会堆很多悬空镜像none标签占用磁盘。定期用docker system prune清理一次能释放大量空间。这个命令会删除所有停止的容器、无用网络和悬空镜像执行前确认你的容器不需要保留。第三容器网络要规划。简单场景用默认 bridge 网络完全够但如果你要部署微服务或者多容器互联比如 MySQL 容器里配置了连接其他容器的地址建议创建一个自定义网络docker network create mynet然后在docker run时加--network mynet。自定义网络的容器之间可以用容器名互相访问不用记 IP。第四升级 Docker 有风险。apt upgrade docker-ce升级后daemon 会重启如果容器没有配置restart: always升级期间容器就停了。对于生产环境我建议在升级前先docker ps记录容器列表或者干脆用 Compose 管理所有服务升级后一条docker compose up -d全部拉起。5.3 排查日志的思路日志永远是排查容器问题的第一入口。我的习惯是容器出问题先看docker logs再看配置文件最后才考虑重装。很多时候问题根本不复杂比如 MySQL 连不上日志里清清楚楚写着Access denied for user那就是账号密码问题和 Docker 本身无关。看日志有两个重要参数docker logs --tail 100 容器名 docker logs -f 容器名--tail 100只显示最后 100 行避免大量无用刷屏-f是持续跟踪新输出适合观察启动过程中的实时日志。日志定位之后如果是配置文件层面的问题可以用docker inspect查看容器当前的详细配置包括挂载路径、环境变量、网络模式这些信息对判断问题很有帮助。6. 容器网络与端口的进阶理解6.1 端口映射的本质很多人用-p 8080:80时只知其然不知其所以然。其实 Docker 的端口映射本质上是 iptables 的 DNAT 规则宿主机收到目标端口为 8080 的 TCP 包后iptables 会把目标地址和端口改写成容器的 IP 和 80 端口然后通过 Docker 的 bridge 网络转发给容器。这带来一个容易被忽略的结果容器内看到的端口永远是自己进程监听的端口和宿主机的映射端口无关。比如 MySQL 容器内 mysqld 监听 3306你在宿主机映射成 33060连接时用的是宿主机IP:33060但进入容器内netstat看到的还是 3306。理解这一点就不会出现“明明映射了 33060 却连不上”的困惑。6.2 容器间通信的两种方式实际部署多个容器时容器间通信方式选择很关键。最简单的办法是用容器 IP 直接通信但容器重建后 IP 会变这不可靠。更推荐的做法是用 Docker 网络别名或 Compose 的服务名。在自定义网络下容器可以用名称解析。比如你在docker-compose.yml里定义了一个叫mysql8的服务另一个应用容器里配置数据库地址时直接写mysql8:3306就行Docker 内置的 DNS 会把它解析到正确的容器 IP。这个机制对于热词里提到的“访问docker容器内的mysql”这类需求尤其有用——你不用费劲去找容器的 IP直接写服务名就行。6.3 网络故障排查三步法如果容器之间不通我一般按这个顺序排查第一步确认容器都活着docker ps。第二步确认它们在同一个网络docker network inspect mynet看容器的 IP 归属。第三步进入源容器 ping 目标容器的 IP 或名称docker exec -it app-container ping mysql8如果容器里没装 ping 工具可以用docker exec进入容器后用apt install iputils-ping临时安装或者干脆用nc测试端口连通性。这一套组合拳能定位 90% 的网络问题。7. 个人的一些实操体会写到这里安装和使用的核心内容基本讲完了。最后说几句掏心窝的话。我在 Ubuntu 上折腾 Docker 也有几年了从早期手动下载 deb 包安装、到后来用 apt 源、再到现在用 Compose 统一管理最大的体会是文档和社区里的大量报错都源于环境差异而不是技术本身的问题。同一套命令在你的 Ubuntu 桌面版上可能和我的服务器版表现就不一样因为内核模块、防火墙配置、网络环境都不同。所以如果你按照我上面的步骤走到某一步报错了先别急着怀疑教程静下心来看报错信息。Ubuntu 的报错提示已经很友好了尤其是 apt 相关的错误基本都会告诉你缺少什么、地址在哪、下一步该做什么。学会读报错比记住一百条命令更有用。另一个很深的感受是容器化之后运维习惯要跟着变。以前在宿主机上装 MySQL、Redis服务由 systemd 管理更新系统时要注意别破坏数据目录。现在所有服务都在容器里升级流程变成了“拉新镜像、停旧容器、起新容器”看起来麻烦一点但实际上回滚更容易。只要数据挂载目录不变镜像怎么换都没事。最后再补一个个人偏好如果你准备长期使用 Docker 管理多个服务非常建议把所有资源配置都写成docker-compose.yml存在项目目录里。哪怕只有一个服务Compose 的表达能力也比一长串docker run参数强得多。哪天系统崩了重装把配置目录拷回来一条docker compose up -d就能恢复所有服务这才是容器化带来的最大红利。