SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

تفاوت Podman و Docker در VPS: بررسی فنی و کاربردی

تفاوت اصلی Podman و Docker در نبود دیمون و اجرای rootless است. در این مطلب بررسی می‌کنیم که چگونه این معماری بر مدیریت Volume، پورت‌های زیر 1024 و Quadlet اثر می‌گذارد.

تفاوت واقعی بین Podman و Docker چیست

Podman و Docker هر دو ایمیج‌های یکسان OCI (مخفف Open Container Initiative) را روی یک VPS اجرا می‌کنند، بنابراین انتخاب بین آن‌ها به نوع نرم‌افزاری که می‌توانید اجرا کنید مربوط نمی‌شود. تفاوت اصلی در مدل پردازشی آن‌هاست. Docker یک دیمون (daemon) با دسترسی root اجرا می‌کند که مالک تمام کانتینرهاست و دستور docker در واقع یک کلاینت کوچک است که از آن دیمون می‌خواهد کار را انجام دهد. Podman فاقد دیمون است: podman run کانتینر را به عنوان یک پردازش فرزند از هر چیزی که آن را فراخوانی کرده، تحت همان کاربری که دسترسی ویژه (privileged) ندارد، اجرا می‌کند.

تمام تفاوت‌های دیگر از همین یک واقعیت ناشی می‌شوند. شروع خودکار (Auto-start) به جای دیمون، به وظیفه systemd تبدیل می‌شود. مالکیت Volumeها از طریق یک فضای نام کاربری (user namespace) مدیریت می‌شود، بنابراین مالکی که با دستور ls -l روی هاست می‌بینید، همان مالکی نیست که کانتینر مشاهده می‌کند. پورت‌های زیر 1024 تا زمانی که یک تنظیم هسته (kernel setting) را تغییر ندهید، اجازه 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 یک endpoint برای 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 هستند. پیش از کپی کردن مثال‌ها از مستندات رسمی، دستور 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 را در نظر نمی‌گیرد. نام کوتاه تصویر در /etc/containers/registries.conf با استفاده از unqualified-search-registries جستجو می‌شود و در اسکریپت‌هایی که به ترمینال متصل نیستند، عملیات pull با خطای short-name resolution enforced but cannot prompt without a TTY شکست می‌خورد. همیشه نام کامل تصویر را بنویسید. به‌جای nginx از docker.io/library/nginx:1.27 استفاده کنید.

مزایای واقعی کانتینرهای rootless روی سرور اجاره‌ای

یک کانتینر rootless درون یک user namespace اجرا می‌شود؛ قابلیتی در هسته سیستم‌عامل که به یک پردازش، نگاشت اختصاصی خودش از شناسه‌های کاربری (UID) را می‌دهد. درون این namespace، کاربر superuser کانتینر دارای UID 0 است. اما در خارج از آن و روی VPS شما، همان پردازش در واقع کاربر معمولی شماست. بنابراین، root در داخل کانتینر، به معنای root در سیستم میزبان نیست.

این تمام مزیت واقعی این روش است. یک ایمیج که اصرار دارد با دسترسی root اجرا شود، یک برنامه وب که دارای باگ اجرای کد از راه دور (RCE) است، یا یک روش فرار از کانتینر که به UID 0 بودن در خارج از محیط کانتینر وابسته است؛ همگی در نهایت به جای دسترسی‌های کل سیستم، تنها به مجوزهای کاربر غیرممتاز شما محدود می‌شوند. آنچه rootless انجام نمی‌دهد، محافظت از شما در برابر باگ‌های هسته (kernel) است. همچنین این روش از فایل‌های شخصی شما محافظت نمی‌کند، زیرا پردازشی که از کانتینر فرار کرده، با هویت شما در حال اجراست و می‌تواند هر فایلی را که شما به آن دسترسی دارید، بخواند.

