تفاوت FreeBSD Jails و Docker containers چیست؟
در این مقاله تفاوتهای ساختاری FreeBSD Jails و Docker containers را بررسی میکنیم. متوجه شوید چرا Jail یک سیستمعامل کامل و Docker تنها یک پردازش ایزوله است.
مقایسه FreeBSD jails و Docker containers در یک پاراگراف
FreeBSD jails و Docker containers مسئله مشابهی را در دو قالب متفاوت حل میکنند. هر دو محیطهای کاربری ایزولهشدهای را روی یک هسته (kernel) مشترک اجرا میکنند، بنابراین هیچکدام ماشین مجازی نیستند. تفاوت در محتوای آنهاست. یک Docker container تنها یک پردازش را از یک ایمیج لایهبندیشده که از یک registry دریافت کردهاید، اجرا میکند. در مقابل، یک jail یک محیط کاربری کامل FreeBSD را اجرا میکند: /etc اختصاصی خود، اسکریپتهای راهاندازی rc مخصوص به خود، دیتابیس pkg مجزا و هر تعداد پردازشی که بخواهید. تقریباً تمام تفاوتهای دیگر در این صفحه، ناشی از همین تفاوت بنیادین است.
پلتفرم SSD Nodes ایمیجهای FreeBSD ارائه نمیدهد. شما نمیتوانید یک سرور FreeBSD در این پلتفرم اجاره کنید و مطالب زیر راهنمای نصب برای ماشینی که میتوانید از اینجا تهیه کنید، نیست. این صرفاً مقایسهای بین دو مدل ایزولهسازی است تا بتوانید تشخیص دهید هر workload واقعاً به کدامیک نیاز دارد و بتوانید تنظیمات تیم FreeBSD را بدون حدس و گمان مطالعه کنید.
Jail دقیقاً چیست
Jailها در مارس 2000 با FreeBSD 4.0 معرفی شدند؛ یعنی قدیمیتر از cgroups و حدود یک دهه قدیمیتر از Docker هستند. این مکانیزم بر پایه یک فراخوانی هسته (kernel call) استوار است. jail(8) یک درخت دایرکتوری را میگیرد و پردازشها را درون آن با یک jail ID ضمیمه شده اجرا میکند؛ سپس هسته از انجام مجموعهای از عملیاتهای مشخص برای هر پردازشی که آن ID را داشته باشد، جلوگیری میکند. یک پردازش در Jail نمیتواند پردازشهای خارج از Jail خود را ببیند، نمیتواند فایلسیستمها را mount یا unmount کند، نمیتواند ماژولهای هسته را بارگذاری کند و نمیتواند به آدرسهای شبکهای که به آن Jail اختصاص داده نشده است، bind شود. هیچ نوع namespace مجزایی برای یادگیری یا قابلیت انتخابی (opt-in) برای هر ویژگی وجود ندارد: محدودیتها به صورت یک واحد یکپارچه اعمال میشوند و توسط پارامترهای موجود در فایل پیکربندی Jail تنظیم میگردند.
در سیستم میزبان (host)، دستور jls لیست Jailهای در حال اجرا را نمایش میدهد و jexec web sh شما را به یک shell درون Jail با نام web وارد میکند.
شما یک Jail را با قرار دادن یک userland از FreeBSD در یک دایرکتوری میسازید. سیستم پایه این کار را برای شما انجام میدهد:
sudo bsdinstall jail /usr/local/jails/containers/webاین دستور مجموعه توزیع پایه را برای نسخه شما دریافت کرده و مراحل معمول پس از نصب را اجرا میکند، بنابراین شما رمز عبور 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
jlsjls اکنون باید web را به همراه JID، نام میزبان و آدرس IP آن فهرست کند. اگر Jail ظاهر نشد، sudo jail -c web را مستقیماً اجرا کنید. این دستور همان پیکربندی را در پیشزمینه (foreground) اعمال میکند و پارامتری که قادر به پذیرش آن نبوده را چاپ میکند، به جای اینکه خطا را در خروجی سرویس رها کند.
خطی که ارزش دو بار خواندن دارد exec.start = "/bin/sh /etc/rc" است. شروع یک Jail، اسکریپت بوت معمول FreeBSD را در داخل آن اجرا میکند، بنابراین Jail هر سرویسی که در /etc/rc.conf خودش فعال شده باشد را بالا میآورد. یک کانتینر Docker معادل این مرحله را ندارد، زیرا فقط پردازش entrypoint تصویر را اجرا میکند و با توقف آن پردازش، کانتینر نیز متوقف میشود.
نحوه دریافت نرمافزار: ایمیجها و رجیستریها در مقابل محیط کاربری که خودتان میسازید
این تفاوتی است که در همان روز اول احساس میکنید.
در Docker شما نام نرمافزار را مشخص میکنید و آن را دریافت میکنید. docker pull nginx یک ایمیج لایهبندیشده و مبتنی بر محتوا را که توسط شخص دیگری ساخته و تست شده است، دریافت میکند و docker compose up -d آن را به همراه Volumeها و شبکه متصل به آن اجرا میکند. رجیستری در واقع همان محصول است. بخش بزرگی از ارزش گردشکار Docker در این است که هزاران پروژه، یک ایمیج آماده به کار منتشر میکنند؛ همین موضوع باعث میشود اجرای Docker روی یک VPS به جای یک پروژه طولانی، به یک کار کوتاه تبدیل شود.
FreeBSD هیچ رجیستری عمومی پیشفرضی برای ایمیجهای jail ندارد. شما یک محیط کاربری (userland) خالی ایجاد میکنید و نرمافزار را درون آن نصب میکنید، دقیقاً همانطور که یک سرور خام را راهاندازی میکنید. این کار نیاز به تایپ بیشتری دارد. اما شفافتر هم هست، زیرا آنچه در jail اجرا میشود همان چیزی است که pkg در آن قرار داده است، یعنی همان مجموعه بستههایی که میزبان (host) از آنها استفاده میکند.
ابزارها این فرآیند را کوتاه میکنند. BastilleBSD مدیر رایج jail است و خود یک بسته (package) محسوب میشود:
sudo pkg install bastille
sudo sysrc bastille_enable=YES
sudo bastille setup
sudo bastille bootstrap 15.1-RELEASEbastille setup شبکه، فضای ذخیرهسازی و فایروال را برای شما پیکربندی میکند. bastille bootstrap یک نسخه (release) را یکبار دانلود میکند و هر jail دیگری که بعد از آن بسازید، از همان استفاده مجدد میکند. FreeBSD 15.1 نسخه عملیاتی فعلی است که در ژوئن 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 webbastille console web به شما یک shell برای ورود به داخل jail میدهد و bastille list آنچه را که روی میزبان وجود دارد نشان میدهد. برای تکرار یک build، قالبهای Bastille مراحل را در یک فایل نگه میدارند و آنها را روی یک jail اعمال میکنند؛ این نزدیکترین چیزی است که در این دنیا به Dockerfile وجود دارد. یک قالب روی هر jail بازپخش میشود. هیچچیز از پیش ساختهشدهای وارد نمیشود.
بنابراین خلاصه صادقانه کوتاه است. Docker ساختههای دیگران را به شما میدهد. Jails به شما اجازه میدهد نصبهای خودتان را داشته باشید. اگر نرمافزار موجود در لیست شما فقط به صورت container image عرضه میشود، این موضوع پیش از بررسی هر فاکتور دیگری، تکلیف انتخاب را روشن میکند.
وضعیت و ارتقا: تغییرات بخش ZFS
Docker وضعیت (state) را بهصورت هدفمند تفکیک میکند. سیستمفایل کانتینر دورریختنی است، دادههای شما در یک named volume یا یک bind mount قرار دارند و یک ارتقا شامل docker compose pull و سپس docker compose up -d است. کانتینر جایگزین میشود و هر چیزی که در 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 بررسی کنید؛ مسیر بالا چیدمانی است که در کتابچه راهنما (handbook) استفاده شده است. گرفتن snapshot حدود یک ثانیه زمان میبرد و تا زمانی که محتویات jail تغییر نکند، تقریباً هیچ فضایی اشغال نمیکند. اگر ارتقا باعث خرابی سرویس شود، rollback کل userland را به وضعیت قبلی بازمیگرداند، که شامل دیتابیس بستهها و فایلهای پیکربندی که ساعت 2 بامداد بهصورت دستی ویرایش کردهاید نیز میشود. Docker هیچ معادل داخلی برای این کار ندارد، زیرا مدل آن فرض را بر این میگذارد که شما هرگز به چنین قابلیتی نیاز ندارید.
zfs clone نیمه دیگر ماجراست. یک clone از یک snapshot، یک jail جدید و قابلنوشتن است که بلوکهای تغییرنیافته را با والد خود به اشتراک میگذارد؛ بنابراین یک کپی staging از یک jail با حجم 3 گیگابایت، تا زمانی که شروع به تغییر آن نکنید، تقریباً هیچ هزینهای روی دیسک ندارد. این همان روشی است که یک مدیر سیستم FreeBSD برای ساخت یک jail «دقیقاً مشابه محیط عملیاتی» جهت تمرین ارتقا استفاده میکند.
ارتقای سیستم پایه از بستهها جداست. برای 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 شما را مجبور میکند مدل را از ابتدا انتخاب کنید، و دو مدل وجود دارد.
Shared IP. در این مدل ip4.addr = "10.0.0.10" آن آدرس را به یک رابط (interface) موجود در host اضافه میکند و jail را به آن محدود میسازد. jail پشته شبکه (network stack) اختصاصی خود را ندارد، بنابراین نمیتواند فایروال خود را اجرا کند. همچنین نمیتواند واقعاً به همه آدرسها bind شود: یک سوکت در jail که درخواست 0.0.0.0 میکند، توسط هسته (kernel) به آدرس اختصاصی همان jail بازنویسی میشود. دو jail نمیتوانند همزمان روی پورت 80 از یک آدرس واحد گوش دهند، بنابراین یا به هر کدام یک آدرس میدهید، یا یک reverse proxy در مقابل آنها قرار میدهید.
VNET. با افزودن vnet; به jail، آن jail یک پشته شبکه کامل دریافت میکند: رابطهای اختصاصی، جدول مسیریابی اختصاصی و قوانین فایروال اختصاصی خود. شما آن را با یک epair به host متصل میکنید؛ یک کابل مجازی که یک سر آن در هر طرف قرار دارد، و سرِ سمت host را روی یک bridge میگذارید. این مدل نزدیکترین شباهت را به آنچه Docker ارائه میدهد دارد و همان حالتی است که در پسزمینه انواع jailهای -V و -B در Bastille قرار دارد.
فوروارد کردن یک پورت host به داخل یک jail، یک قانون تغییر مسیر (redirect) از نوع pf است. Bastille این فرآیند را بستهبندی میکند:
sudo bastille rdr web tcp 80 80در اینجا هیچ EXPOSE و هیچ انتشار خودکاری وجود ندارد. هیچچیز به یک jail نمیرسد مگر اینکه آدرس آن یا یک قانون تغییر مسیر، اجازه آن را صادر کند. این شروعی کندتر و فایروالی بسیار ساکتتر است.
محدودیتهای منابع: cgroups در مقابل rctl
Docker یک کانتینر را با استفاده از cgroups محدود میکند و این محدودیتها در همان جایی تعریف میشوند که کانتینر تعریف شده است: --memory=1g --cpus=1.5 در خط فرمان، یا کلیدهای متناظر در یک فایل Compose. اگر پشته (stack) خود را در یک فایل Docker Compose روی یک VPS نگهداری میکنید، محدودیت در کنار سرویسی که به آن اعمال میشود قرار میگیرد و همراه با آن در git جابهجا میشود.
FreeBSD از rctl استفاده میکند و این زیرسیستمی است که باید آن را فعال کنید. حسابرسی منابع بهصورت پیشفرض غیرفعال است، زیرا در هر تخصیص منابع، هزینهٔ اندکی دارد. پارامتر تنظیمپذیر (tunable) را به /boot/loader.conf اضافه کنید و سیستم را reboot کنید:
kern.racct.enable=1سپس یک قانون تعریف کرده و آن را پایش کنید:
sudo rctl -a jail:web:vmemoryuse:deny=1g
rctl -hu jail:webدستور rctl -hu jail:web میزان استفادهٔ فعلی jail را با واحدهای قابلفهم برای انسان چاپ میکند، بنابراین میتوانید ببینید پیش از بروز هرگونه اختلال، چقدر به محدودیت نزدیک شدهاید. عمل deny باعث میشود تخصیص منابع بیش از حد مجاز در داخل jail با شکست مواجه شود، بنابراین بهجای مشاهدهٔ پیام kill در میزبان (host)، خطای تخصیص خودِ برنامه را خواهید دید.
قوانینی که با rctl -a اضافه میشوند، با reboot بعدی از بین میروند. سرویس rctl در FreeBSD آنها را از /etc/rctl.conf بارگذاری مجدد میکند، بنابراین قانون را در آن فایل بنویسید و سرویس را فعال کنید:
sudo sysrc rctl_enable=YESاین همان نقطهای است که Docker بهوضوح راحتتر است. محدودیت در یک فایل Compose همراه با سرویسی که آن را محدود میکند بازبینی میشود. اما یک قانون rctl، خطی در یک فایل جداگانه است که به یک jail تعریفشده در جای دیگری اشاره دارد.
زمانی که پاسخ یک ماشین مجازی است: bhyve
یک jail از هسته (kernel) میزبان استفاده میکند، بنابراین برخی قابلیتها برای همیشه خارج از دسترس هستند. این محیط نمیتواند نسخهٔ متفاوتی از هسته را اجرا کند، قادر به بارگذاری ماژول هسته نیست و نمیتواند باینریهای Linux را به شیوهای که کانتینرهای Linux اجرا میشوند، اجرا کند. سیستمعامل FreeBSD دارای یک لایهٔ سازگاری با Linux به نام linuxulator است، اما این لایه تنها زیرمجموعهای از فراخوانهای سیستمی (system calls) لینوکس را پیادهسازی میکند و پاسخ کاملی برای اجرای ایمیجهای دلخواه Linux نیست.
bhyve هایپروایزر (hypervisor) اختصاصی FreeBSD است و زمانی که به مرزهای یک ماشین واقعی نیاز دارید، ابزار مناسبی محسوب میشود: برای اجرای یک سیستمعامل متفاوت، یک هستهٔ متفاوت، یا زمانی که نمیخواهید هستهٔ سیستم را با یک مستأجر (tenant) به اشتراک بگذارید. هزینهٔ این کار، اختصاص حافظهای است که بهجای اشتراکگذاری، رزرو میشود و همچنین مدیریت یک هستهٔ دوم برای اعمال وصلههای امنیتی. این همان تصمیمی است که در Linux میان استفاده از کانتینرها و ماشینهای مجازی کامل میگیرید و همین تصمیم تعیین میکند که آیا به یک VPS که از nested virtualization پشتیبانی میکند در لایهٔ زیرین نیاز دارید یا خیر.
اکوسیستم؛ دلیل صادقانهای که اکثر تیمها از Docker استفاده میکنند
تمام مطالب بالا مربوط به مدل فنی است. آنچه انتخاب اکثر تیمها را تعیین میکند، وسعت دنیای پیرامون هر یک از این ابزارهاست.
Docker با خود Docker Hub و GHCR، docker compose، قابلیت استفاده از Kubernetes در زمانی که یک سرور دیگر پاسخگو نیست، CI runnerهایی که از پیش برای پشتیبانی از کانتینر سیمکشی شدهاند، و دستورات quickstart در فایل README تقریباً تمام پروژهها را به همراه میآورد. Jails با خود FreeBSD ports tree را میآورد که بزرگ و بهدقت نگهداری میشود، بهعلاوه مجموعهای بسیار کوچکتر از بستههای آمادهٔ اجرا برای برنامهها. وقتی پروژهای فقط یک container image منتشر میکند و هیچ چیز دیگری ارائه نمیدهد، مسیر FreeBSD این است که مستندات آن را بخوانید و قطعات را خودتان کنار هم بچینید.
Jails جایگاه خود را در سوی دیگر این معامله به دست میآورند. زمانی به آنها نیاز دارید که از قبل ZFS را اجرا میکنید و برای قابلیت snapshot و rollback کل یک سرویس ارزش قائل هستید، یا زمانی که سرویسهای شما بومی FreeBSD هستند، یا وقتی به جای یک پردازش واحد، به یک userland کامل برای هر tenant نیاز دارید، یا زمانی که میخواهید هسته سیستمعامل، packet filter، سیستم فایل و مستندات همگی به عنوان یک سیستم واحد نگهداری شوند. نکته آخر همان چیزی است که منظور افراد از منسجم (coherent) خواندن FreeBSD است؛ موضوعی که در مقایسه گستردهتر Linux و FreeBSD به عنوان پلتفرمهای سرور و در تغییرات FreeBSD 15 برای استفاده در سرور فضای بیشتری به آن اختصاص داده شده است.
یک قضاوت نهایی: اگر تیم شما از قبل Docker را میشناسد، هزینه مهاجرت واقعی است و سود حاصل از آن باید مشخص و ملموس باشد. صرفاً به خاطر کیفیت ایزولاسیون تغییر مسیر ندهید؛ این دو مدل آنقدر به هم نزدیک هستند که پیکربندی شما اهمیت بیشتری نسبت به انتخاب ابزار دارد. تنها در صورتی مهاجرت کنید که به قابلیت rollback کل سرویسها با پشتیبانی ZFS نیاز دارید، یا از قبل در حال استفاده از FreeBSD هستید.
FAQ
آیا میتوانم ایمیجهای Docker را روی FreeBSD اجرا کنم؟
خیر، ایمیجهای لینوکسی پشتیبانی نمیشوند و مسیر استانداردی برای این کار وجود ندارد. FreeBSD از کانتینرهای OCI پشتیبانی میکند: sudo pkg install -y podman-suite ابزار Podman را نصب میکند که کانتینرها را از طریق ocijail اجرا میکند؛ یک runtime که در لایه زیرین، jailهای واقعی ایجاد میکند. این ابزار به fdescfs که روی /dev/fd مونت شده باشد برای مانیتورینگ کانتینر، و pf برای NAT (ترجمه آدرس شبکه) کانتینر نیاز دارد. ایمیجهای OCI که بومی FreeBSD هستند بهترین عملکرد را دارند. ایمیجهای لینوکسی علاوه بر این به لایه سازگاری لینوکس نیاز دارند و تا آگوست 2026، پورت Podman در FreeBSD همچنان به عنوان آزمایشی شناخته میشود. اگر استک شما شامل ایمیجهای لینوکسی است، آن را روی لینوکس اجرا کنید.
آیا jailهای FreeBSD نسبت به کانتینرهای Docker امنتر هستند؟
هر دو از یک هسته (kernel) میزبان استفاده میکنند، بنابراین یک باگ در هسته برای هر دو ریسک محسوب میشود و هیچکدام مرز امنیتی مناسبی برای کدهای کاملاً غیرقابلاعتماد نیستند. تفاوت در نقطه شروع است. یک jail با مجموعهای گسترده از عملیاتهای غیرمجاز شروع میشود و شما باید آنها را یکبهیک فعال کنید. یک کانتینر Docker به عنوان root در مجموعهای از namespaceها شروع میشود که برخی قابلیتهای آن حذف شده است و سختسازی بیشتر (hardening) اختیاری است. در عمل، پیکربندی بیش از مدل امنیتی تعیینکننده است: یک jail که با allow.mount و allow.raw_sockets فعال اجرا شود، لزوماً امنتر از یک کانتینر با پیکربندی دقیق نیست.
چگونه از یک jail نسخه پشتیبان تهیه کنم؟
از dataset مربوطه snapshot بگیرید و آن را ارسال کنید. sudo zfs snapshot zroot/jails/containers/web@backup را اجرا کنید و سپس آن snapshot را با zfs send به یک pool دیگر یا فایلی که از سرور کپی میکنید، انتقال دهید. از آنجا که یک jail تمام userland خود را در یک dataset نگه میدارد، snapshot تمامی بستههای نصبشده و دادهها را در یک نقطه زمانی منسجم، به همراه تمام فایلهای پیکربندی که دستی ویرایش کردهاید، ذخیره میکند. این دقیقاً برعکس عادت Docker است که در آن از volumeهای نامگذاریشده و فایل Compose نسخه پشتیبان میگیرید و بقیه موارد را از روی ایمیج بازسازی میکنید.
آیا به BastilleBSD نیاز دارم یا سیستم پایه کافی است؟
سیستم پایه کافی است و نقطه شروع بهتری است. دستورات jail.conf، jls، jexec و service jail start کل مدل را پوشش میدهند و وقتی آنها را بشناسید، میتوانید بدون نیاز به یادگیری ابزارهای خاص هر میزبان، هر سیستم FreeBSD را مدیریت کنید. Bastille یک لایه راحتی روی سیستم پایه است: این ابزار نسخهها را bootstrap میکند، jailهای سبک میسازد، قالبها را اعمال میکند و قوانین هدایت pf را برای شما مینویسد. ابتدا دستورات پایه را یاد بگیرید و زمانی که تعداد jailها تایپ کردن دستورات را خستهکننده کرد، از Bastille استفاده کنید.