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

FreeBSD jail 与 Docker 容器有什么区别

FreeBSD jail 运行完整用户空间,Docker 运行注册表中的分层镜像和单个入口进程。本文比较两者在软件、状态、网络与资源限制上的实际差异。

FreeBSD jail 与 Docker 容器:一段话说明

FreeBSD jail 和 Docker 容器以两种不同的方式解决同一个问题。两者都在同一个共享内核上运行相互隔离的用户空间,因此都不是虚拟机。区别在于其中包含的内容。Docker 容器从注册表获取分层镜像,并运行其中的一个进程。jail 运行完整的 FreeBSD 用户空间:拥有自己的 /etc、自己的 rc 启动脚本、自己的 pkg 数据库,以及任意数量的进程。本页中的几乎所有其他差异都源于这一点。

SSD Nodes 不提供 FreeBSD 镜像。您无法在此平台上租用 FreeBSD 服务器,下面的内容也不是适用于您可在此购买的机器的安装指南。这是对两种隔离模型的比较,旨在帮助您判断工作负载实际需要哪一种,也帮助您阅读 FreeBSD 团队的设置,而无需猜测。

实际的 jail 是什么

jail 于 2000 年 3 月在 FreeBSD 4.0 中出现,因此比 cgroups 更早,比 Docker 早大约 10 年。该机制通过一次内核调用实现。jail(8) 接收一个目录树,并在其中启动带有 jail ID 的进程;随后,内核会拒绝任何携带该 ID 的进程执行一组固定操作。jail 中的进程无法看到 jail 外的进程,无法挂载或卸载文件系统,无法加载内核模块,也无法绑定到未分配给该 jail 的网络地址。无需学习单独的命名空间类型,也无需按功能逐项启用;这些限制作为一个整体生效,并通过 jail 配置中的参数进行调整。

在主机上,jls 列出正在运行的 jail,jexec web sh 则进入名为 web 的 jail 中的 shell。

创建 jail 的方法是将 FreeBSD 用户空间放入一个目录。基本系统会为您完成这一步:

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

该命令会获取当前发行版对应的 base distribution set,并运行常规的安装后步骤。因此,您可以像在新服务器上一样设置 root 密码和时区。最终结果是一个位于目录中的 FreeBSD 安装。然后,在 /etc/jail.conf 中描述它:

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 容器没有对应步骤,因为它只运行镜像的 entrypoint 进程,并在该进程停止后退出。

软件如何进入系统:镜像和注册表与自行填充的用户空间

这是您从第一天就能感受到的差异。

使用 Docker 时,您指定软件并获取它。docker pull nginx 会获取由其他人构建并测试的分层内容寻址镜像,docker compose up -d 会在挂载卷并连接网络后启动该镜像。注册表就是核心。Docker 工作流的大部分价值在于,数千个项目都会发布可运行的镜像。这使得在 VPS 上运行 Docker只需很短时间,而不是一个项目。

FreeBSD 不提供默认的公共 jail 镜像注册表。您需要创建一个空的用户空间并在其中安装软件,方式与配置裸服务器相同。这需要输入更多命令。但过程也更透明,因为 jail 中运行的内容就是 pkg 放入其中的内容,使用的正是主机采用的软件包集合。

工具可以简化这一过程。BastilleBSD 是常用的 jail 管理器,并且可以作为软件包安装:

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

bastille setup 会为您配置网络、存储和防火墙。bastille bootstrap 只需下载一次发行版,之后创建的每个 jail 都会重复使用它。FreeBSD 15.1 是当前的生产版本,于 June 2026 发布;请替换为您实际运行的版本。

创建 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 内为您提供登录 shell,bastille list 则显示主机上存在的内容。要重复构建过程,可以将步骤写入 Bastille 模板文件,然后应用到 jail。模板是这种环境中最接近 Dockerfile 的机制。每个 jail 都会重新执行模板。不会有预构建内容自动进入其中。

因此,结论很简单。Docker 将他人构建的内容交给您。jail 则由您自行安装软件。如果您考虑的软件只以容器镜像形式发布,没有其他分发方式,那么在其他维度进行比较之前,答案已经确定。

状态与升级:ZFS 改变了这一部分

