现在安装 Docker 本身已经不是一件困难的事情。无论是查阅官方说明文档,还是直接让 AI 根据当前 Linux 环境生成安装命令,都可以很快完成部署。

真正值得理解的,是 Docker 从什么软件源安装,不同发行版之间有什么区别,以及 Docker 安装完成以后,镜像应该从哪里获取和分发。

Linux 上安装 Docker 的流程其实非常统一。Ubuntu 和 Debian 使用 APT,RHEL 和 Rocky Linux 使用 DNF,但最终安装的都是同一组 Docker Engine、CLI、containerd、Buildx 和 Compose 组件。

flowchart TD
    A[Linux]
    A --> B[Debian / Ubuntu]
    A --> C[RHEL / Rocky Linux]

    B --> D[APT Repository]
    C --> E[DNF Repository]

    D --> F[Docker Packages]
    E --> F

    F --> G[Docker Engine]
    F --> H[Docker CLI]
    F --> I[containerd]
    F --> J[Buildx]
    F --> K[Docker Compose]

本文按照这个共同流程介绍 Docker 的安装,而不是为不同发行版重复写几套几乎相同的教程。

1. Docker 安装了什么

我们平时说”安装 Docker”,实际上会安装多个组件:

docker-ce
docker-ce-cli
containerd.io
docker-buildx-plugin
docker-compose-plugin

其中,docker-ce 提供 Docker Engine,也就是长期运行在后台的 dockerddocker-ce-cli 提供平时使用的 docker 命令,CLI 通过 Docker API 与 daemon 通信。containerd 负责更底层的 container 生命周期管理,Buildx 提供现代 image 构建能力,而 Compose plugin 用于管理多个 container 组成的服务。

它们之间可以简单理解为:

flowchart LR
    A[Docker CLI]
    B[Docker Engine]
    C[containerd]
    D[Container]

    A -->|Docker API| B
    B --> C
    C --> D

现在 Docker Compose 已经作为 Docker CLI plugin 安装,因此应该使用 docker compose,而不是旧教程中常见的 docker-compose,也不再需要通过 pip install docker-compose 安装 Compose。

如果机器以前安装过系统仓库中的 Docker、旧版本 Docker 或其他兼容实现,可以先根据实际情况清理冲突软件包。但如果机器上已经存在正在使用的 Podman workload,不应该为了安装 Docker 而无脑删除 Podman。

2. 配置 Docker 软件源

不同 Linux 发行版安装 Docker 时,最大的差异其实就是 package manager 和 repository。

flowchart TD
    A[Docker Repository]
    A --> B[Debian]
    A --> C[Ubuntu]
    A --> D[RHEL]
    A --> E[Rocky Linux]

    B --> B1[linux/debian]
    C --> C1[linux/ubuntu]
    D --> D1[linux/rhel]
    E --> E1[本文使用 linux/centos]

    B1 --> F[APT]
    C1 --> F
    D1 --> G[DNF]
    E1 --> G

Debian 和 Ubuntu 使用 APT。首先安装基础工具并创建 keyring:

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings

可以从 /etc/os-release 获取当前发行版:

. /etc/os-release

DISTRO="$ID"
CODENAME="${UBUNTU_CODENAME:-$VERSION_CODENAME}"

对于本文覆盖的系统:

Ubuntu  -> DISTRO=ubuntu
Debian  -> DISTRO=debian

下载对应的 Docker GPG key:

sudo curl -fsSL \
    "https://download.docker.com/linux/${DISTRO}/gpg" \
    -o /etc/apt/keyrings/docker.asc

sudo chmod a+r /etc/apt/keyrings/docker.asc

然后创建 Docker APT source:

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${DISTRO}
Suites: ${CODENAME}
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

最后更新 package index:

sudo apt update

RHEL 和 Rocky Linux 使用 DNF。首先安装 repository 管理工具:

sudo dnf install -y dnf-plugins-core

RHEL 可以直接使用 Docker 提供的 RHEL repository:

sudo dnf config-manager --add-repo \
    https://download.docker.com/linux/rhel/docker-ce.repo

Rocky Linux 需要特别注意。

不要根据发行版名称自行构造:

https://download.docker.com/linux/rocky/

Docker 并没有提供这样的独立 repository。

本文在 Rocky Linux 上使用 Docker 的 CentOS repository:

sudo dnf config-manager --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

