FreeBSD jail یا Docker container؛ تفاوت چیست؟
FreeBSD jail یک userland کامل را ایزوله میکند، اما Docker از imageهای لایهای registry استفاده میکند. تفاوت software، state، networking و محدودیتها را ببینید.
FreeBSD jailها در برابر Docker containerها، در یک پاراگراف
FreeBSD jailها و Docker containerها یک مسئله را با دو ساختار متفاوت حل میکنند. هر دو userlandهای ایزوله را روی یک kernel مشترک اجرا میکنند؛ بنابراین هیچکدام ماشین مجازی نیستند. تفاوت در محتوای آنهاست. یک Docker container یک process را از image لایهای اجرا میکند که از یک registry دریافت کردهاید. یک jail یک userland کامل FreeBSD را اجرا میکند: /etc مخصوص خود، اسکریپتهای راهاندازی rc مخصوص خود، پایگاهداده pkg مخصوص خود و هر تعداد process که نیاز دارید. تقریباً همه تفاوتهای دیگر این صفحه از همین تفاوت اصلی ناشی میشوند.
SSD Nodes imageهای FreeBSD ارائه نمیدهد. در این پلتفرم نمیتوانید یک سرور FreeBSD اجاره کنید و هیچیک از مطالب زیر راهنمای نصب روی ماشینی نیست که بتوانید از اینجا خریداری کنید. این متن مقایسهای میان دو مدل ایزولهسازی است تا بتوانید تشخیص دهید یک workload واقعاً به کدام مدل نیاز دارد و تنظیمات تیمی را که از FreeBSD استفاده میکند، بدون حدسزدن بخوانید.
یک jail واقعاً چیست
jailها در مارس 2000 و همراه با FreeBSD 4.0 معرفی شدند؛ بنابراین از cgroups قدیمیتر و تقریباً یک دهه از Docker قدیمیتر هستند. این سازوکار با یک فراخوانی kernel کار میکند. jail(8) یک درخت دایرکتوری را میگیرد و پردازهها را با یک jail ID متصل به آن داخل همان درخت اجرا میکند. سپس kernel مجموعهای مشخص از عملیات را برای هر پردازهای که آن ID را دارد، رد میکند. پردازهای که داخل jail اجرا میشود نمیتواند پردازههای خارج از jail خود را ببیند، filesystemها را mount یا unmount کند، kernel moduleها را بارگذاری کند یا به آدرسهای شبکهای متصل شود که به jail اختصاص داده نشدهاند. برای استفاده از این قابلیت، لازم نیست نوع namespace جداگانهای یاد بگیرید و برای هر ویژگی نیز گزینهای مستقل وجود ندارد. این محدودیتها بهصورت یک مجموعه اعمال میشوند و پارامترهای موجود در configuration مربوط به jail آنها را تنظیم میکنند.
در host، jls فهرست jailهای در حال اجرا را نمایش میدهد و jexec web sh شما را وارد shell مربوط به jail دارای نام web میکند.
برای ساختن jail، userland مربوط به FreeBSD را داخل یک دایرکتوری قرار میدهید. base system این کار را برای شما انجام میدهد:
sudo bsdinstall jail /usr/local/jails/containers/webاین دستور مجموعه base distribution مربوط به release شما را دریافت میکند و بیشتر مراحل معمول پس از نصب را اجرا میکند. بنابراین مانند نصب یک server جدید، root password را تنظیم میکنید، serviceها را انتخاب میکنید و user اضافه میکنید. این دستور هرگز درباره time zone سؤال نمیکند؛ installer فایل /etc/localtime مربوط به host را داخل jail کپی میکند، بنابراین jail، time zone مربوط به host را به ارث میبرد. نتیجه، یک نصب 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;
}آن را start کنید و سپس بررسی کنید:
sudo service jail start web
jlsاکنون jls باید web را همراه با JID، hostname و IP address آن نمایش دهد. اگر jail ظاهر نشد، sudo jail -c web را مستقیماً اجرا کنید. این دستور همان configuration را در foreground اعمال میکند و پارامتری را که نتوانسته است بپذیرد چاپ میکند؛ بنابراین خطا در خروجی service پنهان نمیماند.
خطی که باید آن را دوبار بخوانید exec.start = "/bin/sh /etc/rc" است. start کردن یک jail، boot script معمول FreeBSD را داخل آن اجرا میکند. بنابراین jail همه serviceهایی را که در /etc/rc.conf خود فعال شدهاند، راهاندازی میکند. Docker container مرحله معادلی ندارد، زیرا process مربوط به entrypoint image را اجرا میکند و با توقف آن process متوقف میشود.
نحوه دریافت نرمافزار: imageها و registryها در برابر userlandای که خودتان پر میکنید
این تفاوت را از همان روز اول احساس میکنید.
در Docker، نام نرمافزار را مشخص میکنید و آن را دریافت میکنید. docker pull nginx یک image لایهای و content-addressed را که شخص دیگری ساخته و آزمایش کرده است دریافت میکند و docker compose up -d آن را همراه با volumeها و شبکه متصلشده اجرا میکند. registry خود محصول است. بخش زیادی از ارزش workflow در Docker به این دلیل است که هزاران پروژه یک image آمادهبهکار منتشر میکنند؛ به همین دلیل اجرای Docker روی یک VPS به کاری کوتاه، نه یک پروژه، تبدیل میشود.
FreeBSD یک registry عمومی پیشفرض برای imageهای jail ارائه نمیکند. شما یک userland خالی ایجاد میکنید و نرمافزار را داخل آن نصب میکنید؛ درست مانند راهاندازی یک سرور bare. این کار به تایپ بیشتری نیاز دارد. در عوض، شفافتر است، چون چیزی که داخل jail اجرا میشود همان چیزی است که pkg در آن قرار داده است و از همان مجموعهٔ package مورد استفادهٔ host میآید.
ابزارها این فرایند را کوتاه میکنند. BastilleBSD مدیر رایج jail است و بهصورت یک package ارائه میشود:
sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASEbastille setup شبکه، storage و firewall را برای شما پیکربندی میکند. bastille bootstrap یک release را یکبار دانلود میکند و هر jailای که بعداً ایجاد میکنید از همان release استفاده میکند. FreeBSD 15.1 release فعلی production است و در June 2026 منتشر شده است؛ release مورد استفادهٔ خود را جایگزین کنید.
پس ایجاد یک jail با یک command انجام میشود و پرکردن آن نیز فقط یک command دیگر لازم دارد:
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 webbastille console web یک login shell درون jail در اختیار شما میگذارد و bastille list نشان میدهد روی host چه چیزهایی وجود دارد. برای تکرار یک build، templateهای Bastille مراحل را در یک file نگه میدارند و آنها را روی یک jail اعمال میکنند؛ این نزدیکترین معادل Dockerfile در این محیط است. یک template روی هر jail دوباره اجرا میشود. هیچ چیز از پیش build شده دریافت نمیشود.
بنابراین خلاصهٔ صادقانه کوتاه است. Docker buildهای دیگران را در اختیار شما میگذارد. jailها نصبهای خودتان را در اختیار شما میگذارند. اگر نرمافزار موجود در فهرست شما فقط بهصورت container image منتشر شده باشد و شکل دیگری نداشته باشد، پاسخ پیش از آنکه محور دیگری فرصت تصمیمگیری پیدا کند مشخص است.
وضعیت و ارتقا: بخشی که ZFS تغییر میدهد
Docker عمداً وضعیت را تفکیک میکند. فایلسیستم container قابل دورریختن است، دادههای شما در یک named volume یا bind mount قرار میگیرند و فرایند ارتقا شامل docker compose pull و سپس docker compose up -d است. container جایگزین میشود و هر چیزی را که در volume قرار نداده باشید از دست میدهید. اگر این قاعده را رعایت کنید، این یک قابلیت است؛ اما اگر آن را فراموش کنید، به رخداد از دست رفتن داده تبدیل میشود. به همین دلیل انتخاب بین bind mount و named volume در یک stack مربوط به Compose اهمیت زیادی دارد.
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پیش از اجرای این دستور، نام واقعی dataset را با zfs list بررسی کنید؛ مسیر بالا همان layout استفادهشده در handbook است. ایجاد snapshot حدود 1 ثانیه طول میکشد و تا زمانی که محتوای jail تغییر نکند، تقریباً هیچ فضای اضافی مصرف نمیکند. اگر ارتقا باعث ازکارافتادن سرویس شود، rollback کل userland را به وضعیت قبلی برمیگرداند؛ این شامل package database و فایلهای configuration است که ساعت 2 بامداد بهصورت دستی ویرایش کردهاید. Docker معادل داخلی چنین قابلیتی ندارد، زیرا مدل آن بر این فرض استوار است که هرگز به چنین قابلیتی نیاز ندارید.
zfs clone نیمه دیگر این فرایند است. clone یک snapshot، jail جدید و قابلنوشتنی است که blockهای تغییرنکرده را با parent خود بهاشتراک میگذارد. بنابراین یک کپی staging از jail با حجم 3 GB، تا زمانی که شروع به تغییر آن نکنید، تقریباً هیچ فضای اضافی روی دیسک مصرف نمیکند. مدیر FreeBSD به این روش یک jail با وضعیت «مشابه production» میسازد تا ارتقا را در آن تمرین کند.
ارتقای base system از packageها جدا است. برای jailی که کپی مستقل خود از userland را نگه میدارد:
sudo freebsd-update -b /usr/local/jails/containers/web fetch installthin jailها از تکرار این کار جلوگیری میکنند. آنها یک base مشترک و فقطخواندنی را از طریق nullfs mount میکنند و به هر jail یک لایه کوچک و مستقلِ قابلنوشتن میدهند. بنابراین base را یکبار patch میکنید و همه jailها نتیجه را میبینند. Bastille بهصورت پیشفرض thin jail ایجاد میکند.
شبکهبندی: پورتهای منتشرشده در برابر تصمیم آدرسدهی
Docker تصمیمگیری درباره شبکه را برای شما انجام میدهد و از شما میخواهد استثناها را منتشر کنید. کانتینرها روی یک bridge قرار میگیرند، از طریق نام سرویس در یک شبکه تعریفشده توسط کاربر به یکدیگر دسترسی پیدا میکنند و -p 8080:80 یکی از آنها را برای میزبان (host) در دسترس قرار میدهد. Docker قوانین فیلتر بسته (packet filter) خود را برای تحقق این امر مینویسد؛ این همان دلیلی است که یک پورت کانتینر منتشرشده مستقیماً از ufw عبور میکند.
یک jail شما را مجبور میکند مدل را از ابتدا انتخاب کنید و دو مدل وجود دارد.
IP اشتراکی. ip4.addr = "10.0.0.10"، jail را به آن آدرس محدود میکند. این مدل بهتنهایی با رابطهای میزبان کاری ندارد، بنابراین آدرس باید از قبل روی یکی از آنها پیکربندی شده باشد. برای اینکه jail(8) هنگام ایجاد jail، آدرس را به عنوان یک alias اضافه کند، رابط را نامگذاری کنید: interface = "em0"; را در بلوک jail تنظیم کنید یا آن را به صورت ip4.addr = "em0|10.0.0.10"; قبل از آدرس قرار دهید. jail پشته شبکه (network stack) اختصاصی خود را ندارد، بنابراین نمیتواند فایروال خود را اجرا کند. همچنین نمیتواند واقعاً به همه آدرسها bind شود: یک سوکت در jail که درخواست 0.0.0.0 میکند، توسط هسته (kernel) به آدرس خودِ jail بازنویسی میشود. دو jail نمیتوانند همزمان روی پورت 80 از یک آدرس گوش دهند، بنابراین یا به هر کدام یک آدرس اختصاص میدهید یا یک reverse proxy در مقابل آنها قرار میدهید.
VNET. عبارت vnet; را به jail اضافه کنید تا یک پشته شبکه کامل دریافت کند: رابطهای اختصاصی، جدول مسیریابی اختصاصی و قوانین فایروال اختصاصی. شما آن را با یک epair به میزبان متصل میکنید؛ یک کابل مجازی که یک سر آن در هر طرف قرار دارد و سرِ سمت میزبان را روی یک bridge میگذارید. این نزدیکترین معادل به چیزی است که Docker ارائه میدهد و همان حالتی است که پشت انواع jailهای -V و -B در Bastille قرار دارد.
فوروارد کردن یک پورت میزبان به داخل یک jail، یک قانون تغییر مسیر pf است. Bastille این کار را بستهبندی میکند:
sudo bastille rdr web tcp 80 80هیچ EXPOSE و انتشار خودکاری وجود ندارد. هیچچیز به یک jail نمیرسد مگر اینکه آدرس آن یا یک قانون تغییر مسیر اجازه دهد. این شروعی کندتر و فایروالی بسیار ساکتتر است.
محدودیتهای منابع: cgroups در برابر rctl
Docker یک کانتینر را با cgroups محدود میکند و محدودیتها در جایی قرار دارند که کانتینر تعریف شده است: --memory=1g --cpus=1.5 در خط فرمان، یا کلیدهای مشابه در یک فایل Compose. اگر قبلاً پشته خود را در یک فایل Docker Compose روی یک VPS نگهداری میکنید، محدودیت در کنار سرویسی که برای آن اعمال میشود قرار میگیرد و همراه با آن در git جابهجا میشود.
FreeBSD از rctl استفاده میکند و این زیرسیستمی است که باید آن را فعال کنید. هسته GENERIC حسابداری منابع را کامپایل میکند اما آن را از طریق گزینه هسته RACCT_DEFAULT_TO_DISABLED غیرفعال نگه میدارد. این tunable را به /boot/loader.conf اضافه کنید و سیستم را reboot کنید:
kern.racct.enable=1سپس یک قانون تنظیم کنید و آن را مانیتور کنید:
sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:webrctl -hu jail:web میزان استفاده فعلی jail را با واحدهای قابلفهم برای انسان چاپ میکند، بنابراین میتوانید ببینید قبل از بروز هرگونه خرابی، چقدر به محدودیت نزدیک است. عمل deny باعث میشود تخصیص بیش از حد در داخل jail با شکست مواجه شود، بنابراین شما خطای تخصیص خودِ برنامه را میبینید، نه یک پیام kill در میزبان.
قوانینی که با rctl -a اضافه میشوند، با reboot بعدی از بین میروند. سرویس rctl در FreeBSD آنها را از /etc/rctl.conf بارگذاری مجدد میکند، بنابراین قانون را در آن فایل بنویسید و سرویس را فعال کنید:
sudo sysrc rctl_enable=YESاین همان محوری است که Docker در آن بهوضوح راحتتر است. یک محدودیت در فایل Compose همراه با سرویسی که آن را محدود میکند، بازبینی میشود. یک قانون rctl، خطی در یک فایل جداگانه است که به jail تعریفشده در جای دیگری اشاره میکند.
وقتی پاسخ یک ماشین مجازی است: bhyve
یک jail از kernel میزبان استفاده میکند؛ بنابراین برخی قابلیتها برای همیشه خارج از دسترس آن هستند. jail نمیتواند نسخهای متفاوت از kernel را اجرا کند، نمیتواند kernel module بارگذاری کند و نمیتواند binaryهای Linux را مانند یک Linux container اجرا کند. FreeBSD یک لایهٔ سازگاری با Linux به نام linuxulator دارد، اما این لایه فقط زیرمجموعهای از system callهای Linux را پیادهسازی میکند و برای imageهای دلخواه Linux راهحل عمومی نیست.
bhyve hypervisor مربوط به FreeBSD است و زمانی ابزار مناسب محسوب میشود که به یک مرز واقعی میان ماشینها نیاز دارید: یک سیستمعامل متفاوت، یک kernel متفاوت یا tenantای که ترجیح میدهید kernel را با آن به اشتراک نگذارید. در این حالت، بخشی از هزینه به memoryای مربوط میشود که بهجای اشتراکگذاری رزرو میشود و بخشی نیز به kernel دوم برای patch کردن آن اختصاص دارد. این همان تصمیمی است که در Linux بین containerها و ماشینهای مجازی کامل میگیرید و مشخص میکند آیا به یک VPS با پشتیبانی از nested virtualization در لایهٔ زیرین نیاز دارید یا خیر.
اکوسیستم؛ دلیل واقعی استفاده بیشتر تیمها از Docker
تمام مطالب بالا به مدل مربوط میشود. عاملی که برای بیشتر تیمها انتخاب را تعیین میکند، اندازه اکوسیستم پیرامون هر مدل است.
Docker، Docker Hub و GHCR، docker compose، Kubernetes را زمانی که یک سرور دیگر کافی نیست، runnerهای CI با پشتیبانی از container که از قبل پیکربندی شده است، و quickstart تکدستوری را تقریباً در README هر پروژه ارائه میکند. Jailها درخت ports مربوط به FreeBSD را دارند که بزرگ و با دقت نگهداری میشود، بهعلاوه مجموعهای بسیار کوچکتر از bundleهای آماده اجرای برنامهها. وقتی یک پروژه فقط image مربوط به container را منتشر میکند و چیز دیگری ارائه نمیدهد، مسیر FreeBSD این است که مستندات آن را بخوانید و اجزا را خودتان سرهم کنید.
Jailها در سوی دیگر این موازنه جایگاه خود را پیدا میکنند. اگر از قبل ZFS را اجرا میکنید و snapshot و rollback یک سرویس کامل برایتان اهمیت دارد، اگر سرویسهایتان nativeِ FreeBSD هستند، اگر برای هر tenant یک userland کامل بهجای یک فرایند واحد میخواهید، یا اگر میخواهید kernel، packet filter، filesystem و مستندات بهصورت یک سیستم واحد و هماهنگ نگهداری شوند، از jail استفاده کنید. منظور افراد از coherent بودن FreeBSD همین نکته است؛ این موضوع در مقایسه گستردهتر Linux و FreeBSD بهعنوان پلتفرمهای سرور و در تغییرات FreeBSD 15 برای استفاده در سرورها با جزئیات بیشتری بررسی شده است.
یک جمعبندی نهایی. اگر تیم شما از قبل با Docker آشناست، هزینه مهاجرت واقعی است و مزیت آن باید مشخص باشد. بهدلیل کیفیت isolation جابهجا نشوید؛ این دو مدل آنقدر به هم نزدیک هستند که configuration شما اهمیت بیشتری دارد. زمانی جابهجا شوید که rollback مبتنی بر ZFS برای سرویسهای کامل میخواهید، یا از قبل روی FreeBSD هستید.
FAQ
آیا میتوانم imageهای Docker را روی FreeBSD اجرا کنم؟
imageهای Linux را نمیتوان بهصورت پشتیبانیشده اجرا کرد. FreeBSD از containerهای OCI پشتیبانی میکند: sudo pkg install -y podman-suite، Podman را نصب میکند و Podman containerها را از طریق ocijail اجرا میکند؛ این runtime در لایه زیرین jailهای واقعی ایجاد میکند. برای monitor مربوط به container، fdescfs باید روی /dev/fd mount شود و برای NAT مربوط به containerها، یعنی network address translation، به pf نیاز است. imageهای OCI بومی FreeBSD بهترین عملکرد را دارند. imageهای Linux علاوه بر این به لایه سازگاری Linux نیاز دارند. همچنین تا October 2026، پیام نصب خود port مربوط به Podman در FreeBSD همچنان آن را آزمایشی و مناسب ارزیابی و تست معرفی میکند. اگر deployment شما مجموعهای از imageهای Linux است، آن را روی Linux اجرا کنید. در خانواده RHEL، این کار به نصب Docker روی Rocky Linux یا AlmaLinux منجر میشود؛ در این حالت Podman برای بار دوم ظاهر میشود، چون پیش از آنکه چیزی نصب کنید، packageای است که مالک command مربوط به docker است.
آیا jailهای FreeBSD از containerهای Docker امنتر هستند؟
هر دو از یک kernel میزبان استفاده میکنند. بنابراین یک باگ kernel برای هر دو خطر محسوب میشود و هیچکدام مرزی نیستند که برای کد واقعاً غیرقابلاعتماد انتخاب کنید. تفاوت در نقطه شروع است. jail با مجموعه گستردهای از عملیات مسدودشده آغاز میشود و شما آن عملیات را یکییکی با parameterها دوباره فعال میکنید. container مربوط به Docker با root درون مجموعهای از namespaceها آغاز میشود؛ برخی capabilityها حذف شدهاند و hardening بیشتر بهصورت opt-in انجام میشود. در عمل، configuration بیش از مدل معماری تعیینکننده است: jailای که allow.mount و allow.raw_sockets در آن فعال باشد، از containerای که با دقت پیکربندی شده است امنتر نیست.
چگونه از یک jail backup بگیرم؟
از dataset snapshot بگیرید و آن را ارسال کنید. sudo zfs snapshot zroot/jails/containers/web@backup، سپس آن snapshot را با zfs send به pool دیگری بفرستید یا داخل فایلی قرار دهید که از سرور خارج میکنید. از آنجا که یک jail تمام userland خود را در یک dataset نگه میدارد، snapshot packageهای نصبشده و data را در یک نقطه زمانی سازگار ثبت میکند و تمام فایلهای configuration را که بهصورت دستی ویرایش کردهاید نیز شامل میشود. این روش نقطه مقابل رویکرد معمول Docker است؛ در آنجا از named volumeها و فایل Compose backup میگیرید و بقیه محیط را از image دوباره میسازید.
آیا به BastilleBSD نیاز دارم یا base system کافی است؟
base system کافی است و نقطه شروع بهتری محسوب میشود. jail.conf، jls، jexec و service jail start کل این مدل را پوشش میدهند. وقتی آنها را یاد بگیرید، میتوانید هر host مربوط به FreeBSD را بدون آنکه ابتدا tooling همان host را یاد بگیرید، بررسی کنید. Bastille یک لایه convenience روی این قابلیتهاست: releaseها را bootstrap میکند، jailهای thin میسازد، templateها را اعمال میکند و ruleهای redirect مربوط به pf را برای شما مینویسد. ابتدا commandهای پایه را یاد بگیرید و سپس وقتی تعداد jailها باعث شد واردکردن commandها tedious شود، Bastille را اضافه کنید.