以下步骤适用于 Ubuntu 22.04 / 24.04 / 26.04 LTS,命令需要 sudo 权限。

Ubuntu 20.04(Focal)已在 2025 年 5 月结束标准支持,之后进入需要 Ubuntu Pro 的 ESM 阶段;Docker 官方安装文档现在也只列 26.04、24.04、22.04 三个 LTS。新机器直接用 24.04 或 26.04。


什么是Docker?

Docker是一款开源的容器化平台,通过将应用程序与其运行环境打包成轻量级容器,实现跨平台一致性运行。其核心特点包括:

  • 轻量高效:共享宿主机内核,无需完整操作系统虚拟化
  • 环境一致性:容器镜像可在开发、测试、生产环境无缝运行
  • 快速部署:秒级启动时间,支持弹性扩缩容
  • 资源隔离:每个容器拥有独立的文件系统、网络和进程空间

核心概念解析

术语 说明
镜像(Image) 静态模板文件,包含应用程序及其运行环境(类似软件安装包)
容器(Container) 镜像的运行实例,提供隔离的运行时环境
仓库(Registry) 镜像存储和分发平台(如Docker Hub)

系统要求

  • 64 位 Ubuntu:26.04 LTS (Resolute)、24.04 LTS (Noble)、22.04 LTS (Jammy)
  • 兼容架构:x86_64/amd64、armhf、arm64、s390x、ppc64le
  • 衍生发行版(如 Linux Mint)不在官方支持范围内

装之前先看两件事,它们会决定你后面会不会踩坑:

  1. 用 ufw 或 firewalld 的话,容器发布的端口会绕过你的防火墙规则。这是 Docker 直接改写 iptables 的结果,不是配置错误。需要在 DOCKER-USER 链里自己加规则,见官方文档 Packet filtering and firewalls
  2. 默认(iptables 后端)下 Docker 只兼容 iptables-nftiptables-legacy,用 nft 原生命令写的规则集不被支持,防火墙规则要用 iptables/ip6tables 写。Docker Engine 29.0 起另有实验性的 nftables 后端(daemon.json"firewall-backend": "nftables"),启用后 Docker 自己写 nft 规则、没有 DOCKER-USER 链,自定义规则改在自己新建的 nft 表里按相同 base chain 与 priority 书写。

卸载旧版本 (全新机器跳过这一步)

在安装 Docker Engine 之前,先卸载所有有冲突的软件包。

发行版可能自带非官方的 Docker 软件包,它们与 Docker 官方包冲突;装 Docker Engine 正式版之前要先卸载这些包。

要卸载的非官方软件包包括:

  • docker.io
  • docker-compose
  • docker-compose-v2
  • docker-doc
  • podman-docker

此外,Docker Engine 依赖于containerdrunc。Docker Engine 将这些依赖项捆绑为一个包:containerd.io。如果你之前安装了
containerdrunc,请卸载它们以避免与 Docker Engine 捆绑的版本发生冲突。

  • 运行以下命令来卸载所有冲突的包:
1
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc | cut -f1)

apt 会告诉你其中哪些包本来就没装,这不是错误。

  • 清理残留配置
1
sudo apt-get autoremove -y
  • 彻底卸载 Docker Engine(连数据一起清掉,官方三步)
1
2
3
4
5
6
7
8
9
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras

# 下面两条会删除所有镜像、容器、卷与网络,不可恢复,确认没有要保留的卷再执行
sudo rm -rf /var/lib/docker # 镜像、容器、卷、网络
sudo rm -rf /var/lib/containerd # containerd 的镜像与快照数据,漏删常常留下几个 GB

# 顺手清掉源与密钥
sudo rm /etc/apt/sources.list.d/docker.sources
sudo rm /etc/apt/keyrings/docker.asc

注意 /var/lib/containerd 这一条:漏掉它常常会留下几个 GB 的数据。只要清旧包、想保留镜像和卷的话,执行到 apt purge 为止即可。


使用apt存储库安装

在新的主机上首次安装 Docker Engine 之前,你需要设置 Docker apt存储库。之后,你可以从存储库安装和更新 Docker。

  • 设置 Docker 的apt存储库。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install 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

# Add the repository to Apt sources(deb822 格式,Docker 官方文档当前写法):
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt-get update
  • 安装 Docker 包。
1
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
  • 确认服务在跑
1
sudo systemctl status docker

不是 running 就手动起一次:sudo systemctl start docker

  • 测试 Docker
1
sudo docker run hello-world

上述命令会下载一个 Docker 测试镜像,并在容器内执行一个 “hello_world” 样例程序。