Docker نیز می‌تواند به صورت rootless اجرا شود. dockerd-rootless-setuptool.sh install یک daemon اختصاصی برای هر کاربر راه‌اندازی می‌کند و به‌خوبی کار می‌کند. تفاوت در جهت‌گیری پیش‌فرض است. با Podman شما بدون نیاز به تنظیمات خاص، از همان ابتدا rootless هستید؛ بنابراین اولین خطای شما این خواهد بود که کانتینر نمی‌تواند پورت 80 را bind کند، نه اینکه سرویسی داشته باشید که دو سال بی‌سروصدا با دسترسی root اجرا شده باشد.

چرا مالکیت فایل‌های volume من با UID 100999 است؟

دلیل این موضوع همان user 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 را درون همان user namespace اجرا می‌کند، جایی که اعداد همان معنایی را دارند که برای کانتینر دارند.
  • دستور -v "$PWD/data:/data:U" از Podman می‌خواهد که مالکیت دایرکتوری منبع را برای شما اصلاح کند. از این دستور روی یک دایرکتوری تازه استفاده کنید، نه روی داده‌هایی که برایتان اهمیت دارند.
  • دستور --userns=keep-id UID میزبان شما را به همان UID درون کانتینر نگاشت می‌کند، بنابراین فایل‌های جدید با مالکیت خود شما ایجاد می‌شوند.
  • یک volume نام‌گذاری شده مانند -v appdata:/data این مسئله را دور می‌زند، زیرا Podman آن را درون فضای ذخیره‌سازی شما با مالکیت صحیح ایجاد می‌کند.

اگر در Docker با این موضوع مواجه شده‌اید، این همان مشکل در یک لایه بالاتر است. متغیرهای PUID و PGID که بسیاری از ایمیج‌ها ارائه می‌دهند، UID مورد استفاده توسط پردازش درون کانتینر را تعیین می‌کنند و در حالت rootless Podman، آن UID برای بار دوم نگاشت می‌شود. دستور PUID=1000 درون یک کانتینر rootless همچنان فایل‌های میزبان را با مالکیت 100999 می‌نویسد. اعداد را با در نظر گرفتن آن نگاشت دوم انتخاب کنید، یا داده‌ها را به یک volume نام‌گذاری شده منتقل کنید و دیگر نگران آن نباشید.

دو نکته دیگر درباره mountها: فلگ‌های :z و :Z که در مثال‌های Fedora و RHEL می‌بینید، گزینه‌های relabel کردن SELinux هستند و چون Ubuntu از AppArmor استفاده می‌کند، در آنجا هیچ کاری انجام نمی‌دهند. همچنین، rootless Podman نمی‌تواند دایرکتوری میزبانی را که کاربر شما اجازه خواندن آن را ندارد mount کند؛ این یک ویژگی امنیتی است، نه یک نقص.

چرا Podman بدون دسترسی root (rootless) از انتشار پورت 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 صادر و تمدید شوند.

انتشار پورت در حالت rootless، دیدگاه برنامه شما را نیز تغییر می‌دهد. Podman نسخه 4.x به‌صورت پیش‌فرض از slirp4netns با مدیریت پورت rootlesskit استفاده می‌کند و اتصالات فوروارد شده با آدرس مبدأ بازنویسی‌شده می‌رسند، بنابراین لاگ دسترسی، تمام بازدیدکنندگان را با آدرس 10.0.2.100 ثبت می‌کند. Podman نسخه 5.0 این پیش‌فرض را به pasta تغییر داد که آدرس واقعی کلاینت را حفظ می‌کند. در نسخه 4.x، استفاده از --network slirp4netns:port_handler=slirp4netns آدرس مبدأ واقعی را بازیابی می‌کند، هرچند این کار هزینه‌ای از نظر توان عملیاتی (throughput) دارد.