Docker 会有意将状态分离。容器文件系统是临时的,数据存储在命名卷或绑定挂载中,升级过程是 docker compose pull,然后执行 docker compose up -d。容器会被替换,未放入卷中的所有内容都会丢失。遵循这一规则时,这是一个特性;忘记这一点时,就会导致数据丢失。因此,在 Compose 堆栈中,绑定挂载与命名卷之间的选择非常重要。

jail 不会分离状态,而 ZFS 使这种方式可行。整个 jail 是一个数据集:

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 检查实际的数据集名称;上面的路径是手册使用的布局。创建快照大约需要 1 秒。在 jail 内容发生变化之前,它几乎不占用额外空间。如果升级导致服务故障,回滚会将整个用户空间恢复到之前的状态,包括软件包数据库和您在凌晨 2 点手动编辑的配置文件。Docker 没有内置的等效功能,因为它的模型假定您从未需要这种功能。

zfs clone 是另一半机制。快照的克隆是一个新的可写 jail,它与父数据集共享未更改的数据块。因此,3 GB jail 的暂存副本几乎不占用额外磁盘空间,直到您开始修改它。FreeBSD 管理员就是这样创建一个“与生产环境相同”的 jail 来演练升级的。

基础系统升级与软件包升级是分开的。对于包含自身用户空间副本的 jail:

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

轻量 jail 可以避免重复这项工作。它们通过 nullfs 挂载一个共享的只读基础系统,并为每个 jail 提供一个独立的小型可写层。因此,您只需修补一次基础系统,所有 jail 都能看到更新结果。Bastille 默认创建轻量 jail。

网络:发布端口与寻址决策

Docker 会为您决定网络配置,并要求您发布例外端口。容器连接到 bridge 网络,在用户定义的网络中通过服务名相互访问,-p 8080:80 将其中一个容器暴露给主机。Docker 会写入自己的数据包过滤规则来实现这一点,这也是 已发布的容器端口为何可以直接绕过 ufw 的原因。

jail 要求您预先选择网络模型,共有两种。

共享 IP。 ip4.addr = "10.0.0.10" 将该地址添加到现有主机接口,并将 jail 限制为使用此地址。jail 没有自己的网络协议栈,因此无法运行自己的防火墙。它也无法真正绑定到所有地址:jail 中的套接字请求 0.0.0.0 时,内核会将其改写为 jail 自身的地址。两个 jail 不能同时监听同一地址的 80 端口,因此需要为每个 jail 分配一个地址,或者在前面放置反向代理。

VNET。 将 vnet; 添加到 jail 后,jail 会获得完整的网络协议栈,包括自己的接口、路由表和防火墙规则。使用 epair 将其连接到主机;这是两端分别位于两侧的虚拟网线,并将主机端接入 bridge。此模式最接近 Docker 提供的网络模型,也是 Bastille 的 -V 和 -B jail 类型所使用的模式。

将主机端口转发到 jail 中需要使用 pf 重定向规则。Bastille 对其进行了封装:

sudo bastille rdr web tcp 80 80

这里没有 EXPOSE,也不会自动发布端口。除非 jail 的地址或重定向规则允许,否则任何流量都无法到达 jail。启动配置较为繁琐,但防火墙会安静得多。

资源限制:cgroups 与 rctl

Docker 使用 cgroups 限制容器,限制配置在容器定义所在的位置:命令行中的 --memory=1g --cpus=1.5,或 Compose 文件中的对应键。如果您已经将堆栈保存在 VPS 上的 Docker Compose 文件 中,限制就位于适用服务旁边,并会随服务一起纳入 git 管理。

FreeBSD 使用 rctl,但必须先启用这个子系统。默认情况下不会启用资源计量,因为每次分配都会产生少量开销。将该可调参数添加到 /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 容器那样运行 Linux 二进制文件。FreeBSD 提供了 Linux 兼容层 linuxulator,但它只实现了部分 Linux 系统调用,不能作为运行任意 Linux 镜像的通用方案。

bhyve 是 FreeBSD 的 hypervisor。当您需要真正的机器隔离边界时,应使用它:例如运行不同的操作系统、不同的内核,或运行您不希望与其他工作负载共享内核的租户。代价是需要预留而不是共享的内存,以及需要额外维护和修补的第二个内核。这与您在 Linux 上决定使用容器还是完整虚拟机时面临的问题相同,也决定了您是否需要底层支持嵌套虚拟化的 VPS。