输出里有 Hello from Docker! 就是装好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
...
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

镜像摘要与第 2 步括号里的架构会随版本和机器变化(arm64 服务器上会显示 (arm64)),不必与自己机器的输出逐字一致。

  • 确认 Compose 插件可用
1
docker compose version

输出形如 Docker Compose version v5.x.y。注意命令是 docker compose(v2 插件),不是已停止维护的独立二进制 docker-composedocker-compose-plugin 这个包提供的正是前者。


配置非Root用户权限(可选)

非root用户执行docker命令的时候, 如 docker ps -a 可能会报错

1
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/json?all=1": dial unix /var/run/docker.sock: connect: permission denied

解决方法:

1
2
3
sudo groupadd docker           # 组通常已由安装包创建,重复执行只会提示已存在
sudo usermod -aG docker $USER # 把当前用户加入 docker 组
newgrp docker # 让当前 shell 立即生效;也可以直接重新登录

加入 docker 组等于给了这个用户 root 级权限(组成员可以直接挂载宿主机目录、以 root 跑容器),官方文档把这一点写在 Docker Daemon Attack Surface 里。单人服务器上这是常见的取舍;多人共用的机器上更稳妥的做法是 rootless 模式。

如果你在加组之前用 sudo docker 跑过命令,可能会看到这条报错:

1
2
WARNING: Error loading config file: /home/user/.docker/config.json -
stat /home/user/.docker/config.json: permission denied

原因是 ~/.docker/ 目录被 root 创建过。按官方文档修:

1
2
sudo chown "$USER":"$USER" ~/.docker -R
sudo chmod g+rwx "$HOME/.docker" -R

生产环境还要做的六件事

下面六件事按「先止损、再安全、最后收敛权限」排列,前两件建议装完就做。

1. 给日志设上限