یک نکته مثبت در اینجا وجود دارد. پورت منتشر شده در حالت rootless، یک سوکت گوش‌دهنده معمولی است که متعلق به یک پردازش عادی است، بنابراین قوانین ورودی فایروال شما روی آن اعمال می‌شود. Docker پورت‌ها را با نوشتن قوانین NAT (ترجمه آدرس شبکه) به همراه قوانین پذیرش فورواردینگ خود منتشر می‌کند، و دقیقاً به همین دلیل است که یک پورت منتشر شده در Docker، قانون ufw که فکر می‌کردید آن را مسدود کرده است نادیده می‌گیرد. Podman در حالت rootful از ساختار مشابهی استفاده می‌کند و همان مشکل را به ارث می‌برد، اما حالت rootless این‌گونه نیست.

آیا فایل‌های Docker Compose من همچنان در Podman کار می‌کنند؟

بیشتر اوقات، بله؛ از دو مسیر متفاوت. مسیر اول podman-compose است، یک پیاده‌سازی مجزا که همان فایل را می‌خواند و CLI مربوط به 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 ps

docker 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 همچنان روش مناسبی برای توصیف یک stack چند کانتینری در یک فایل واحد است و در Podman به عنوان یک لایه ترجمه عمل می‌کند. برای stackهایی که قصد دارید سال‌ها از آن‌ها نگهداری کنید، آن‌ها را به quadlet تبدیل کنید تا به جای دو انتزاع، تنها یک انتزاع را مدیریت کنید.

پادها: ایده‌ای که Docker پاسخی برای آن ندارد

پاد (Pod) گروهی از کانتینرهاست که یک فضای نام شبکه (network namespace) مشترک دارند. Podman یک کانتینر کوچک infra را اجرا می‌کند تا آن فضای نام را باز نگه دارد، سپس اعضا از طریق 127.0.0.1 به یکدیگر دسترسی پیدا می‌کنند، بدون اینکه نیازی به تعریف شبکه توسط کاربر یا استفاده از سرویس discovery باشد.

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 دسترسی پیدا می‌کند. دو قانون از فضای نام مشترک حاصل می‌شود: پورت‌ها را روی پاد منتشر کنید و نه روی اعضا، و هیچ دو عضوی نباید روی یک پورت یکسان گوش دهند.

این مدل Kubernetes است و Podman از آن پیروی می‌کند. دستور podman kube generate app > app.yaml یک مانیفست Kubernetes از آنچه در حال اجراست می‌نویسد (در بسته‌های قدیمی‌تر نام آن podman generate kube است) و podman kube play app.yaml آن را روی میزبان دیگری بازسازی می‌کند. Quadlet یک نوع unit به نام .kube دارد که چنین فایلی را به عنوان یک سرویس systemd اجرا می‌کند. این روشی کاملاً متفاوت برای گروه‌بندی سرویس‌هاست و قوی‌ترین دلیل برای انتخاب Podman در صورتی است که Kubernetes در آینده کاری شما جایگاهی دارد.

راه‌اندازی خودکار بدون daemon: واحدهای Quadlet

Quadlet یک تولیدکننده برای systemd است. این ابزار یک فایل کوتاه که کانتینر را توصیف می‌کند، در زمان بوت به یک سرویس واقعی systemd تبدیل می‌کند. فایل‌ها برای یک کاربر بدون دسترسی root در ~/.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 را اجرا نکنید. واحدهای تولیدشده را نمی‌توان enable کرد و 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، رجیستری را برای یافتن image جدیدتر با همان تگ بررسی می‌کند، واحد را restart می‌کند و در صورت عدم شروع کانتینر جدید، به image قبلی بازمی‌گردد (rollback). ابتدا podman auto-update --dry-run را اجرا کنید تا ببینید چه تغییراتی اعمال خواهد شد. دستور قدیمی‌تر podman generate systemd همچنان وجود دارد اما منسوخ شده است، بنابراین برای هر چیز جدیدی از quadlet استفاده کنید.

جایی که alias داکر کار می‌کند و جایی که کار نمی‌کند

sudo apt install -y podman-docker
sudo touch /etc/containers/nodocker
docker ps

