SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 已更新 2026-08-30

FreeBSD jail 與 Docker container 有何不同?

比較 FreeBSD jail 與 Docker container 的隔離模型:jail 執行完整 FreeBSD userland,Docker 使用 registry 的分層 image,並說明軟體、狀態、網路與資源限制差異。

FreeBSD jail 與 Docker container,簡述

FreeBSD jail 與 Docker container 解決的是相同問題,但採用兩種不同形式。兩者都在同一個共用 kernel 上執行隔離的 userland,因此都不是虛擬機器。兩者的差異在於隔離環境包含哪些內容。Docker container 會從 registry 下載的分層 image 中執行單一程序。jail 則執行完整的 FreeBSD userland:包含自己的 /etc、自己的 rc 啟動指令碼、自己的 pkg 資料庫,以及任意數量的程序。本頁幾乎所有其他差異,都源自這一點。

SSD Nodes 不提供 FreeBSD image。您無法在此平台租用 FreeBSD 伺服器,以下內容也不是適用於可在此購買之機器的安裝指南。這是對兩種隔離模型的比較,目的是協助您判斷工作負載實際需要哪一種,也能在閱讀 FreeBSD 團隊的設定時不必猜測。

實際上的 jail 是什麼

Jail 在 2000 年 3 月隨 FreeBSD 4.0 推出,因此比 cgroups 更早,約比 Docker 早 10 年。這套機制只需一次核心呼叫。jail(8) 會取得一個目錄樹,並在其中啟動帶有 jail ID 的程序;核心接著會拒絕任何帶有該 ID 的程序執行一組固定操作。Jail 內的程序看不到 jail 外的程序,無法掛載或卸載檔案系統,無法載入核心模組,也無法繫結到 jail 未獲指派的網路位址。這不需要學習另一種 namespace 類型,也不必逐項啟用功能;限制會以單一整體套用,再由 jail 設定中的參數調整。

在主機上,jls 會列出執行中的 jails,而 jexec web sh 會讓你進入名為 web 的 jail 內執行 shell。

建立 jail 的方式,是將 FreeBSD userland 放入目錄。基礎系統會替你完成這項工作:

sudo bsdinstall jail /usr/local/jails/containers/web

這會擷取適用於該版本的 base distribution set,並執行一般的安裝後步驟。因此,你可以像設定新伺服器一樣設定 root 密碼並選擇時區。完成後,資料夾中會有一套 FreeBSD 安裝內容。接著在 /etc/jail.conf 中描述這個 jail:

web {
  host.hostname = "web.example.internal";
  path = "/usr/local/jails/containers/web";
  ip4.addr = "10.0.0.10";
  exec.start = "/bin/sh /etc/rc";
  exec.stop = "/bin/sh /etc/rc.shutdown";
  mount.devfs;
}

啟動它,然後檢查狀態:

sudo service jail start web
jls

此時,jls 應列出帶有 JID、主機名稱及 IP 位址的 web。如果看不到該 jail,請直接執行 sudo jail -c web。它會在前景套用相同設定,並列印無法接受的參數,而不是讓錯誤只出現在服務輸出中。

值得讀兩次的行是 exec.start = "/bin/sh /etc/rc"。啟動 jail 時,會在其中執行 FreeBSD 的一般開機指令碼,因此 jail 會啟動自身 /etc/rc.conf 中啟用的每項服務。Docker container 沒有對應的步驟,因為它會執行 image 的 entrypoint 程序,並在該程序停止時停止。

軟體的取得方式:映像與 registry,對比自行填入的 userland

這是您在第一天就會感受到的差異。

使用 Docker 時,您指定軟體,系統就會取得它。docker pull nginx 會抓取由他人建置與測試、採用分層且以內容定址的映像,docker compose up -d 則會在掛載 volume 並連接網路後啟動該映像。registry 就是產品本身。Docker 工作流程的大部分價值,在於數千個專案都會發布可運作的映像。因此,在 VPS 上執行 Docker 是一項短期工作,而不是一個專案。