json-file 是默认的日志驱动,它默认不限制大小:容器一直往 stdout 写,/var/lib/docker/containers/*/ 下的日志文件就一直涨,最后撑爆磁盘。这是服务器磁盘被 Docker 吃满最常见的原因。

写一个 /etc/docker/daemon.json

1
2
3
4
5
6
7
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}
1
sudo systemctl restart docker

这个配置只对之后新建的容器生效,已经在跑的容器要重建才会用上新值。不想管这些参数的话,官方提供了 local 驱动,它默认就带轮转:

1
2
3
4
5
6
{
"log-driver": "local",
"log-opts": {
"max-size": "10m"
}
}

参考:Configure default logging driverjson-file 驱动

2. 把数据目录挪到别的盘(可选)

默认数据目录是 /var/lib/docker,但 Docker Engine 29 起全新安装默认启用 containerd image store:镜像与容器快照实际存在 /var/lib/containerd"data-root" 搬不走它们。系统盘小的时候要迁两处:

1
2
3
4
5
6
7
8
9
# 必须连 socket 一起停:docker.service 声明了 Requires=docker.socket,
# 只停 service 的话,任何一次 docker 客户端连接都会把守护进程重新拉起来
sudo systemctl stop docker.socket
sudo systemctl stop docker
ss -x | grep docker.sock || echo "socket 已释放,可以开始拷贝"

sudo mkdir -p /data/docker /data/containerd
sudo rsync -aHAX --info=progress2 /var/lib/docker/ /data/docker/
sudo rsync -aHAX --info=progress2 /var/lib/containerd/ /data/containerd/

然后在 /etc/docker/daemon.json 里加一行:

1
2
3
{
"data-root": "/data/docker"
}

再让 containerd 也换根,编辑 /etc/containerd/config.toml

1
2
version = 2
root = "/data/containerd"
1
2
3
sudo systemctl start containerd
sudo systemctl start docker
docker info | grep "Docker Root Dir" # 应显示 /data/docker

确认新目录工作正常之后再删旧目录。rsync-aHAX 三个参数负责保留权限、硬链接与扩展属性,少了它们容器层会出各种权限错误。

只有从 28 及更早版本升级上来、仍用 overlay2 的机器可以只迁 data-root;29 的全新安装必须两处都迁,否则镜像照样把系统盘写满(官方说明:data-root 不影响 containerd image store 里的镜像与容器数据)。

3. 磁盘回收

服务器磁盘被 Docker 吃满,多数时候是停掉的容器、构建缓存和匿名卷在堆积。先看占用,再动手:

1
2
docker system df          # 分类看:镜像 / 容器 / 卷 / 构建缓存
docker system df -v # 细到每个镜像、每个卷
1
2
3
4
docker system prune                    # 停掉的容器 + 未使用网络 + 悬空镜像 + 未用构建缓存
docker system prune -a # 上面这些,外加所有「没有任何容器关联」的镜像
docker system prune -a --volumes # 再加匿名卷:这一步会删数据,确认后再执行
docker builder prune # 只想清构建缓存时用这个

边界要说清楚:默认不删卷(官方原话:By default, volumes aren’t removed),匿名卷只有带 --volumes 才清;-a 删的是「没有被任何容器引用」的镜像,正在跑的服务用到的镜像不会被碰。想保守一点可以加时间过滤:

1
docker system prune -a --filter "until=168h"   # 只清一周前创建的

4. 卷的备份与恢复

迁移数据目录不等于备份:卷被误删、机器坏掉时数据一起没。官方给的标准做法是「借一个临时容器把卷打成 tar」:

1
2
3
4
5
# 备份:把 myvol 卷的内容打包到当前目录的 backup.tar
docker run --rm -v myvol:/data:ro -v "$(pwd)":/backup alpine tar cvf /backup/backup.tar -C /data .

# 恢复:把 tar 解回同一个卷(或另一个卷)
docker run --rm -v myvol:/data -v "$(pwd)":/backup alpine sh -c "rm -rf /data/* && tar xvf /backup/backup.tar -C /data"

对数据库这类有写入的服务,备份前先停容器(docker stop)或让它自己做一致性快照,热备份拿到的 tar 可能是半写状态。官方文档在 Back up, restore, or migrate data volumes 里给了基于 --volumes-from 的等价写法。

5. rootless 模式(多人机器或对外服务值得上)

docker 组等价于 root,这一点在前面提过。rootless 模式让守护进程和容器都在普通用户名下运行,解决了这个问题:

1
2
3
4
5
6
7
8
9
10
11
# 前置:内核允许普通用户分配子 UID/GID
sudo apt-get install -y uidmap
grep ^$(whoami): /etc/subuid # 应有至少 65536 个子 UID

# 装工具(官方包默认带,没有就补)
sudo apt-get install -y docker-ce-rootless-extras

# 以普通用户身份初始化
dockerd-rootless-setuptool.sh install
systemctl --user start docker
sudo loginctl enable-linger $USER # 开机自动起(不跑这步,退出登录就停)

它带来两个差别,写在这里免得踩:

  • 控制命令从 sudo systemctl 变成 systemctl --user代理 drop-in 的路径也跟着变~/.config/systemd/user/docker.service.d/proxy.conf
  • 容器不能直接绑 1024 以下的端口,除非把 net.ipv4.ip_unprivileged_port_start 调低(sudo sysctl net.ipv4.ip_unprivileged_port_start=80)。

如果机器上已经有系统级守护进程在跑,先按官方文档把它停掉再开 rootless(或加 --force):sudo systemctl disable --now docker.service docker.socket

6. 资源与权限收敛

自建服务暴露在公网上时,最坏情况是被人拿来挖矿或打内存。给容器套几道限制,代价几乎为零:

1
docker run -d --name ytdlp   --memory 512m --memory-swap 512m   --cpus 1   --pids-limit 256   --read-only --tmpfs /tmp   --security-opt no-new-privileges   -p 127.0.0.1:8000:8000   -v "$PWD/data:/data"   ytdlp-service
  • --memory + --memory-swap 相等表示禁用 swap,内存超了直接 OOM 而不是拖垮整机
  • --pids-limit 防 fork bomb
  • --read-only 把根文件系统挂成只读,需要写的地方单独用 --tmpfs 或卷挂出来
  • no-new-privileges 阻止容器内进程通过 setuid 提权
  • 已经在运行的容器可以用 docker update --memory 512m --cpus 1 ytdlp 补内存与 CPU 限制(--read-only 这类要重建容器)

总结

本篇覆盖:

  • Docker 核心概念与系统要求(含防火墙与 iptables 兼容性提醒)
  • 冲突包的清理与官方 apt 源(deb822 格式)的配置
  • Docker Engine 与 Compose 插件的安装和验证(docker run hello-worlddocker compose version
  • 免 sudo 的组权限配置与 ~/.docker 权限修复
  • 日志轮转与数据目录迁移
  • 磁盘回收的边界(默认不删卷)、卷的备份与恢复、rootless 落地、容器的资源与权限限制

拉取镜像慢或超时,通常是出口网络的问题,给 Docker 守护进程配代理的做法见 Ubuntu 24.04 配置 Sing-box 代理服务器完整指南


参考资料

系列索引:网络与自建服务