podman-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 جایی برای اجرا ندارد. ابزارهایی که با Docker socket صحبت می‌کنند، نیاز دارند که Podman socket را export کنند و برخی از آن‌ها همچنان تفاوت را تشخیص می‌دهند؛ provider داکر در Traefik زمانی کار می‌کند که به /run/user/<uid>/podman/podman.sock اشاره کند، در حالی که Watchtower هیچ جایگاهی ندارد، زیرا podman auto-update این وظیفه را انجام می‌دهد. فضای ذخیره‌سازی جداگانه است، بنابراین Podman نمی‌تواند imageهایی را که قبلاً با Docker دریافت کرده‌اید ببیند و podman images در یک میزبان داکر شلوغ، در ابتدا خالی خواهد بود.

مهاجرت گام‌به‌گام یک stack در حال اجرا

  1. کاربر بدون دسترسی ریشه (unprivileged) که مالک کانتینرها خواهد بود را ایجاد یا انتخاب کنید و مطمئن شوید که محدوده‌ای در /etc/subuid دارد.
  2. تمام ایمیج‌هایی که از یک registry دریافت شده‌اند را با استفاده از نام‌های کامل (fully qualified) دوباره pull کنید. Podman مخزن ایمیج اختصاصی خود را دارد و ایمیج‌های Docker را نمی‌خواند.
  3. ایمیج‌های ساخته‌شده به‌صورت محلی را با استفاده از docker save app:1.4 | podman load منتقل کنید.
  4. کانتینر Docker را متوقف کنید، محتویات هر volume را از /var/lib/docker/volumes/<name>/_data کپی کنید و سپس مالکیت فایل‌ها را با podman unshare chown -R 1000:1000 <path> اصلاح کنید.
  5. مسئله پورت‌ها را تعیین تکلیف کنید: پورت‌های بالای 1024 را پشت یک reverse proxy منتشر کنید یا net.ipv4.ip_unprivileged_port_start را تنظیم نمایید.
  6. برای هر کانتینر یک فایل quadlet بنویسید، systemctl --user daemon-reload را اجرا کنید و هر سرویس را استارت بزنید.
  7. دستور sudo loginctl enable-linger <user> را اجرا کنید، VPS را reboot کنید، دوباره وارد شوید و بررسی کنید که podman ps تمام سرویس‌ها را مجدداً لیست می‌کند.

این دو موتور هیچ منبع مشترکی ندارند: فضای ذخیره‌سازی ایمیج و شبکه‌ها کاملاً جدا هستند. بنابراین می‌توانید در حین مهاجرت هر دو را همزمان اجرا کنید و تنها تداخل احتمالی، شماره پورت میزبان است. یک سرویس را منتقل کنید، یک روز آن را زیر نظر بگیرید و سپس به سراغ سرویس بعدی بروید.

مقایسه Podman و Docker: کدام‌یک برای VPS شما مناسب‌تر است؟

اگر پشته (stack) شما در فایل‌های compose قرار دارد که دیگران نیز آن را نگهداری می‌کنند، یا به ابزارهایی وابسته‌اید که با Docker socket در ارتباط هستند، از Docker استفاده کنید. سازگاری با آنچه دیگران می‌نویسند یک قابلیت واقعی است و Docker در این زمینه برتری دارد. تیمی که لپ‌تاپ‌های اعضای آن همگی Docker را اجرا می‌کنند، از اجرای همان موتور در محیط production نیز مزایای ملموسی به دست می‌آورد.