FreeBSD 沒有預設的公開 jail 映像 registry。您要建立空的 userland,再將軟體安裝其中,方式與設定裸機伺服器相同。這需要輸入更多指令,但也更透明,因為 jail 中執行的內容,就是 pkg 使用主機相同的 package set 安裝的內容。

工具可以縮短這個流程。BastilleBSD 是常用的 jail 管理工具,而且可透過 package 安裝:

sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASE

bastille setup 會代您設定網路、儲存空間與防火牆。bastille bootstrap 只需下載一次 release,之後建立的每個 jail 都會重複使用該 release。FreeBSD 15.1 是目前的 production release,於 June 2026 發布;請替換成您實際執行的 release。

建立 jail 只需一個指令,填入內容則再需要一個指令:

sudo bastille create web 15.1-RELEASE 10.17.89.10/24
sudo bastille pkg web install nginx
sudo bastille service web nginx start
sudo bastille console web

bastille console web 會在 jail 內提供 login shell,bastille list 則會顯示主機上存在的內容。若要重複建置,Bastille template 會將步驟保存於檔案中,並套用至 jail;這是此生態系統中最接近 Dockerfile 的做法。每個 jail 都會重新套用 template,不會有任何內容預先建置完成。

因此,結論很簡單。Docker 會提供他人建置的內容。Jail 則讓您自行安裝。如果候選軟體只以 container image 發布,沒有其他形式,那麼在比較其他條件之前,答案就已經確定。

狀態與升級:ZFS 改變了這一部分

Docker 會刻意分離狀態。容器檔案系統可以丟棄,資料則存放在具名 volume 或 bind mount 中,而升級流程是 docker compose pull,接著執行 docker compose up -d。容器會被替換,未放入 volume 的內容都會消失。遵守這項規則時,這是設計上的特性;忘記時,則會造成資料遺失。因此,在 Compose stack 中,bind mount 與具名 volume 之間的選擇非常重要。

jail 不會分離狀態,而 ZFS 正是這種作法可行的原因。整個 jail 就是一個 dataset:

sudo zfs snapshot zroot/jails/containers/web@pre-upgrade
sudo pkg -j web upgrade
sudo zfs rollback zroot/jails/containers/web@pre-upgrade

執行這項操作前,先使用 zfs list 確認實際的 dataset 名稱;上方路徑是 handbook 使用的配置。建立 snapshot 約需 1 秒,除非 jail 內容發生變更,否則幾乎不會占用額外空間。若升級導致服務故障,rollback 會將整個 userland 還原到先前狀態,包括 package database,以及你在凌晨 2 點手動修改的設定檔。Docker 沒有內建的等效功能,因為它的模型假設你從未需要這項功能。

zfs clone 是另一個部分。snapshot 的 clone 是新的可寫入 jail,會與父項共用未變更的 blocks。因此,建立 3 GB jail 的 staging copy 幾乎不會立即增加磁碟用量,直到你開始修改內容為止。FreeBSD 管理員就是這樣建立「與 production 相同」的 jail,來演練升級流程。

base system upgrade 與 packages 分開處理。對於自帶 userland 副本的 jail:

sudo freebsd-update -b /usr/local/jails/containers/web fetch install

thin jail 可避免重複這項工作。它們透過 nullfs 掛載一份共用的唯讀 base,並為每個 jail 提供自己的小型可寫入層。因此,只需修補一次 base,所有 jail 都會看到更新結果。Bastille 預設會建立 thin jail。

網路:發佈連接埠與定址決策

Docker 會替你決定網路設定,再要求你發佈例外項目。容器會連接到 bridge,並在 user-defined network 上透過服務名稱互相連線,而 -p 8080:80 會將其中一個容器公開給主機。Docker 會寫入自己的封包過濾規則來完成這些設定;這也是 已發佈的容器連接埠會直接繞過 ufw 的原因。