Docker 生态:大多数团队使用 Docker 的真实原因

上文讨论的是模型本身。对大多数团队来说,真正决定选择的是每种方案周围的生态规模。

Docker 提供 Docker Hub 和 GHCR、docker compose,还提供在单台主机无法满足需求时使用的 Kubernetes,以及已预先配置好容器支持的 CI runner。几乎每个项目的 README 中都有一条命令即可执行的快速入门。Jail 依托 FreeBSD ports tree。该软件包集合规模较大且维护完善,但可直接运行的应用程序包少得多。如果项目只发布容器镜像而不提供其他形式的软件包,那么在 FreeBSD 上使用它时,就需要阅读项目文档并自行组装各个组件。

Jail 在另一种取舍下更有优势。如果你已经运行 ZFS,并且重视对整个服务执行快照和回滚,就应考虑使用 Jail。如果服务是 FreeBSD 原生服务,或者你希望每个租户拥有完整的 userland,而不是只运行单个进程,也应考虑使用 Jail。你还可能希望内核、packet filter、文件系统和文档作为一个整体共同维护。人们称 FreeBSD 具有一致性时,指的就是最后这一点。Linux 和 FreeBSD 作为服务器平台的更广泛比较以及FreeBSD 15 对服务器使用方式的改变对此有更详细的说明。

最后给出一个判断。如果团队已经熟悉 Docker,迁移成本确实存在,因此收益必须明确。不要仅因隔离质量而切换;这两种模型足够接近,配置的影响更大。只有在你需要基于 ZFS 对整个服务执行回滚,或者已经使用 FreeBSD 时,才应考虑切换。

FAQ

我可以在 FreeBSD 上运行 Docker 镜像吗?

不能运行 Linux 镜像,也不应将其作为受支持的方案。FreeBSD 确实支持 OCI 容器:sudo pkg install -y podman-suite 会安装 Podman,Podman 通过 ocijail 运行容器;该运行时会在底层创建真正的 jail。容器监控程序需要将 fdescfs 挂载到 /dev/fd,容器 NAT(网络地址转换)需要 pf。原生 FreeBSD OCI 镜像的运行效果最好。Linux 镜像还需要 Linux 兼容层;截至 August 2026,FreeBSD 的 Podman 移植版本仍被描述为实验性版本。如果部署由 Linux 镜像组成,请在 Linux 上运行。在 RHEL 系列系统上,这意味着在 Rocky Linux 或 AlmaLinux 上安装 Docker;在安装任何软件前,Podman 会再次出现,因为它已经提供了 docker 命令。

FreeBSD jail 比 Docker 容器更安全吗?

两者共享同一个主机内核,因此内核漏洞会同时影响两者;对于真正不受信任的代码,两者都不是应选择的隔离边界。区别在于默认状态。jail 默认拒绝一组较广泛的操作,然后逐项重新启用。Docker 容器则默认以 root 身份运行在一组命名空间中,同时删除部分 capability;进一步加固需要显式配置。实际上,配置的影响大于模型本身:启用了 allow.mount 和 allow.raw_sockets 的 jail,并不比经过仔细配置的容器更安全。

如何备份 jail?

创建数据集快照,然后发送该快照。先执行 sudo zfs snapshot zroot/jails/containers/web@backup,再通过 zfs send 将该快照发送到另一个池,或发送到一个可复制到主机外部的文件中。由于 jail 将整个用户空间保存在一个数据集中,快照会捕获同一时间点的已安装软件包和数据,以及你手动编辑过的所有配置文件。这与 Docker 的常见做法相反:备份命名卷和 Compose 文件,其余内容从镜像重新构建。

我需要 BastilleBSD,还是基础系统就够了?

基础系统已经足够,也是更合适的起点。jail.conf、jls、jexec 和 service jail start 覆盖了整个模型。掌握这些命令后,你无需先学习某台主机的工具,就能阅读任意 FreeBSD 主机的配置。Bastille 是位于基础系统之上的便捷层:它会引导安装 release、创建 thin jail、应用模板,并为你写入 pf 重定向规则。先学习基础命令;当 jail 数量增加、手动输入变得繁琐时,再添加 Bastille。