現在安裝 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 的工作方式,則更適合作為另一篇文章單獨介紹。