Rocky Linux、RHEL 和 CentOS 都属于 Enterprise Linux 生态,对应的软件包通常可以看到:

.el9
.el10

例如 Rocky Linux 9 使用 EL9 软件包,Rocky Linux 10 使用 EL10 软件包。

配置完成以后,可以检查 repository 是否正常:

dnf repolist | grep docker
dnf list docker-ce --showduplicates

如果以前错误配置过:

download.docker.com/linux/rocky/

可以先删除对应的 /etc/yum.repos.d/docker-ce.repo,再重新配置。

3. 安装与基础配置

repository 配置完成以后,不同发行版之间已经几乎没有区别。

Debian 和 Ubuntu 使用:

sudo apt install -y \
    docker-ce \
    docker-ce-cli \
    containerd.io \
    docker-buildx-plugin \
    docker-compose-plugin

RHEL 和 Rocky Linux 使用:

sudo dnf install -y \
    docker-ce \
    docker-ce-cli \
    containerd.io \
    docker-buildx-plugin \
    docker-compose-plugin

安装完成以后启动 Docker:

sudo systemctl enable --now docker

检查版本:

docker --version
docker compose version

然后运行测试 container:

sudo docker run --rm hello-world

这条命令已经完成了一次最简单的 Docker 工作流:

flowchart LR
    A[Docker Client]
    B[Docker Engine]
    C[Docker Registry]
    D[Image]
    E[Container]

    A -->|docker run| B
    B -->|本地没有 Image 时 pull| C
    C --> D
    D --> B
    B -->|Create + Start| E

Docker daemon 默认需要 root 权限。如果希望普通用户直接运行 docker

sudo usermod -aG docker "$USER"

重新登录 Shell 后即可:

docker ps

需要注意,能够访问 Docker daemon 的用户实际上拥有非常高的系统权限,因此不要随意把不可信用户加入 docker group。

Docker daemon 的主要配置文件是:

/etc/docker/daemon.json

默认数据目录是:

/var/lib/docker

如果系统盘比较小,或者机器有单独的数据盘,可以修改:

{
  "data-root": "/data/docker"
}

然后:

sudo systemctl restart docker

如果默认磁盘空间足够,则没有必要为了”优化”而修改 Docker 数据目录。

4. Docker Registry 与镜像分发

Docker 安装完成以后,下一个经常遇到的问题就是:

Docker image 从哪里来?

例如:

docker pull nginx

实际上是在从 Registry 下载 image。

一个完整的 image reference 可以写成:

registry.example.com/project/nginx:1.0

其中包含 Registry、repository 和 tag。

flowchart LR
    A[registry.example.com]
    B[project/nginx]
    C[1.0]

    A -->|Registry| B
    B -->|Repository| C

Docker Hub 本身就是一个公共 Registry。

如果只有一台服务器,直接使用公共 Registry 一般已经足够。但如果有多台服务器、CI/CD、内网环境或者公共 Registry 访问不稳定,就可以自己部署 Registry。

flowchart LR
    A[Developer / CI]
    B[Private Registry]
    C[Server A]
    D[Server B]
    E[Server C]

    A -->|docker push| B
    B -->|docker pull| C
    B -->|docker pull| D
    B -->|docker pull| E

最简单的测试方式是:

docker run -d \
    --name registry \
    --restart always \
    -p 5000:5000 \
    registry:3

然后可以测试 push:

docker pull nginx:latest

docker tag nginx:latest \
    localhost:5000/nginx:latest

docker push \
    localhost:5000/nginx:latest

这里的 docker tag 并不是重新复制 image,而是给现有 image 增加一个新的 reference。reference 中的 Registry hostname 决定 docker push 应该把 image 上传到哪里。

长期运行时,应该把 Registry 数据持久化:

services:
  registry:
    image: registry:3
    container_name: registry
    restart: unless-stopped
    ports:
      - "127.0.0.1:5000:5000"
    volumes:
      - /data/registry:/var/lib/registry

然后:

docker compose up -d

这里故意只监听:

127.0.0.1:5000

因为一个没有 TLS 和认证的 Registry 不应该直接暴露到公网。

如果只是偶尔向另一台机器传一个 image,也没有必要一定部署 Registry。可以直接:

docker save -o nginx.tar nginx:latest
rsync -avP nginx.tar server:/tmp/
docker load -i /tmp/nginx.tar

两种方式的区别可以简单理解为:

flowchart TD
    A[需要传输 Docker Image]
    A --> B{是否长期多机器使用?}

    B -->|| C[docker save / load]
    B -->|| D[Private Registry]

5. 使用 Nginx 为 Registry 提供 HTTPS

如果 Registry 需要被其他服务器长期访问,应该使用 HTTPS,并且配置访问控制。

一种比较容易维护的结构是:

flowchart TD
    A[Docker Client]
    B["Nginx<br/>registry.example.com:443"]
    C["Docker Registry<br/>127.0.0.1:5000"]
    D["/data/registry"]

    A -->|HTTPS| B
    B -->|HTTP| C
    C -->|Persistent Data| D

Registry 本身只监听本机:

127.0.0.1:5000

域名、TLS certificate 和公网入口全部交给 Nginx。

例如:

server {
    listen 443 ssl;
    server_name registry.example.com;

    ssl_certificate \
        /etc/letsencrypt/live/registry.example.com/fullchain.pem;
    ssl_certificate_key \
        /etc/letsencrypt/live/registry.example.com/privkey.pem;

    client_max_body_size 0;

    location / {
        proxy_pass http://127.0.0.1:5000;

        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_read_timeout 900;
        proxy_request_buffering off;
    }
}

client_max_body_size 0 可以避免 Docker image layer 因普通 Web 上传限制而失败。

如果 Nginx 本身也运行在 Docker 中,则不能再使用:

proxy_pass http://127.0.0.1:5000;

因为 container 内部的 127.0.0.1 指向 Nginx container 自己。

这时应该让 Nginx 和 Registry 位于同一个 Docker network,并通过 service name 访问:

proxy_pass http://registry:5000;

结构变成:

flowchart TD
    A[Docker Client]

    subgraph Docker Network
        B[Nginx Container]
        C["Registry Container<br/>registry:5000"]
    end

    D["/data/registry"]

    A -->|HTTPS| B
    B -->|HTTP| C
    C --> D

只有 HTTPS 还不够。如果 Registry 可以从公网访问,还应该配置认证。

可以在 Nginx 上增加 Basic Authentication:

location / {
    auth_basic "Docker Registry";
    auth_basic_user_file /etc/nginx/registry.htpasswd;

    proxy_pass http://127.0.0.1:5000;

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_read_timeout 900;
    proxy_request_buffering off;
}

Docker client 登录:

docker login registry.example.com

之后就可以:

docker tag nginx:latest \
    registry.example.com/nginx:latest

docker push \
    registry.example.com/nginx:latest

其他服务器:

docker login registry.example.com

docker pull \
    registry.example.com/nginx:latest

对于公网 Registry,不建议为了方便使用 HTTP insecure-registry。正常跨机器使用应该优先配置 HTTPS。

6. 总结

Linux 上安装 Docker,本质上并不存在几套完全不同的流程。

Ubuntu 和 Debian 使用 APT,RHEL 和 Rocky Linux 使用 DNF,真正有差异的主要是 repository 和 package manager:

flowchart TD
    A[配置 Docker Repository]

    A --> B[APT<br/>Debian / Ubuntu]
    A --> C[DNF<br/>RHEL / Rocky Linux]

    B --> D[安装 Docker Packages]
    C --> D

    D --> E[启动 Docker Engine]
    E --> F[配置用户权限]

    F --> G{如何获取和分发 Image?}

    G -->|单机| H[Public Registry]
    G -->|偶尔离线传输| I[docker save / load]
    G -->|长期多服务器| J[Private Registry]

Debian 和 Ubuntu 可以直接使用各自对应的 Docker APT repository。RHEL 可以使用 Docker 的 RHEL repository。Rocky Linux 没有独立的 linux/rocky Docker repository,本文使用 CentOS Docker repository 作为兼容性安装方案。

Docker 安装完成以后,如果只是单机运行应用,公共 Registry 通常已经足够;如果只是偶尔离线传输 image,docker save/load 更简单;如果存在 CI/CD、多服务器部署或者长期的镜像分发需求,则可以部署自己的 Registry,并通过 Nginx 提供 HTTPS 和认证。

到这里解决的是 Docker 基础设施层面的问题:

如何安装 Docker,以及 Docker image 如何获取、存储和分发。

至于 image、container、volume、bind mount、network、端口映射和 Docker Compose 的工作方式,则更适合作为另一篇文章单独介绍。