jail 會要求你事先選擇模型,共有兩種。

共用 IP。 ip4.addr = "10.0.0.10" 會將該位址加入現有的主機介面,並限制 jail 僅使用這個位址。jail 沒有自己的網路堆疊,因此無法執行自己的防火牆。它也無法真正繫結至所有位址:jail 中的 socket 若要求使用 0.0.0.0,kernel 會將其改寫為 jail 自己的位址。兩個 jail 無法同時在同一個位址的 port 80 上監聽,因此你必須為各自配置一個位址,或在前方放置 reverse proxy。

VNET。 將 vnet; 加入 jail 後,jail 會取得完整的網路堆疊,包括自己的介面、路由表與防火牆規則。你可以透過 epair 將它連接至主機;這是一條兩端分別連接兩側的虛擬纜線,並將主機端接到 bridge。這是最接近 Docker 網路模型的方式,也是 Bastille 的 -V 與 -B jail 類型所使用的模式。

將主機連接埠轉送至 jail 時,需要使用 pf redirect 規則。Bastille 將其包裝如下:

sudo bastille rdr web tcp 80 80

這裡沒有 EXPOSE,也不會自動發佈連接埠。除非 jail 的位址或 redirect 規則允許,否則任何流量都無法抵達 jail。這種方式啟動較慢,但防火牆會安靜許多。

資源限制:cgroups 與 rctl

Docker 使用 cgroups 限制容器,限制設定會放在容器定義所在的位置:命令列中的 --memory=1g --cpus=1.5,或 Compose 檔案中的對應索引鍵。如果您已將堆疊保存在VPS 上的 Docker Compose 檔案中,限制就會與適用的服務放在一起,並隨服務一同納入 git。

FreeBSD 使用 rctl,而且必須啟用這個子系統。資源 accounting 預設為關閉,因為每次配置都會增加少量成本。將可調參數加入 /boot/loader.conf,然後重新開機:

kern.racct.enable=1

接著設定規則並監控:

sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:web

rctl -hu jail:web 會以人類可讀的單位顯示 jail 目前的使用量,讓您在發生問題前確認目前使用量距離限制還有多少。deny 動作會讓 jail 內超過限制的配置失敗,因此您會看到應用程式本身的配置錯誤,而不是主機上的終止訊息。

使用 rctl -a 新增的規則會在下次重新開機時消失。FreeBSD 的 rctl 服務會從 /etc/rctl.conf 重新載入規則,因此請將規則寫入該檔案並啟用服務:

sudo sysrc rctl_enable=YES

這是 Docker 明顯較方便的地方。Compose 檔案中的限制會與受其約束的服務一同接受檢視。rctl 規則則是獨立檔案中的一行,指定一個在其他位置定義的 jail。

當答案是虛擬機器時:bhyve

Jail 會共用主機核心,因此有些功能永遠無法使用。它無法執行不同版本的核心、載入核心模組,也無法像 Linux container 一樣執行 Linux 二進位檔。FreeBSD 提供 Linux 相容層 linuxulator,但它只實作部分 Linux 系統呼叫,無法普遍支援任意 Linux 映像檔。

bhyve 是 FreeBSD 的 hypervisor。當您需要真正的機器邊界時,應使用它,例如執行不同的作業系統或核心,或隔離不希望與其他租戶共用核心的租戶。代價是需要保留而非共用的記憶體,以及另一個需要修補的核心。這與您在 Linux 上於 containers 和完整虛擬機器之間所做的決策相同,也會決定您是否需要底層支援 nested virtualization 的 VPS。

生態系:多數團隊使用 Docker 的實際原因

以上內容都在說明模型。多數團隊的選擇,取決於每個模型周邊生態系的規模。