اگر VPS شما تعداد محدودی سرویس را اجرا می‌کند که کنترل کامل آن‌ها در دست شماست، یا می‌خواهید هر برنامه تحت یک کاربر بدون دسترسی ریشه (unprivileged) و بدون وجود گروه docker روی سیستم اجرا شود، به سراغ Podman بروید. همسویی با توزیع سیستم‌عامل نیز مهم است: RHEL و نسخه‌های بازسازی‌شده آن، Podman را به عنوان موتور پشتیبانی‌شده ارائه می‌دهند؛ بنابراین در این سیستم‌ها، Podman مسیری با غافلگیری‌های کمتر است. اگر پیش از این همه چیز را با unitهای systemd مدیریت کرده‌اید، Quadletها برای شما مانند قطعه‌ای گمشده خواهند بود که سر جای خود قرار می‌گیرد، نه ابزاری جدید که نیاز به یادگیری داشته باشد.

یک گزینه میانی نیز ارزش اشاره دارد. Podman در حالت rootful رفتاری بسیار شبیه به Docker دارد، دستور docker را از طریق wrapper حفظ می‌کند و همچنان daemon همیشه فعال را حذف می‌نماید. البته این حالت مزیت rootless بودن را که عامل اصلی تغییر وضعیت امنیتی شماست از بین می‌برد، بنابراین آن را به عنوان یک ایستگاه موقت در مسیر خود در نظر بگیرید.

اگر هنوز در حال ساخت اولین میزبان کانتینر خود هستید، مسیر راه‌اندازی و مقاوم‌سازی Docker روی یک VPS تازه کوتاه‌ترین راه است و هیچ‌کدام از دانش‌های کسب‌شده در این مسیر هدر نخواهد رفت. Imageها و volumeها در هر دو موتور اشیاء یکسانی هستند، بنابراین مهاجرت در آینده تنها نحوه مدیریت سرویس‌های شما را تغییر می‌دهد و تأثیر دیگری نخواهد داشت.

FAQ

آیا Podman جایگزین مستقیم Docker است؟

برای دستوراتی که تایپ می‌کنید، تقریباً بله. نصب podman-docker یک wrapper به نام /usr/bin/docker در اختیار شما می‌گذارد و دستورات run، ps، build، logs و exec مشابه Docker عمل می‌کنند. با این حال، این ابزار جایگزینی برای daemon نیست. Swarm معادلی در Podman ندارد، ابزارهایی که به /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 باعث می‌شود instance مربوط به systemd آن کاربر بدون نیاز به نشست فعال، در حال اجرا باقی بماند؛ این همان قابلیتی است که باعث می‌شود کانتینرها پس از reboot دوباره به‌طور خودکار شروع به کار کنند.

چرا مالکیت فایل‌ها در volume من به UID 100999 تغییر کرده است؟

در حالت rootless، ابزار Podman شناسه UID 0 کانتینر را به کاربر میزبان (host) شما نگاشت می‌کند و سپس UID 1 و بالاتر را به محدوده subuid شما اختصاص می‌دهد. با شروع محدوده از 100000، شناسه UID 1000 کانتینر به 100999 روی میزبان تبدیل می‌شود. برای اصلاح این مورد، از داخل namespace با دستور podman unshare chown 1000:1000 /path/to/data اقدام کنید، در اولین اجرا از فلگ :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، سرویس‌هایی که socket داکر را mount می‌کنند، و restart: always که برای بقا پس از reboot به یک unit از نوع quadlet و قابلیت linger نیاز دارد، ممکن است با چالش‌هایی مواجه شوید.

آیا اجرای rootless واقعاً کانتینرها را امن‌تر می‌کند؟

این کار یک ریسک خاص را حذف می‌کند: اگر فرآیندی از یک کانتینر rootless فرار کند، تنها دسترسی‌های کاربر غیرممتاز شما را خواهد داشت، نه دسترسی‌های root را. این ویژگی ارزشمند است و به همین دلیل است که گروه docker که معادل root است، در Podman بدون root (rootless) وجود ندارد. این روش جلوی آسیب‌پذیری‌های هسته (kernel) را نمی‌گیرد و از فایل‌هایی که کاربر خودتان می‌تواند بخواند محافظت نمی‌کند؛ بنابراین سایر اقدامات امنیتی که روی هر سرور دیگری انجام می‌دهید را همچنان رعایت کنید.