Share Notes

chundev

View the Project on GitHub latteouka/share-notes

完整離線部署 Docker CE + 應用服務到 Air-Gapped Linux 環境

日期:2026-04-01 環境:Ubuntu 24.04 (amd64)、Docker CE 29.x、Apple Silicon Mac 作為打包機


TL;DR

在完全無網路的 Linux 機器上部署 Docker 容器化應用,需要解決三個問題:(1) Docker Engine 本身的離線安裝(含所有遞迴依賴的 .deb 套件)、(2) 應用 Docker images 的跨平台打包、(3) 一鍵安裝腳本。最大的坑不是 image 打包,而是 Docker CE 的 .deb 套件有 30+ 個 transitive dependencies(iptables、nftables、libsystemd 等),必須用 apt-cache depends --recurse 完整解析後一起帶過去。


背景

需要將一個容器化的 Node.js API 服務(含 nginx HTTPS reverse proxy)部署到一台完全沒有外網的 Ubuntu 24.04 伺服器。目標機器:

需要打包一個「USB 隨身碟就能帶過去」的離線安裝 bundle。


架構設計

離線部署分為兩階段:

階段一:在有網路的機器上準備 bundle

prepare-bundle.sh
├── [1] 下載 Docker CE .deb 套件(含所有遞迴依賴)
├── [2] Build 應用程式 Docker images(指定 linux/amd64 平台)
├── [3] docker save 匯出 images
└── [4] 複製 compose 設定 + 產生 SSL 憑證

階段二:在離線機器上執行安裝

install.sh
├── [1] dpkg -i 安裝 Docker CE(從 .deb 包)
├── [2] docker load 載入 images
└── [3] docker compose up -d 啟動服務

最終 bundle 結構:

offline-bundle/
├── docker-debs/           # Docker CE + 所有依賴的 .deb 套件
│   ├── containerd.io_*.deb
│   ├── docker-ce_*.deb
│   ├── docker-ce-cli_*.deb
│   ├── docker-compose-plugin_*.deb
│   ├── docker-buildx-plugin_*.deb
│   ├── iptables_*.deb
│   ├── libnftables1_*.deb
│   ├── ... (共 30+ 個套件)
│   └── install-docker.sh
├── app-images.tar.gz      # docker save 匯出的 images
├── docker-compose.yml
├── nginx/
│   ├── nginx.conf
│   └── ssl/
│       ├── server.crt
│       └── server.key
└── install.sh             # 一鍵安裝腳本

核心知識一:解析 Docker CE 的遞迴依賴

問題

Docker 官方文件說下載 5 個 .deb 就好:containerd.iodocker-cedocker-ce-clidocker-buildx-plugindocker-compose-plugin。但在全新的 Ubuntu 24.04 上 dpkg -i 這 5 個套件會失敗——因為它們還依賴 iptablesnftableslibsystemd0libseccomp2 等系統套件。

錯誤訊息

dpkg: dependency problems prevent configuration of docker-ce:
 docker-ce depends on iptables; however:
  Package iptables is not installed.
 docker-ce depends on libsystemd0; however:
  Package libsystemd0 is not installed.
error while loading shared libraries: libnftables.so.1: cannot open shared object file

解法:apt-cache depends –recurse

在一個相同 OS 版本的連網容器中,用 apt-cache depends --recurse 遞迴解析所有 transitive dependencies:

PKGS="containerd.io docker-ce docker-ce-cli docker-compose-plugin docker-buildx-plugin"

# 解析所有遞迴依賴(過濾掉非套件行)
ALL_DEPS=$(apt-cache depends --recurse \
  --no-recommends --no-suggests \
  --no-conflicts --no-breaks \
  --no-replaces --no-enhances \
  $PKGS 2>/dev/null | grep "^\w" | sort -u)

# 批次下載所有 .deb
apt-get download $ALL_DEPS 2>&1 | grep -v "^W:" || true

關鍵參數說明:

參數 作用
--recurse 遞迴解析,不只看第一層依賴
--no-recommends 排除「建議安裝」的套件
--no-suggests 排除「推薦安裝」的套件
grep "^\w" 過濾掉 apt-cache 輸出的中間行(如 Depends:PreDepends: 標籤行)
sort -u 去重

實際解析結果

Docker CE 在 Ubuntu 24.04 上的完整依賴約 30-35 個套件,包括:

containerd.io, docker-ce, docker-ce-cli, docker-compose-plugin, docker-buildx-plugin,
iptables, nftables, libnftables1, libnetfilter-conntrack3, libnfnetlink0,
libip4tc2, libip6tc2, libxtables12, libsystemd0,
libseccomp2, libapparmor1, libdevmapper1.02.1, ...

替代方案比較

方法 優點 缺點
apt-cache depends --recurse + apt-get download 簡單直接 可能包含虛擬套件名稱
apt-get install --download-only APT 自己解析最可靠 需要完整的 APT cache
aptly 可建立完整離線 repo 學習成本高
apt-offline 專為離線設計 需額外安裝工具
Docker 靜態二進位檔 無套件依賴問題 缺少 systemd 整合、compose plugin

建議: 如果只是偶爾做離線部署,apt-cache depends --recurse 最實用。如果要長期維護離線環境,考慮用 aptly 建立本地 repo mirror。