Docker 提供 Docker Hub 和 GHCR、docker compose、Kubernetes(單台主機不敷使用時)、已預先整合容器支援的 CI runners,以及幾乎每個專案 README 中都有的單一命令快速入門。Jails 提供 FreeBSD ports tree;該套件樹規模龐大且維護完善,但可直接執行的應用程式套件少得多。如果專案只發布 container image,而沒有其他內容,採用 FreeBSD 的做法就是閱讀其文件,然後自行組合所需元件。

Jails 在權衡的另一端展現其價值。當你已經使用 ZFS,並重視對整個服務進行 snapshot 和 rollback;當你的服務原生支援 FreeBSD;當你希望每個 tenant 都有完整 userland,而不是只有單一程序;或當你希望 kernel、packet filter、filesystem 與文件由同一套系統一併維護時,就應該考慮使用 Jails。人們所說的 FreeBSD 具備一致性,指的就是最後一點。這部分會在Linux 與 FreeBSD 作為伺服器平台的完整比較以及FreeBSD 15 對伺服器用途的變更中進一步說明。

最後的判斷如下。如果團隊已經熟悉 Docker,遷移成本確實存在,因此必須有明確的效益。不必為了隔離品質而切換;兩種模型的差異不大,實際設定的影響更大。應在需要由 ZFS 支援的整個服務 rollback,或已經使用 FreeBSD 時,才考慮切換。

FAQ

我可以在 FreeBSD 上執行 Docker 映像嗎?

不能執行 Linux 映像,也不屬於受支援的做法。FreeBSD 確實支援 OCI 容器:sudo pkg install -y podman-suite 會安裝 Podman,Podman 透過 ocijail 執行容器;這個 runtime 會在底層建立真正的 jail。容器監控程式需要將 fdescfs 掛載至 /dev/fd,而容器 NAT(network address translation)需要 pf。以 FreeBSD 原生 OCI 映像為佳。Linux 映像還需要 Linux 相容層;截至 August 2026,FreeBSD 的 Podman port 仍標示為實驗性。如果部署內容是一組 Linux 映像,請在 Linux 上執行。在 RHEL 系列中,這表示在 Rocky Linux 或 AlmaLinux 上安裝 Docker;此時 Podman 會再次出現,成為在你安裝任何內容之前就已經擁有 docker command 的 package。

FreeBSD jail 比 Docker 容器更安全嗎?

兩者都共用同一個 host kernel,因此 kernel bug 對兩者都是風險;如果要執行真正不受信任的程式碼,兩者都不是你會選擇的隔離邊界。差異在於預設起點。jail 預設拒絕一組廣泛的操作,之後再逐一透過參數重新啟用。Docker container 則在一組 namespaces 中以 root 身分啟動,並停用部分 capabilities;其他強化措施則須自行選擇啟用。實務上,設定比模型更重要:啟用 allow.mount 與 allow.raw_sockets 的 jail,不會比妥善設定的 container 更安全。

如何備份 jail?

建立 dataset snapshot,然後傳送出去。sudo zfs snapshot zroot/jails/containers/web@backup,接著將該 snapshot 以 zfs send 傳送至其他 pool,或寫入檔案後複製到主機之外。由於 jail 將完整的 userland 保存在一個 dataset 中,snapshot 會擷取已安裝的 packages、同一時間點的資料,以及你手動編輯過的所有設定檔。這與 Docker 的做法相反;在 Docker 中,你會備份 named volumes 和 Compose file,其餘內容則從 image 重新建立。

我需要 BastilleBSD,還是 base system 就足夠?

base system 已經足夠,也是較適合的起點。jail.conf、jls、jexec 與 service jail start 涵蓋完整模型。熟悉這些指令後,你就能讀懂任何 FreeBSD host,而不必先學習該 host 使用的工具。Bastille 是建置在其上的便利層:它會 bootstrap releases、建立 thin jails、套用 templates,並替你寫入 pf redirect rules。先學會 base commands,再在 jail 數量增加到手動輸入變得繁瑣時加入 Bastille。