تفاوت Podman و Docker در سرور مجازی (VPS)
تفاوت اصلی در نبود daemon و اجرای rootless است. این مقاله بررسی میکند که چگونه این معماری روی فایلهای compose، مدیریت پورتهای زیر 1024 و مالکیت volumeها در سرور تاثیر میگذارد.
تفاوت واقعی بین Podman و Docker چیست
Podman و Docker هر دو ایمیجهای یکسانی با استاندارد OCI (مخفف Open Container Initiative) را روی یک VPS اجرا میکنند، بنابراین انتخاب بین آنها به نوع نرمافزاری که میتوانید اجرا کنید مربوط نمیشود. تفاوت اصلی در مدل پردازشی آنهاست. Docker یک daemon با دسترسی root اجرا میکند که مالکیت تمام کانتینرها را بر عهده دارد و دستور docker در واقع یک کلاینت کوچک است که از آن daemon میخواهد کار را انجام دهد. Podman فاقد daemon است: podman run کانتینر را به عنوان یک پردازش فرزند (child process) از هر چیزی که آن را فراخوانی کرده است، تحت کاربر بدون دسترسی ویژه (unprivileged) شما اجرا میکند.
تمام تفاوتهای دیگر از همین یک واقعیت ناشی میشوند. شروع خودکار (auto-start) به جای daemon، به وظیفه systemd تبدیل میشود. مالکیت volumeها از طریق یک user namespace عبور میکند، بنابراین مالکی که با دستور ls -l روی میزبان (host) میبینید، همان مالکی نیست که کانتینر مشاهده میکند. پورتهای زیر 1024 تا زمانی که یک تنظیم kernel را تغییر ندهید، اجازه bind شدن ندارند. رابط خط فرمان (CLI) با دستور docker از طریق یک wrapper به کار خود ادامه میدهد، تا زمانی که سرویسی بخواهد به Docker socket متصل شود.
بدون دیمون: هنگام اجرای یک کانتینر واقعاً چه چیزی اجرا میشود
روی یک میزبان Docker، دستور pstree -a نشان میدهد که dockerd با دسترسی root اجرا شده، containerd در کنار آن قرار دارد و به ازای هر کانتینر در حال اجرا، یک containerd-shim-runc-v2 وجود دارد. برنامه شما فرزند آن shim است و shim خود فرزند PID 1 محسوب میشود. هیچ اتصالی بین کانتینر و shell که آن را شروع کرده وجود ندارد. اگر دیمون را متوقف کنید، کنترل تمام کانتینرهای موجود روی سیستم را از دست میدهید و با غیرفعال بودن تنظیم پیشفرض live-restore، دستور systemctl restart docker نیز کانتینرهای شما را مجدداً راهاندازی نمیکند.
Podman هیچ پردازش معادلی ندارد. با شروع یک کانتینر، شما یک پردازش conmon (مانیتور کانتینر) دریافت میکنید که پردازش اصلی کانتینر را نگه میدارد و مالک آن کاربری است که دستور را اجرا کرده است.
podman run -d --name web -p 8080:80 docker.io/library/caddy:2
ps -o user,pid,ppid,args -C conmon
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080دستور ps باید conmon را در حال اجرا با نام کاربری شما و نه به عنوان root فهرست کند و curl باید 200 را چاپ کند. از آنجا که هیچ سرویس مرکزی مالک کانتینر نیست، sudo apt upgrade podman هیچ چیزی را که از قبل در حال اجراست متوقف نمیکند و کرش کردن مانیتور یک کانتینر نمیتواند باعث از کار افتادن سایر کانتینرها شود.
نبود دیمون هزینهای هم دارد. پس از reboot، هیچ چیزی کانتینرهای شما را بهطور خودکار شروع نمیکند. قابلیت --restart=always در Docker وعدهای است که دیمون در زمان بوت به آن عمل میکند، اما Podman آن را با systemd جایگزین کرده است که بخش quadlet در ادامه به همین موضوع میپردازد.
سوکت، نیمه دیگر این ماجراست. /var/run/docker.sock یک نقطه پایانی API (رابط برنامهنویسی اپلیکیشن) است که مالکیت آن با root است و هر پردازشی که بتواند روی آن بنویسد، میتواند یک کانتینر دارای امتیاز (privileged) را شروع کند که فایلسیستم میزبان را mount میکند. افزودن یک کاربر به گروه docker به آن کاربر دسترسی root از مسیری غیرمستقیم میدهد که ارزش دارد در کنار دادن حداقل دسترسی مورد نیاز به هر حساب کاربری مطالعه شود. Podman تا زمانی که خودتان درخواست نکنید، هیچ سوکتی را در معرض قرار نمیدهد و سوکتی که دریافت میکنید متعلق به یک کاربر واحد در مسیر /run/user/<uid>/podman/podman.sock است.
نصب Podman روی Ubuntu 24.04 و تایید عملکرد rootless
sudo apt update
sudo apt install -y podman uidmap
podman --version
podman info | grep -i rootlessبسته uidmap شامل newuidmap و newgidmap است. اینها ابزارهای کمکی setuid هستند که به یک کاربر عادی اجازه میدهند محدودهای از شناسههای فرعی (subordinate IDs) را در اختیار بگیرد؛ بدون این ابزارها، کانتینرهای rootless اجرا نمیشوند. دستور podman info باید خروجی rootless: true را نمایش دهد.
توزیع Ubuntu 24.04 همراه با Podman 4.9 و Debian 13 همراه با Podman 5.x عرضه میشود که در اوت 2026 بررسی شد. این تفاوت نسخه اهمیت دارد، زیرا فایلهای quadlet به نسخه 4.4 یا جدیدتر نیاز دارند و فایلهای quadlet .pod به نسخه 5.0 نیازمندند. پیش از کپی کردن یک نمونه از مستندات upstream، دستور podman --version را اجرا کنید.
هر کاربر rootless به یک محدوده شناسه فرعی نیاز دارد:
grep "$USER" /etc/subuid /etc/subgidکاربری که توسط adduser در Ubuntu ایجاد میشود، بهطور خودکار یک محدوده دریافت میکند. کاربری که توسط useradd -M یا ابزارهای پیکربندی ساخته شده باشد، اغلب فاقد این محدوده است و خطا دقیقاً همین موضوع را اعلام میکند:
Error: cannot find UID/GID for user deploy: no subuid ranges found for user "deploy" in /etc/subuid - check rootless mode in man pages.یک محدوده اختصاص دهید و سپس storage آن کاربر را بازنشانی کنید تا نگاشت جدید اعمال شود:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrateیک نکته غافلگیرکننده دیگر در اولین اجرا: Podman بهصورت پیشفرض Docker Hub را در نظر نمیگیرد. نام کوتاه image در برابر unqualified-search-registries در فایل /etc/containers/registries.conf بررسی میشود و در اسکریپتی که به ترمینال متصل نیست، عملیات pull با خطای short-name resolution enforced but cannot prompt without a TTY شکست میخورد. همیشه نام کامل را بنویسید. بهجای nginx از docker.io/library/nginx:1.27 استفاده کنید.
مزایای واقعی کانتینرهای rootless در سرورهای اجارهای
یک کانتینر rootless درون یک user namespace اجرا میشود؛ قابلیتی در هسته (kernel) که به یک پردازش، نگاشت اختصاصی خود از شناسههای کاربری (UID) را میدهد. درون این namespace، کاربر superuser کانتینر دارای UID 0 است. اما در خارج از آن و روی VPS شما، همان پردازش در واقع کاربر معمولی شماست. بنابراین، root در کانتینر به معنای root در سیستم میزبان نیست.
این تمام مزیت واقعی این روش است. یک image که اصرار دارد با دسترسی root اجرا شود، یک برنامه وب که دارای باگ اجرای کد از راه دور (RCE) است، یا یک فرار (escape) که وابسته به UID 0 بودن در خارج از کانتینر است؛ همگی در نهایت به جای دسترسیهای کل ماشین، تنها به دسترسیهای کاربر بدون امتیاز شما محدود میشوند. آنچه rootless انجام نمیدهد، محافظت در برابر باگهای هسته است؛ همچنین از فایلهای شخصی شما محافظت نمیکند، زیرا پردازشی که فرار کرده با هویت شما در حال اجراست و میتواند هر فایلی را که شما قادر به خواندنش هستید، بخواند. واحدی که ایزوله میشود حداقل به اندازه نگاشت UID اهمیت دارد؛ موضوعی که در یک FreeBSD jail که کل فضای کاربری را مانند یک ماشین کوچک مدیریت میکنید نسبت به یک image لایهبندی شده که از یک registry دریافت میشود، ملموستر است.
Docker نیز میتواند به صورت rootless اجرا شود. dockerd-rootless-setuptool.sh install یک daemon برای هر کاربر راهاندازی میکند و بهخوبی کار میکند. تفاوت در جهتگیری پیشفرض است. با Podman شما بدون درخواست اضافه، rootless را دریافت میکنید؛ بنابراین اولین خطای شما کانتینری است که نمیتواند پورت 80 را bind کند، نه سرویسی که دو سال بیسروصدا با دسترسی root اجرا شده است.
چرا فایلهای volume من متعلق به UID 100999 هستند؟
این موضوع به دلیل همان namespace کاربر است. UID 0 در کانتینر به UID میزبان شما نگاشت میشود. UID 1 در کانتینر به اولین ID در محدوده subuid شما نگاشت شده و از آنجا به بالا شمارش میشود. با محدودهای که از 100000 شروع میشود، UID 1000 کانتینر به صورت 100999 روی میزبان ظاهر میشود.
mkdir -p "$PWD/data"
podman run --rm --user 1000 -v "$PWD/data:/data" docker.io/library/alpine:3 sh -c 'id -u; touch /data/f'
ls -ln "$PWD/data"کانتینر عبارت 1000 را چاپ میکند. لیستگیری در میزبان، مالک را 100999 نشان میدهد، زیرا 100000 به علاوه 1000 منهای 1 برابر با 100999 است. هیچ چیزی خراب نیست و یک دستور ساده chown مشکل را حل نخواهد کرد، زیرا کاربر بدون دسترسی ریشه (unprivileged) شما اصلاً نمیتواند مالکیت فایل را خارج از namespace تغییر دهد.
چهار راه حل وجود دارد:
- دستور
podman unshare chown 1000:1000 "$PWD/data"عملیات chown را در همان namespace کاربر اجرا میکند، جایی که اعداد همان معنایی را دارند که برای کانتینر دارند. - دستور
-v "$PWD/data:/data:U"از Podman میخواهد که مالکیت دایرکتوری منبع را برای شما اصلاح کند. از این گزینه روی یک دایرکتوری جدید استفاده کنید، نه روی دادههایی که برایتان اهمیت دارند. - دستور
--userns=keep-idUID میزبان شما را به همان UID داخل کانتینر نگاشت میکند، بنابراین فایلهای جدید با مالکیت شما ایجاد میشوند. - یک volume نامگذاریشده مانند
-v appdata:/dataاین مسئله را دور میزند، زیرا Podman آن را در فضای ذخیرهسازی خودتان و با مالکیت از پیش اصلاحشده ایجاد میکند.
اگر در Docker با این مشکل مواجه شدهاید، این همان مشکل در یک لایه بالاتر است. متغیرهای PUID و PGID که بسیاری از imageها ارائه میدهند، UID مورد استفاده توسط فرآیند داخل کانتینر را تعیین میکنند و در حالت rootless Podman، آن UID برای بار دوم نگاشت میشود. دستور PUID=1000 داخل یک کانتینر rootless همچنان فایلهای میزبان را با مالکیت 100999 مینویسد. اعداد را با در نظر گرفتن آن نگاشت دوم انتخاب کنید، یا دادهها را به یک volume نامگذاریشده منتقل کنید و دیگر نگران آن نباشید.
دو نکته دیگر درباره mountها: فلگهای :z و :Z که در مثالهای Fedora و RHEL میبینید، گزینههای بازنشانی برچسب SELinux هستند و چون Ubuntu از AppArmor استفاده میکند، در آنجا هیچ کاری انجام نمیدهند. همچنین rootless Podman نمیتواند دایرکتوری میزبانی را که کاربر شما قادر به خواندن آن نیست mount کند، که این خود یک ویژگی امنیتی است نه یک نقص.
چرا Podman بدون دسترسی root اجازه انتشار پورت 80 را نمیدهد؟
زیرا bind کردن پورتهای زیر 1024 نیازمند امتیازی است که کاربر شما فاقد آن است. پیام خطا راه حل را مشخص میکند:
Error: rootlessport cannot expose privileged port 80, you can add 'net.ipv4.ip_unprivileged_port_start=80' to /etc/sysctl.conf (currently 1024), or choose a larger port number (>= 1024): listen tcp 0.0.0.0:80: bind: permission deniedدو راه حل وجود دارد. کاهش آستانه برای کل میزبان:
echo 'net.ipv4.ip_unprivileged_port_start=80' | sudo tee /etc/sysctl.d/99-podman.conf
sudo sysctl --system
sysctl net.ipv4.ip_unprivileged_port_startدستور آخر باید مقدار 80 را برگرداند. توجه داشته باشید که این تنظیم چه کاری انجام میدهد: اکنون هر کاربری روی سیستم میتواند پورتهای 80 و 443 را bind کند، نه فقط کاربری که کانتینرها را اجرا میکند. در یک VPS با یک مدیر واحد، این یک معامله قابلقبول است. اما در سیستمی که حسابهای کاربری دیگران روی آن قرار دارد، اینطور نیست. راه حل دیگر، انتشار روی پورت 8080 و قرار دادن یک reverse proxy در مقابل آن است؛ جایی که در هر صورت میخواهید گواهیها توسط certbot روی nginx صادر و تمدید شوند.
انتشار بدون دسترسی root، دید برنامه شما را نیز تغییر میدهد. Podman نسخه 4.x بهطور پیشفرض از slirp4netns با مدیریت پورت rootlesskit استفاده میکند و اتصالات فوروارد شده با آدرس مبدأ بازنویسیشده میرسند، بنابراین لاگ دسترسی، هر بازدیدکننده را به عنوان 10.0.2.100 ثبت میکند. Podman نسخه 5.0 پیشفرض را به pasta تغییر داد که آدرس واقعی کلاینت را حفظ میکند. در نسخه 4.x، استفاده از --network slirp4netns:port_handler=slirp4netns آدرس مبدأ واقعی را بازیابی میکند که البته هزینهای در توان عملیاتی (throughput) دارد.
یک نکته مثبت در اینجا وجود دارد. پورت منتشر شده در حالت بدون root، یک سوکت listening معمولی است که متعلق به یک پردازش عادی است، بنابراین قوانین ورودی فایروال شما روی آن اعمال میشود. Docker پورتها را با نوشتن قوانین NAT (ترجمه آدرس شبکه) به همراه قوانین پذیرش فورواردینگ خود منتشر میکند، و دقیقاً به همین دلیل است که یک پورت منتشر شده توسط Docker، قانون ufw که فکر میکردید آن را مسدود کرده است نادیده میگیرد. Podman با دسترسی root از ساختار مشابهی استفاده میکند و همان مشکل را به ارث میبرد. اما حالت بدون root اینگونه نیست.
آیا فایلهای Docker Compose من همچنان در Podman کار میکنند؟
در بیشتر موارد بله، از دو مسیر متفاوت. مسیر اول podman-compose است؛ یک پیادهسازی مجزا که همان فایل را میخواند و رابط خط فرمان Podman را هدایت میکند:
sudo apt install -y podman-compose
podman-compose up -d
podman psمسیر دوم استفاده از Docker Compose واقعی است که با API سازگار با Docker در Podman از طریق یک سوکت به ازای هر کاربر ارتباط برقرار میکند:
systemctl --user enable --now podman.socket
export DOCKER_HOST="unix:///run/user/$(id -u)/podman/podman.sock"
docker compose up -d
podman psdocker compose ps و podman ps باید کانتینرهای یکسانی را فهرست کنند، زیرا تنها یک مجموعه از آنها وجود دارد. وضوح نام (Name resolution) نیز به خوبی کار میکند: بکاِند شبکه پیشفرض Podman یعنی netavark، سرویس aardvark-dns را اجرا میکند، بنابراین کانتینرها در یک شبکه تعریفشده توسط کاربر، یکدیگر را با نام پیدا میکنند.
تفاوتهای جزئی اما واقعی وجود دارند. هر چیزی که /var/run/docker.sock را mount میکند باید به سوکت Podman اشاره کند یا حذف شود. network_mode: host تحت یک فضای نام کاربری (user namespace) رفتار متفاوتی دارد. depends_on با condition: service_healthy در نسخههای مختلف podman-compose به شکل یکسانی پشتیبانی نمیشود. restart: always بهتنهایی پس از reboot باقی نمیماند، که بخش بعدی این مشکل را حل میکند. Compose همچنان روش مناسبی برای توصیف یک پشته چند-کانتینری در یک فایل واحد است و در Podman به عنوان یک لایه ترجمه عمل میکند. برای پشتهای که قصد دارید سالها از آن نگهداری کنید، آن را به quadlets تبدیل کنید تا به جای دو انتزاع، تنها یک مورد را مدیریت کنید.
پادها: ایدهای که Docker پاسخی برای آن ندارد
پاد (Pod) گروهی از کانتینرهاست که یک فضای نام شبکه (network namespace) مشترک دارند. Podman یک کانتینر کوچک infra را اجرا میکند تا آن فضای نام را باز نگه دارد، سپس اعضا بدون نیاز به شبکه تعریفشده توسط کاربر یا سرویس دیسکاوری، از طریق 127.0.0.1 به یکدیگر دسترسی پیدا میکنند.
podman pod create --name app -p 8080:80
podman run -d --pod app --name app-cache docker.io/library/redis:7
podman run -d --pod app --name app-web docker.io/library/nginx:1.27
podman pod ps
podman ps --podدستور podman pod ps باید پاد Running را با سه کانتینر (با احتساب کانتینر زیرساختی) نشان دهد. اکنون کانتینر وب به جای app-cache:6379، از طریق 127.0.0.1:6379 به Redis دسترسی دارد. دو قانون از فضای نام مشترک پیروی میکنند: پورتها را روی پاد منتشر کنید و نه روی اعضا، و هیچ دو عضوی نباید روی یک پورت یکسان گوش دهند (listen).
این مدل Kubernetes است و Podman از آن پیروی میکند. دستور podman kube generate app > app.yaml یک مانیفست Kubernetes از آنچه در حال اجراست مینویسد (در بستههای قدیمیتر با نام podman generate kube شناخته میشود) و podman kube play app.yaml آن را روی میزبان دیگری بازسازی میکند. Quadlet یک نوع واحد .kube دارد که چنین فایلی را به عنوان یک سرویس systemd اجرا میکند. این روشی واقعاً متفاوت برای گروهبندی سرویسهاست و قویترین دلیل برای انتخاب Podman در صورتی است که Kubernetes در آینده کاری شما جایگاهی داشته باشد.
اجرای خودکار بدون دیمون: واحدهای Quadlet
Quadlet یک تولیدکننده برای systemd است. این ابزار یک فایل کوتاه که کانتینر را توصیف میکند، در زمان بوت به یک سرویس واقعی systemd تبدیل میکند. فایلها برای کاربر rootless در مسیر ~/.config/containers/systemd/ و برای کاربر root در مسیر /etc/containers/systemd/ قرار میگیرند.
~/.config/containers/systemd/caddy.container:
[Unit]
Description=Caddy web server
After=network-online.target
[Container]
Image=docker.io/library/caddy:2
ContainerName=caddy
PublishPort=8080:80
Volume=caddy-data.volume:/data
Environment=TZ=UTC
AutoUpdate=registry
[Service]
Restart=always
MemoryMax=512M
[Install]
WantedBy=default.target~/.config/containers/systemd/caddy-data.volume میتواند تقریباً خالی باشد، زیرا عنوان بخش همان چیزی است که volume را ایجاد میکند:
[Volume]systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50نام سرویس از نام فایل گرفته میشود: caddy.container به caddy.service تبدیل میشود. دستور systemctl --user enable caddy را اجرا نکنید. واحدهای تولیدشده را نمیتوان فعال کرد و systemd پاسخ Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated. را میدهد. بخش [Install] همان چیزی است که کانتینر را در زمان بوت شروع میکند و daemon-reload دستوری است که واحد را پس از ویرایش فایل، بازتولید میکند.
اکنون تنظیمی که تقریباً همه را دچار مشکل میکند:
sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Lingerانتظار Linger=yes را داشته باشید. بدون قابلیت linger، وقتی آخرین اتصال SSH شما بسته میشود، systemd کل نشست کاربر را از بین میبرد؛ بنابراین هر کانتینر rootless همراه با آن متوقف میشود و هیچکدام در زمان بوت بازنمیگردند. کانتینرهایی که با خروج شما از سیستم ناپدید میشوند، همیشه به همین دلیل است.
از آنجا که کانتینر، پردازش اصلی یک واحد سرویس معمولی است، کنترلهای خودِ systemd مستقیماً اعمال میشوند. MemoryMax= و CPUQuota= در بخش [Service] دقیقاً همانطور رفتار میکنند که برای هر سرویس دیگری که با systemd محدود میکنید عمل میکنند. این قابلیت به cgroup v2 (نسخه 2 گروه کنترل) نیاز دارد که اوبونتو از نسخه 22.04 بهصورت پیشفرض از آن استفاده میکند. با دستور podman info | grep -i cgroup این موضوع را تأیید کنید.
بهروزرسانیها دارای یک مکانیزم تطبیقی هستند. AutoUpdate=registry به همراه systemctl --user enable --now podman-auto-update.timer، رجیستری را برای یافتن ایمیج جدیدتر با همان تگ بررسی میکند، واحد را ریاستارت میکند و اگر کانتینر جدید شروع نشد، به ایمیج قبلی بازمیگردد (rollback). ابتدا دستور podman auto-update --dry-run را اجرا کنید تا ببینید چه تغییراتی اعمال خواهد شد. دستور قدیمیتر podman generate systemd همچنان وجود دارد اما منسوخ شده است، بنابراین برای هر چیز جدیدی از quadlet استفاده کنید.
جایی که alias داکر کار میکند و جایی که کار نمیکند
sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker pspodman-docker یک wrapper از نوع /usr/bin/docker نصب میکند که Podman را فراخوانی میکند. بدون فایل nodocker، هر فراخوانی ابتدا عبارت Emulate Docker CLI using podman. Create /etc/containers/nodocker to quiet msg. را چاپ میکند. این wrapper دستوراتی را که در طول روز استفاده میکنید پوشش میدهد: run، ps، logs، exec، build، pull، push، inspect، cp، volume، network.
مواردی که منتقل نمیشوند، لیست کوتاهتر و البته حساستری دارند. حالت Swarm هیچ معادلی ندارد، بنابراین یک stack مربوط به Swarm جایی برای اجرا ندارد. ابزارهایی که با socket داکر صحبت میکنند، نیاز دارند که socket مربوط به Podman را دریافت کنند و برخی از آنها همچنان متوجه تفاوت میشوند؛ provider داکر در Traefik زمانی کار میکند که به /run/user/<uid>/podman/podman.sock اشاره کند، در حالی که Watchtower اصلاً جایگاهی ندارد، زیرا podman auto-update این وظیفه را انجام میدهد. فضای ذخیرهسازی جداگانه است، بنابراین Podman نمیتواند imageهایی را که قبلاً با داکر دریافت کردهاید ببیند و podman images در یک host شلوغ داکر، در ابتدا خالی خواهد بود.
مهاجرت مرحلهبهمرحله یک استک در حال اجرا
- کاربر بدون دسترسی ریشه (unprivileged) که مالکیت کانتینرها را بر عهده خواهد داشت ایجاد یا انتخاب کنید و تأیید کنید که محدودهای در
/etc/subuidدارد. - هر چیزی را که از یک رجیستری دریافت شده است، با استفاده از نامهای کاملاً واجد شرایط (fully qualified names) دوباره دریافت (pull) کنید. Podman مخزن ایمیج اختصاصی خود را دارد و ایمیجهای Docker را نمیخواند.
- ایمیجهای ساختهشده بهصورت محلی را با استفاده از
docker save app:1.4 | podman loadمنتقل کنید. - کانتینر Docker را متوقف کنید، محتویات هر volume را از
/var/lib/docker/volumes/<name>/_dataکپی کنید و سپس مالکیت آنها را باpodman unshare chown -R 1000:1000 <path>اصلاح کنید. - مسئله پورتها را تعیین تکلیف کنید: پورتهای بالاتر از 1024 را پشت یک reverse proxy منتشر کنید یا
net.ipv4.ip_unprivileged_port_startرا تنظیم کنید. - برای هر کانتینر یک فایل quadlet بنویسید،
systemctl --user daemon-reloadرا اجرا کنید و هر سرویس را استارت بزنید. - دستور
sudo loginctl enable-linger <user>را اجرا کنید، VPS را ریبوت کنید، دوباره وارد شوید و بررسی کنید کهpodman psتمام سرویسها را دوباره لیست میکند.
این دو موتور هیچ منبع مشترکی ندارند: فضای ذخیرهسازی ایمیج و شبکهها کاملاً جدا هستند. بنابراین میتوانید در حین مهاجرت هر دو را همزمان اجرا کنید و تنها تداخل احتمالی بر سر شماره پورت میزبان خواهد بود. یک سرویس را منتقل کنید، یک روز آن را مانیتور کنید و سپس به سراغ سرویس بعدی بروید.
مقایسه Podman و Docker: کدامیک برای VPS شما مناسبتر است؟
اگر پشته (stack) شما در فایلهای compose قرار دارد که دیگران نیز آن را مدیریت میکنند، یا به ابزارهایی وابسته هستید که با Docker socket در ارتباطاند، از Docker استفاده کنید. سازگاری با آنچه دیگران مینویسند یک قابلیت واقعی است و Docker در این زمینه برتری دارد. تیمی که لپتاپهای اعضای آن همگی از Docker استفاده میکنند، با اجرای همان موتور در محیط production، مزایای ملموسی به دست میآورد.
اگر VPS شما تعداد انگشتشماری سرویس را اجرا میکند که کنترل کامل آنها در دست شماست، یا میخواهید هر برنامه تحت کاربر غیرممتاز (unprivileged) خود اجرا شود و هیچ گروه docker روی سیستم وجود نداشته باشد، به سراغ Podman بروید. هماهنگی با توزیع سیستمعامل نیز مهم است: RHEL و نسخههای بازسازیشده آن، Podman را به عنوان موتور پشتیبانیشده ارائه میدهند؛ بنابراین در این سیستمها، Podman مسیری با غافلگیریهای کمتر است. اگر با این وجود میخواهید روی یکی از این میزبانها Docker داشته باشید، مسیر dnf در Rocky Linux و AlmaLinux با حذف wrapper مربوط به podman-docker که از قبل مالک دستور docker در آنجاست، آغاز میشود. اگر از قبل همه چیز را با unitهای systemd مدیریت میکنید، quadletها برای شما مانند قطعهای گمشده خواهند بود که سر جای خود قرار میگیرد، نه ابزاری جدید برای یادگیری.
یک گزینه میانی نیز ارزش اشاره دارد. Podman با دسترسی root (Rootful) رفتاری بسیار شبیه به Docker دارد، دستور docker را از طریق wrapper حفظ میکند و همچنان daemon همیشه فعال را حذف مینماید. این حالت مزیت اجرای بدون root را از بین میبرد (بخشی که وضعیت امنیتی شما را تغییر میدهد)، بنابراین آن را به عنوان یک ایستگاه موقت در مسیر خود در نظر بگیرید.
اگر هنوز در حال ساخت اولین میزبان کانتینر خود هستید، مسیر راهاندازی و مقاومسازی Docker روی یک VPS تازه کوتاهترین راه است و هیچکدام از آن دانشها هدر نمیرود. Imageها و volumeها در هر دو موتور اشیاء یکسانی هستند، بنابراین مهاجرت در آینده فقط نحوه نظارت بر سرویسهای شما را تغییر میدهد و تغییرات دیگری نخواهد داشت.
FAQ
آیا Podman جایگزین مستقیم برای Docker است؟
برای دستوراتی که تایپ میکنید، تقریباً بله. نصب podman-docker به شما یک wrapper به نام /usr/bin/docker میدهد و دستورات run، ps، build، logs و exec به همان شکل عمل میکنند. این ابزار جایگزینی برای daemon نیست. Swarm معادلی ندارد، ابزارهایی که به /var/run/docker.sock متصل میشوند باید به جای آن به socket مخصوص کاربر در Podman اشاره کنند و imageهایی که توسط Docker دریافت شدهاند برای Podman قابل مشاهده نیستند، زیرا این دو ابزار فضای ذخیرهسازی جداگانهای دارند.
چرا کانتینرهای rootless Podman من هنگام خروج از SSH متوقف میشوند؟
زیرا systemd با بسته شدن آخرین نشست (session) شما، نشست کاربری و تمام سرویسهای آن را متوقف میکند. دستور sudo loginctl enable-linger <user> را اجرا کنید و سپس بررسی کنید که loginctl show-user <user> --property=Linger عبارت Linger=yes را چاپ کند. قابلیت Linger باعث میشود نمونهٔ systemd آن کاربر بدون نیاز به نشست فعال به کار خود ادامه دهد؛ این همان چیزی است که باعث میشود کانتینرها پس از reboot دوباره شروع به کار کنند.
چرا مالکیت فایلها در volume من با UID 100999 است؟
Rootless Podman مقدار UID 0 کانتینر را به کاربر host شما نگاشت میکند و سپس UID 1 و بالاتر را به محدوده subuid شما نگاشت میکند. با محدودهای که از 100000 شروع میشود، UID 1000 کانتینر به 100999 روی host تبدیل میشود. این مورد را از داخل namespace با podman unshare chown 1000:1000 /path/to/data اصلاح کنید، در اولین اجرا از flag :U استفاده کنید، یا از --userns=keep-id استفاده کنید تا UIDهای کانتینر با UID خودتان مطابقت داشته باشند.
آیا میتوانم همچنان از docker-compose.yml با Podman استفاده کنم؟
بله، به دو روش. podman-compose فایل را میخواند و مستقیماً CLI مربوط به Podman را هدایت میکند. یا socket سازگاری را با systemctl --user enable --now podman.socket فعال کنید، DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock را تنظیم کنید و docker compose واقعی را روی آن اجرا کنید. در مورد network_mode: host، سرویسهایی که Docker socket را mount میکنند و restart: always که برای بقا پس از reboot به یک unit از نوع quadlet و قابلیت linger نیاز دارد، انتظار چالش داشته باشید.
آیا rootless واقعاً کانتینرها را امنتر میکند؟
این قابلیت یک ریسک خاص را حذف میکند: فرآیندی که از یک کانتینر rootless فرار کند، به جای دسترسیهای root، تنها دسترسیهای کاربر غیرممتاز شما را خواهد داشت. این موضوع ارزشمند است و به همین دلیل است که گروه docker که معادل root است، در rootless Podman هیچ معادلی ندارد. این قابلیت جلوی آسیبپذیریهای kernel را نمیگیرد و از فایلهایی که کاربر خودتان میتواند بخواند محافظت نمیکند؛ بنابراین سایر اقدامات امنیتی که روی هر سروری انجام میدهید را همچنان رعایت کنید.