核心知識二:跨平台 Docker Image 打包

問題

打包機是 Apple Silicon Mac (arm64),但目標部署機器是 Linux amd64。如果不指定平台,docker builddocker pull 會拉 arm64 的 image,在 amd64 機器上無法執行。

解法

在所有 Docker 操作中加上 --platform linux/amd64

# Build 應用 image(指定平台)
docker build --platform linux/amd64 -t myapp .

# Pull 第三方 image(指定平台)
docker pull --platform linux/amd64 nginx:alpine

# 啟動容器測試(指定平台)
docker run --platform linux/amd64 myapp

注意事項


核心知識三:完整打包與安裝腳本

docker save 打包 images

# 多個 image 可以一次 save
docker save myapp:latest nginx:alpine | gzip > images.tar.gz

壓縮選擇:

方法 壓縮率 速度 建議場景
gzip ~3-5x 中等 通用,相容性好
pigz ~3-5x 快(多核並行) 大型 image
zstd -T0 ~3-6x 最快 現代系統推薦

離線安裝 Docker CE 的腳本

#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

if command -v docker &> /dev/null; then
    echo "Docker 已安裝: $(docker --version)"
    exit 0
fi

# 安裝所有 .deb(忽略順序問題,dpkg 會自己處理)
dpkg -i "$SCRIPT_DIR"/docker-debs/*.deb 2>&1 || true

# 啟動 Docker daemon
if command -v systemctl &>/dev/null; then
    systemctl enable docker && systemctl start docker
else
    # 無 systemd 環境的 fallback
    dockerd &>/var/log/dockerd.log &
    for i in $(seq 1 30); do
        docker info &>/dev/null 2>&1 && break
        sleep 1
    done
fi

docker --version && docker compose version

重點:

一鍵安裝腳本

#!/bin/bash
set -e
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"

# Step 1: 安裝 Docker(如果尚未安裝)
if ! command -v docker &> /dev/null; then
    bash "$SCRIPT_DIR/docker-debs/install-docker.sh"
fi

# Step 2: 載入 Docker images
docker load < "$SCRIPT_DIR/images.tar.gz"

# Step 3: 啟動服務
cd "$SCRIPT_DIR"
docker compose up -d

核心知識四:測試離線安裝流程

為什麼需要測試

離線部署最怕的是「以為帶齊了其實少了一個 .deb」。在開發機上測試離線安裝流程:

# 啟動乾淨 Ubuntu(privileged 模式以支援 Docker daemon)
docker run -d --name test --privileged --cgroupns=host \
  --platform linux/amd64 ubuntu:24.04 sleep 3600

# 複製 bundle 進去
docker cp offline-bundle test:/root/bundle

# 斷網(關鍵步驟!)
docker exec test bash -c "
  echo '127.0.0.1 localhost' > /etc/resolv.conf
  ip route del default 2>/dev/null || true
"

# 驗證網路已斷
docker exec test bash -c "timeout 2 bash -c 'echo > /dev/tcp/8.8.8.8/53' 2>/dev/null" \
  && echo "⚠️ 網路未斷" || echo "✅ 網路已斷"

# 確認沒有預裝 Docker
docker exec test which docker && echo "❌ 測試無效" || echo "✅ 可以開始"

# 執行離線安裝
docker exec test bash /root/bundle/install.sh

測試方法論

步驟 驗證項目
啟動乾淨 OS 確認沒有預裝 Docker
複製 bundle 確認檔案完整
立即斷網 確認之後的步驟全部離線
安裝 Docker dpkg 不報錯、docker daemon 啟動
載入 images docker load 成功
啟動服務 docker compose up、API 可回應

已知限制

在 Apple Silicon Mac 上透過 QEMU 模擬 amd64 容器做 DinD 測試時,最後一步 docker compose up 可能因 overlay fs mount 失敗而無法執行。這是 QEMU 模擬的限制,不是 bundle 本身的問題。 驗證 Docker 安裝成功(docker --versiondocker compose version)即可。

完整的端對端測試應在原生 amd64 Linux 機器或 CI(如 GitHub Actions 的 ubuntu-latest runner)上進行。


除錯過程

開發這套離線部署流程時遇到的問題:

嘗試 做法 結果 原因
❌ 只帶 5 個 Docker .deb 只下載官方文件列出的套件 dpkg 安裝失敗 缺少 iptables、nftables 等 transitive dependencies
❌ 用 Docker-in-Docker image 測試 docker:dind 當測試容器 測試無效 DinD image 本身已預裝 Docker,無法驗證離線安裝
❌ 不指定 --platform Mac 上直接 build 產出 arm64 image Mac 預設建 arm64,目標機器是 amd64
❌ DinD + QEMU 做完整測試 在模擬的 amd64 容器內跑 Docker daemon 再 compose up overlay mount failed QEMU user-mode 不支援 overlay2 的 mount syscall
apt-cache depends --recurse 遞迴解析所有依賴並下載 成功取得 30+ 個 .deb 完整依賴樹,不遺漏
✅ 乾淨 Ubuntu + 斷網測試 全新 Ubuntu → 複製 bundle → 斷網 → 安裝 Docker CE 安裝成功 正確的測試方法論

學到的事


參考資料