SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

เปรียบเทียบ Podman กับ Docker บน VPS เลือกตัวไหนดี

เจาะลึกความแตกต่างระหว่าง Podman และ Docker บนเซิร์ฟเวอร์ VPS ทั้งเรื่องการรันแบบ rootless ไร้ daemon การจัดการ systemd quadlets สิทธิ์ของ volume และข้อจำกัดการ bind พอร์ต

ความแตกต่างที่แท้จริงระหว่าง Podman และ Docker

Podman และ Docker สามารถรัน OCI (Open Container Initiative) image ตัวเดียวกันบน VPS ได้ ดังนั้นการเลือกใช้งานจึงไม่ใช่เรื่องของซอฟต์แวร์ที่สามารถรันได้ แต่ความแตกต่างอยู่ที่รูปแบบของกระบวนการทำงาน Docker รัน root daemon ซึ่งเป็นเจ้าของ container ทุกตัว และคำสั่ง docker เป็นเพียงไคลเอนต์ขนาดเล็กที่ส่งคำขอให้ daemon ทำงานนั้นๆ ส่วน Podman ไม่มี daemon โดย podman run จะเริ่มการทำงานของ container ในฐานะ child process ของสิ่งที่เรียกใช้มัน ภายใต้สิทธิ์ของผู้ใช้ทั่วไปของคุณเอง

ทุกอย่างที่เหลือเป็นผลสืบเนื่องมาจากข้อเท็จจริงข้างต้น การเริ่มทำงานอัตโนมัติ (auto-start) จึงกลายเป็นหน้าที่ของ systemd แทนที่จะเป็นของ daemon การเป็นเจ้าของ volume จะผ่าน user namespace ดังนั้นเจ้าของที่คุณเห็นด้วยคำสั่ง ls -l บนโฮสต์ จึงไม่ใช่เจ้าของเดียวกับที่ container มองเห็น พอร์ตที่ต่ำกว่า 1024 จะไม่สามารถ bind ได้จนกว่าคุณจะเปลี่ยนการตั้งค่า kernel ส่วน CLI (command line interface) ของ docker ยังคงทำงานได้ผ่าน wrapper จนกระทั่งถึงจุดที่ต้องมีการเรียกใช้งาน Docker socket

ไม่มี daemon: สิ่งที่ทำงานจริงเมื่อคุณเริ่ม container

บน Docker host คำสั่ง pstree -a จะแสดง dockerd ในฐานะ root โดยมี containerd อยู่ข้างๆ และมี containerd-shim-runc-v2 หนึ่งรายการต่อ container ที่กำลังทำงาน แอปพลิเคชันของคุณเป็น child process ของ shim นั้น และ shim เป็น child process ของ PID 1 ไม่มีสิ่งใดเชื่อมต่อ container เข้ากับ shell ที่ใช้สั่งเริ่มทำงาน หากคุณหยุด daemon คุณจะสูญเสีย control plane สำหรับทุก container บนเครื่องนั้น และหากตั้งค่า live-restore เป็นปิดไว้ systemctl restart docker จะรีสตาร์ท container ของคุณไปด้วย

Podman ไม่มี process ที่เทียบเท่ากัน เมื่อเริ่ม container คุณจะได้ process conmon (container monitor) หนึ่งรายการที่คอยดูแล process หลักของ container โดยมีผู้ใช้ที่รันคำสั่งเป็นเจ้าของ

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 เนื่องจากไม่มีบริการส่วนกลางเป็นเจ้าของ container การหยุด sudo apt upgrade podman จึงไม่ส่งผลกระทบต่อสิ่งที่กำลังทำงานอยู่ และการที่ monitor ของ container หนึ่งตัวล่ม จะไม่ทำให้ตัวอื่นล่มตามไปด้วย

การไม่มี daemon ก็มีข้อเสียเช่นกัน ไม่มีสิ่งใดคอยเริ่ม container ให้คุณหลังจากรีบูตเครื่อง คำสั่ง --restart=always ของ Docker คือสัญญาที่ daemon รักษาไว้ตอนบูตเครื่อง แต่ Podman ใช้ systemd เข้ามาแทนที่ ซึ่งเป็นที่มาของส่วน quadlet ด้านล่างนี้

socket คืออีกครึ่งหนึ่งของเรื่องนี้ /var/run/docker.sock เป็น endpoint ของ API (application programming interface) ที่มี root เป็นเจ้าของ และ process ใดก็ตามที่เขียนข้อมูลลงไปได้ สามารถเริ่ม container ที่มีสิทธิ์สูงซึ่ง mount ระบบไฟล์ของ host ได้ การเพิ่มผู้ใช้เข้ากลุ่ม docker เท่ากับเป็นการให้สิทธิ์ root แก่ผู้ใช้นั้นผ่านเส้นทางที่อ้อมกว่า ซึ่งเป็นประเด็นที่ควรศึกษาควบคู่ไปกับ การให้สิทธิ์แก่ service account เฉพาะที่จำเป็นเท่านั้น Podman จะไม่เปิด socket เว้นแต่คุณจะร้องขอ และ socket ที่คุณได้รับจะเป็นของผู้ใช้รายเดียวที่ /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 helper ที่อนุญาตให้ผู้ใช้ทั่วไปสามารถจองช่วงของ subordinate ID ได้ หากไม่มีสิ่งเหล่านี้ คอนเทนเนอร์แบบ 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 ทุกคนจำเป็นต้องมีช่วงของ subordinate ID:

grep "$USER" /etc/subuid /etc/subgid

ผู้ใช้ที่สร้างด้วยคำสั่ง adduser บน Ubuntu จะได้รับช่วง ID โดยอัตโนมัติ แต่ผู้ใช้ที่สร้างด้วยคำสั่ง 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.

ให้กำหนดช่วง ID จากนั้นรีเซ็ตที่เก็บข้อมูลของผู้ใช้นั้นเพื่อให้มีการใช้งาน mapping ใหม่:

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 deploy
podman system migrate

อีกหนึ่งสิ่งที่มักพบในการรันครั้งแรก: Podman ไม่ได้ตั้งค่าเริ่มต้นให้ใช้ Docker Hub ชื่ออิมเมจแบบสั้นจะถูกตรวจสอบเทียบกับ unqualified-search-registries ในไฟล์ /etc/containers/registries.conf และในสคริปต์ที่ไม่มี terminal เชื่อมต่ออยู่ การดึงอิมเมจจะล้มเหลวพร้อมข้อความ short-name resolution enforced but cannot prompt without a TTY ให้ระบุชื่อเต็มทุกครั้ง และใช้ docker.io/library/nginx:1.27 แทน nginx

สิ่งที่ rootless containers มอบให้คุณจริง ๆ บนเซิร์ฟเวอร์เช่า

rootless container ทำงานอยู่ภายใน user namespace ซึ่งเป็นฟีเจอร์ของ kernel ที่ช่วยให้กระบวนการ (process) มีแผนผัง user ID ส่วนตัว ภายใน namespace นี้ superuser ของ container จะเป็น UID (user ID) 0 แต่ภายนอกนั้น บน VPS ของคุณ กระบวนการเดียวกันนี้จะเป็นเพียงผู้ใช้งานทั่วไปที่คุณล็อกอินอยู่ ดังนั้น root ใน container จึงไม่ใช่ root บน host

นั่นคือขอบเขตของประโยชน์ที่ได้รับอย่างแท้จริง อิมเมจที่บังคับให้รันในฐานะ root, เว็บแอปพลิเคชันที่มีช่องโหว่ remote code execution หรือการหลุดออกจาก container (escape) ที่ต้องอาศัยการเป็น UID 0 จากภายนอก ทั้งหมดนี้จะจบลงด้วยการถือสิทธิ์ของผู้ใช้งานที่ไม่มีสิทธิ์พิเศษของคุณแทนที่จะเป็นสิทธิ์ของเครื่อง สิ่งที่ rootless ไม่ได้ทำคือการป้องกันคุณจากบั๊กของ kernel และไม่ได้ปกป้องไฟล์ของคุณเอง เพราะกระบวนการที่หลุดออกมานั้นรันในฐานะตัวคุณและสามารถอ่านทุกอย่างที่คุณอ่านได้ หน่วยที่ถูกแยกส่วนมีความสำคัญพอ ๆ กับการทำ UID mapping ซึ่งเห็นได้ชัดเจนกว่าใน FreeBSD jail ซึ่งครอบคลุมทั้ง userland ที่คุณดูแลเหมือนเครื่องขนาดเล็ก แทนที่จะเป็นอิมเมจแบบเลเยอร์ที่ดึงมาจาก registry

Docker สามารถรันแบบ rootless ได้เช่นกัน dockerd-rootless-setuptool.sh install จะตั้งค่า daemon แยกตามผู้ใช้งานและทำงานได้ดี ความแตกต่างอยู่ที่ค่าเริ่มต้นที่ระบบเลือกให้ สำหรับ Podman คุณจะได้ rootless โดยไม่ต้องร้องขอ ดังนั้นความล้มเหลวแรกที่คุณจะพบคือ container ที่ไม่สามารถ bind พอร์ต 80 ได้ แทนที่จะเป็นบริการที่รันในฐานะ 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 user) ของคุณไม่สามารถเปลี่ยนความเป็นเจ้าของไฟล์ภายนอก namespace ได้เลย

มีทางออก 4 วิธี:

  • podman unshare chown 1000:1000 "$PWD/data" ใช้รันคำสั่ง chown ภายใน user namespace เดียวกัน ซึ่งตัวเลขจะมีความหมายตรงกับที่คอนเทนเนอร์เข้าใจ
  • -v "$PWD/data:/data:U" เป็นการสั่งให้ Podman แก้ไขความเป็นเจ้าของของไดเรกทอรีต้นทางให้คุณโดยอัตโนมัติ ให้ใช้กับไดเรกทอรีใหม่เท่านั้น ห้ามใช้กับข้อมูลที่คุณต้องการเก็บรักษา
  • --userns=keep-id เป็นการแมป UID ของโฮสต์คุณให้ตรงกับ UID ภายในคอนเทนเนอร์ เพื่อให้ไฟล์ใหม่ที่สร้างขึ้นมีคุณเป็นเจ้าของ
  • การใช้ named volume เช่น -v appdata:/data จะช่วยเลี่ยงปัญหานี้ได้ เพราะ Podman จะสร้าง volume ไว้ในพื้นที่จัดเก็บของคุณโดยกำหนดความเป็นเจ้าของให้ถูกต้องตั้งแต่ต้น

หากคุณเคยประสบปัญหานี้ใน Docker นี่คือปัญหาเดียวกันที่เพิ่มระดับความซับซ้อนขึ้นอีกชั้น ตัวแปร PUID และ PGID ที่ image หลายตัวระบุไว้ จะเป็นการกำหนด UID ที่กระบวนการภายในคอนเทนเนอร์ใช้งาน และภายใต้ rootless Podman ตัว UID นั้นจะถูกแมปซ้ำเป็นครั้งที่สอง การใช้ PUID=1000 ภายในคอนเทนเนอร์แบบ rootless จะยังคงเขียนไฟล์ลงโฮสต์โดยมีเจ้าของเป็น 100999 อยู่ดี ดังนั้นควรเลือกตัวเลขโดยคำนึงถึงการแมปซ้ำครั้งที่สองนี้ หรือย้ายข้อมูลไปไว้ใน named volume เพื่อตัดปัญหาดังกล่าวไปเลย

ข้อควรทราบเพิ่มเติมเกี่ยวกับการ mount: แฟล็ก :z และ :Z ที่คุณพบในตัวอย่างของ Fedora และ RHEL คือตัวเลือกสำหรับการทำ SELinux relabel ซึ่งใน Ubuntu จะใช้ AppArmor ดังนั้นแฟล็กเหล่านี้จึงไม่มีผลใดๆ นอกจากนี้ rootless Podman ยังไม่สามารถ mount ไดเรกทอรีบนโฮสต์ที่ผู้ใช้ของคุณไม่มีสิทธิ์อ่านได้ ซึ่งถือเป็นคุณสมบัติความปลอดภัยไม่ใช่ข้อผิดพลาดของระบบ

เหตุใด Podman แบบ rootless จึงไม่ยอมเปิดพอร์ต 80

เนื่องจากการผูกพอร์ตที่ต่ำกว่า 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 ได้ ไม่ใช่แค่ผู้ใช้ที่รันคอนเทนเนอร์เท่านั้น สำหรับ VPS ที่มีผู้ดูแลคนเดียวถือเป็นข้อแลกเปลี่ยนที่ยอมรับได้ แต่สำหรับเครื่องที่มีบัญชีผู้ใช้อื่นใช้งานอยู่ถือว่าไม่เหมาะสม วิธีที่สองคือการเปิดพอร์ต 8080 แล้วใช้ reverse proxy วางไว้ด้านหน้า ซึ่งเป็นจุดที่คุณต้องการให้ certbot บน nginx ออกและต่ออายุใบรับรอง อยู่แล้ว

การเปิดพอร์ตแบบ rootless ยังเปลี่ยนสิ่งที่แอปพลิเคชันของคุณมองเห็นด้วย Podman 4.x ใช้ slirp4netns ร่วมกับตัวจัดการพอร์ต rootlesskit เป็นค่าเริ่มต้น ทำให้การเชื่อมต่อที่ส่งต่อมามีที่อยู่ต้นทางที่ถูกเขียนทับ ส่งผลให้ access log บันทึกผู้เข้าชมทุกคนเป็น 10.0.2.100 ส่วน Podman 5.0 เปลี่ยนค่าเริ่มต้นเป็น pasta ซึ่งจะคงที่อยู่จริงของไคลเอนต์ไว้ สำหรับเวอร์ชัน 4.x การใช้ --network slirp4netns:port_handler=slirp4netns จะช่วยกู้คืนที่อยู่ต้นทางที่แท้จริงได้ แต่ต้องแลกกับประสิทธิภาพการรับส่งข้อมูลที่ลดลง

มีข้อดีที่น่าประหลาดใจอย่างหนึ่งในเรื่องนี้ พอร์ตที่เปิดแบบ rootless คือ listening socket ปกติที่ถูกครอบครองโดยโพรเซสของผู้ใช้ทั่วไป ดังนั้นกฎ input ของไฟร์วอลล์จึงมีผลกับพอร์ตนี้โดยตรง ในขณะที่ Docker เปิดพอร์ตโดยการเขียนกฎ NAT (network address translation) ร่วมกับการยอมรับการส่งต่อของตัวเอง ซึ่งเป็นเหตุผลว่าทำไม พอร์ตที่เปิดโดย Docker จึงเพิกเฉยต่อกฎ ufw ที่คุณคิดว่าบล็อกไว้ Podman แบบ rootful ใช้กลไกที่คล้ายกันและได้รับผลกระทบจากกับดักเดียวกันนี้ แต่แบบ rootless ไม่ได้รับผลกระทบดังกล่าว

ไฟล์ Docker Compose ของฉันยังใช้งานได้บน Podman หรือไม่

โดยส่วนใหญ่แล้วใช้งานได้ โดยมีสองแนวทางที่แตกต่างกัน แนวทางแรกคือ podman-compose ซึ่งเป็นการติดตั้งใช้งานแยกต่างหากที่อ่านไฟล์เดียวกันและสั่งการผ่าน Podman CLI:

sudo apt install -y podman-compose
podman-compose up -d
podman ps

แนวทางที่สองคือการใช้ Docker Compose จริงๆ เพื่อสื่อสารกับ API ของ Podman ที่รองรับ Docker ผ่านซ็อกเก็ตระดับผู้ใช้:

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) ก็ทำงานได้เช่นกัน โดย netavark ซึ่งเป็น network backend เริ่มต้นของ Podman จะรัน aardvark-dns ดังนั้นคอนเทนเนอร์ที่อยู่บนเครือข่ายที่ผู้ใช้กำหนดเองจึงสามารถค้นหากันและกันได้ด้วยชื่อ

อย่างไรก็ตาม ยังมีข้อจำกัดที่ต้องระวัง สิ่งใดก็ตามที่ mount /var/run/docker.sock จะต้องชี้ไปยังซ็อกเก็ตของ Podman หรือต้องตัดออก network_mode: host จะมีพฤติกรรมที่แตกต่างออกไปภายใต้ user namespace ส่วน depends_on ที่ใช้ร่วมกับ condition: service_healthy นั้นได้รับการรองรับไม่เท่ากันในแต่ละเวอร์ชันของ podman-compose และ restart: always ไม่สามารถคงอยู่ได้หลังจากการรีบูตด้วยตัวมันเอง ซึ่งส่วนถัดไปจะมาแก้ไขปัญหานี้ Compose ยังคงเป็นวิธีที่ดีในการอธิบาย สแต็กแบบหลายคอนเทนเนอร์ในไฟล์เดียว และภายใต้ Podman มันทำหน้าที่เป็นเลเยอร์การแปลผล สำหรับสแต็กที่คุณต้องการใช้งานต่อเนื่องเป็นเวลาหลายปี ควรแปลงเป็น quadlets เพื่อรักษา abstraction ไว้เพียงชุดเดียวแทนที่จะเป็นสองชุด

Pods: แนวคิดที่ Docker ไม่มีคำตอบให้

Pod คือกลุ่มของคอนเทนเนอร์ที่ใช้ network namespace ร่วมกัน Podman จะเริ่มคอนเทนเนอร์ขนาดเล็ก infra เพื่อคง namespace นั้นไว้ และสมาชิกในกลุ่มจะสามารถติดต่อกันได้ผ่าน 127.0.0.1 โดยไม่ต้องกำหนด network เองหรือใช้ service 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 ควรแสดง pod Running ที่มีคอนเทนเนอร์ 3 ตัว ซึ่งนับรวมคอนเทนเนอร์โครงสร้างพื้นฐาน (infra container) ด้วย คอนเทนเนอร์ web สามารถติดต่อ Redis ได้ที่ 127.0.0.1:6379 แทนที่จะเป็น app-cache:6379 จากการใช้ namespace ร่วมกันจึงมีกฎ 2 ข้อคือ ให้ publish พอร์ตที่ระดับ pod เท่านั้น ห้ามทำที่ระดับสมาชิก และสมาชิกสองตัวห้ามฟัง (listen) บนพอร์ตเดียวกัน

นี่คือโมเดลของ Kubernetes ซึ่ง Podman นำมาปรับใช้ podman kube generate app > app.yaml จะเขียน Kubernetes manifest จากสิ่งที่กำลังรันอยู่ (แพ็กเกจรุ่นเก่าจะใช้คำสั่ง podman generate kube) และ podman kube play app.yaml จะสร้างมันขึ้นมาใหม่บนโฮสต์อื่น Quadlet มี unit type แบบ .kube ที่รันไฟล์ดังกล่าวในฐานะ systemd service นี่เป็นวิธีการจัดกลุ่มบริการที่แตกต่างอย่างแท้จริง และเป็นเหตุผลที่สำคัญที่สุดในการเลือกใช้ Podman หากคุณมีแผนจะใช้งาน Kubernetes ในอนาคต

การเริ่มทำงานอัตโนมัติโดยไม่ต้องใช้ daemon: quadlet units

Quadlet เป็นตัวสร้าง (generator) ของ systemd ซึ่งจะเปลี่ยนไฟล์ขนาดสั้นที่อธิบายคอนเทนเนอร์ให้กลายเป็น systemd service จริงในขณะบูตระบบ โดยไฟล์เหล่านี้จะถูกวางไว้ใน ~/.config/containers/systemd/ สำหรับผู้ใช้แบบ rootless หรือ /etc/containers/systemd/ สำหรับ root

~/.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 สามารถเว้นว่างไว้เกือบทั้งหมดได้ เนื่องจากส่วนหัวของ section คือสิ่งที่สร้าง volume ขึ้นมา:

[Volume]
systemctl --user daemon-reload
systemctl --user start caddy
systemctl --user status caddy
journalctl --user -u caddy -n 50

ชื่อของ service จะมาจากชื่อไฟล์: caddy.container จะกลายเป็น caddy.service ห้ามรัน systemctl --user enable caddy เนื่องจาก unit ที่ถูกสร้างขึ้นโดยอัตโนมัติไม่สามารถเปิดใช้งาน (enable) ได้ และ systemd จะตอบกลับว่า Failed to enable unit: Unit /run/user/1000/systemd/generator/caddy.service is transient or generated. ส่วน [Install] คือสิ่งที่เริ่มการทำงานของคอนเทนเนอร์ในขณะบูต และ daemon-reload คือสิ่งที่สร้าง unit ขึ้นใหม่หลังจากที่คุณแก้ไขไฟล์

ต่อไปคือการตั้งค่าที่มักจะทำให้เกิดปัญหาสำหรับผู้ใช้ส่วนใหญ่:

sudo loginctl enable-linger deploy
loginctl show-user deploy --property=Linger

ให้คาดหวังผลลัพธ์เป็น Linger=yes หากไม่มีการตั้งค่า linger ระบบ systemd จะปิด session ของผู้ใช้ทั้งหมดเมื่อการเชื่อมต่อ SSH ครั้งสุดท้ายของคุณถูกตัดออก ส่งผลให้คอนเทนเนอร์แบบ rootless ทุกตัวหยุดทำงานและไม่กลับมาเริ่มใหม่ในขณะบูต คอนเทนเนอร์ที่หายไปเมื่อคุณออกจากระบบ (log out) มักเกิดจากสาเหตุนี้เสมอ

เนื่องจากคอนเทนเนอร์เป็นกระบวนการหลักของ service unit ทั่วไป การควบคุมของ systemd จึงมีผลโดยตรง MemoryMax= และ CPUQuota= ในส่วนของ [Service] จะทำงานเหมือนกับ service อื่นๆ ที่คุณจำกัดทรัพยากรด้วย systemd ทุกประการ การตั้งค่านี้จำเป็นต้องใช้ cgroup v2 (control group version 2) ซึ่ง Ubuntu ใช้เป็นค่าเริ่มต้นมาตั้งแต่เวอร์ชัน 22.04 ให้ตรวจสอบด้วย podman info | grep -i cgroup

การอัปเดตมีกลไกที่สอดคล้องกัน AutoUpdate=registry ร่วมกับ systemctl --user enable --now podman-auto-update.timer จะตรวจสอบ registry เพื่อหา image เวอร์ชันใหม่ที่ใช้ tag เดียวกัน จากนั้นจะรีสตาร์ท unit และย้อนกลับ (roll back) ไปยัง image ก่อนหน้าหากคอนเทนเนอร์ใหม่ไม่สามารถเริ่มทำงานได้ ให้รัน podman auto-update --dry-run ก่อนเพื่อดูว่าการเปลี่ยนแปลงจะมีผลอย่างไร คำสั่ง podman generate systemd แบบเก่าที่ยังคงมีอยู่ถือว่าเลิกใช้งานแล้ว (deprecated) ดังนั้นควรเขียน quadlets สำหรับงานใหม่ทั้งหมดแทน

ขอบเขตการใช้งานของ docker 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 mode ไม่มีฟังก์ชันการทำงานที่เทียบเท่ากัน ดังนั้น Swarm stack จึงไม่สามารถทำงานได้ เครื่องมือที่ต้องสื่อสารกับ Docker socket จำเป็นต้องมีการ export Podman socket ออกมา และบางเครื่องมือยังคงตรวจพบความแตกต่างได้ เช่น Docker provider ของ Traefik สามารถทำงานได้เมื่อชี้ไปยัง /run/user/<uid>/podman/podman.sock ในขณะที่ Watchtower ไม่สามารถใช้งานได้เลยเนื่องจาก podman auto-update ทำหน้าที่นั้นแทน นอกจากนี้ พื้นที่จัดเก็บข้อมูลยังแยกจากกัน Podman จึงไม่สามารถมองเห็น image ที่คุณเคยดึงมาด้วย Docker ได้ และ podman images บนโฮสต์ที่ใช้งาน Docker อยู่เดิมจะเริ่มต้นด้วยสถานะว่างเปล่า

การย้าย stack ที่กำลังทำงานอยู่ทีละขั้นตอน

  1. สร้างหรือเลือกผู้ใช้ที่ไม่มีสิทธิ์ root (unprivileged user) เพื่อเป็นเจ้าของคอนเทนเนอร์ และยืนยันว่าผู้ใช้นั้นมีช่วง UID/GID ใน /etc/subuid
  2. ดึงอิมเมจที่มาจาก registry ใหม่อีกครั้งโดยใช้ชื่อแบบเต็ม (fully qualified names) เนื่องจาก Podman มีที่เก็บอิมเมจของตนเองและไม่สามารถอ่านอิมเมจจาก Docker ได้
  3. ย้ายอิมเมจที่สร้างขึ้นในเครื่องข้ามไปยังระบบใหม่โดยใช้ docker save app:1.4 | podman load
  4. หยุดการทำงานของ Docker container คัดลอกเนื้อหาของแต่ละ 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 และเริ่มการทำงานของแต่ละ service
  7. รันคำสั่ง sudo loginctl enable-linger <user>, รีบูต VPS, ล็อกอินกลับเข้ามา และตรวจสอบว่า podman ps แสดงรายการทุก service อีกครั้ง

เอนจินทั้งสองตัวไม่มีส่วนใดที่ใช้ร่วมกัน ทั้งที่เก็บอิมเมจและเครือข่ายแยกจากกันโดยสิ้นเชิง ดังนั้นคุณสามารถรันทั้งสองระบบควบคู่กันไปในระหว่างการย้ายได้ สิ่งเดียวที่อาจเกิดความขัดแย้งกันคือหมายเลขพอร์ตของโฮสต์ ให้ย้ายทีละ service ตรวจสอบการทำงานเป็นเวลาหนึ่งวัน แล้วจึงย้าย service ถัดไป

Podman กับ Docker: แบบไหนที่เหมาะกับ VPS ของคุณ?

ให้ใช้ Docker ต่อไปหาก stack ของคุณอยู่ในไฟล์ compose ที่มีผู้อื่นร่วมดูแลด้วย หรือหากคุณต้องพึ่งพาเครื่องมือที่สื่อสารผ่าน Docker socket ความเข้ากันได้กับสิ่งที่คนอื่นเขียนถือเป็นฟีเจอร์ที่สำคัญ และ Docker มีความเข้ากันได้ในส่วนนี้มากกว่า ทีมที่ใช้ Docker บนแล็ปท็อปทุกคนจะได้รับประโยชน์ที่เป็นรูปธรรมจากการใช้ engine เดียวกันในสภาพแวดล้อม production

ให้เปลี่ยนไปใช้ Podman หาก VPS ของคุณรันบริการเพียงไม่กี่รายการที่คุณควบคุมแบบเบ็ดเสร็จ หรือหากคุณต้องการให้แต่ละแอปพลิเคชันทำงานภายใต้ผู้ใช้ที่ไม่มีสิทธิ์ (unprivileged user) โดยไม่มีกลุ่ม docker อยู่บนเครื่องเลย การเลือกให้ตรงกับ distribution ก็สำคัญเช่นกัน: RHEL และระบบที่สร้างจาก RHEL มาพร้อมกับ Podman ในฐานะ engine ที่ได้รับการสนับสนุน ดังนั้นบนระบบเหล่านี้ Podman จึงเป็นเส้นทางที่พบปัญหาประหลาดใจน้อยกว่า หากคุณยังต้องการใช้ Docker บนโฮสต์เหล่านั้น วิธีการติดตั้งผ่าน dnf บน Rocky Linux และ AlmaLinux จะเริ่มต้นด้วยการลบ podman-docker wrapper ที่ครอบครองคำสั่ง docker อยู่ หากคุณดูแลบริการอื่น ๆ ด้วย systemd units อยู่แล้ว คุณจะรู้สึกว่า quadlets เป็นส่วนประกอบที่ขาดหายไปมากกว่าจะเป็นเครื่องมือใหม่ที่ต้องเรียนรู้

มีตัวเลือกตรงกลางอีกหนึ่งทางที่ควรกล่าวถึง Rootful Podman มีพฤติกรรมคล้ายกับ Docker มาก โดยยังคงใช้คำสั่ง docker ผ่าน wrapper และยังคงตัด daemon ที่ต้องรันตลอดเวลาออกไป อย่างไรก็ตาม วิธีนี้จะสูญเสียคุณสมบัติการรันแบบ rootless ซึ่งเป็นส่วนที่ช่วยปรับปรุงความปลอดภัยของคุณ ดังนั้นให้ถือว่าวิธีนี้เป็นเพียงจุดแวะพักระหว่างทางเท่านั้น

หากคุณกำลังสร้าง container host เครื่องแรก เส้นทางการติดตั้งและเพิ่มความปลอดภัยให้กับ Docker บน VPS ใหม่ เป็นเส้นทางที่สั้นกว่า และความรู้ทั้งหมดที่ได้จะไม่สูญเปล่า เนื่องจาก images และ volumes เป็นอ็อบเจกต์เดียวกันในทั้งสอง engine การย้ายในภายหลังจะเปลี่ยนเพียงวิธีการดูแลบริการของคุณเท่านั้น โดยแทบไม่มีส่วนอื่นที่ต้องปรับเปลี่ยนเลย

FAQ

Podman เป็นตัวแทนของ Docker ได้ทันทีเลยหรือไม่

ในแง่ของคำสั่งที่ใช้ถือว่าใกล้เคียงกันมาก การติดตั้ง podman-docker จะให้ wrapper ชื่อ /usr/bin/docker มาใช้งาน และคำสั่งอย่าง run, ps, build, logs และ exec ก็ทำงานในลักษณะเดียวกัน อย่างไรก็ตาม Podman ไม่ใช่ตัวแทนของ daemon โดยตรง Swarm ไม่มีฟังก์ชันที่เทียบเท่ากัน เครื่องมือที่ต้องเชื่อมต่อกับ /var/run/docker.sock จะต้องถูกชี้ไปยัง socket ของ Podman ในระดับผู้ใช้แทน และ image ที่ดึงมาด้วย Docker จะไม่ปรากฏใน Podman เนื่องจากทั้งสองระบบแยกพื้นที่จัดเก็บข้อมูลออกจากกัน

ทำไม container แบบ rootless ของฉันถึงหยุดทำงานเมื่อออกจากระบบ SSH

เพราะ systemd จะหยุด session ของผู้ใช้และ service ทั้งหมดที่เกี่ยวข้องเมื่อคุณออกจากระบบครั้งสุดท้าย ให้รันคำสั่ง sudo loginctl enable-linger <user> จากนั้นตรวจสอบว่า loginctl show-user <user> --property=Linger แสดงผลเป็น Linger=yes หรือไม่ การตั้งค่า linger จะช่วยให้ instance ของ systemd ในระดับผู้ใช้ทำงานต่อไปได้แม้ไม่มี session ที่ใช้งานอยู่ ซึ่งเป็นวิธีที่ทำให้ container เริ่มทำงานใหม่โดยอัตโนมัติหลังจากรีบูตเครื่อง

ทำไมไฟล์ใน volume ของฉันถึงมีเจ้าของเป็น UID 100999

Rootless Podman จะแมป UID 0 ใน container เข้ากับผู้ใช้บน host ของคุณ จากนั้นจะแมป UID 1 ขึ้นไปเข้ากับช่วง subuid ของคุณ หากช่วงดังกล่าวเริ่มต้นที่ 100000 ค่า UID 1000 ใน container จึงกลายเป็น 100999 บน host คุณสามารถแก้ไขปัญหานี้จากภายใน namespace ด้วย podman unshare chown 1000:1000 /path/to/data, mount ด้วย flag :U ในการรันครั้งแรก หรือใช้ --userns=keep-id เพื่อให้ UID ใน container ตรงกับ UID ของคุณ

ฉันยังสามารถใช้ docker-compose.yml กับ Podman ได้หรือไม่

ได้ โดยทำได้สองวิธี วิธีแรกคือใช้ podman-compose ซึ่งจะอ่านไฟล์และสั่งงานผ่าน Podman CLI โดยตรง วิธีที่สองคือเปิดใช้งาน compatibility socket ด้วย systemctl --user enable --now podman.socket, ตั้งค่า DOCKER_HOST=unix:///run/user/$(id -u)/podman/podman.sock แล้วรัน docker compose จริงๆ เพื่อเชื่อมต่อกับ socket นั้น คุณอาจพบปัญหาในการใช้งาน network_mode: host, บริการที่ต้อง mount Docker socket และ restart: always ซึ่งจำเป็นต้องใช้ quadlet unit และการตั้งค่า linger เพื่อให้ทำงานได้ต่อเนื่องหลังรีบูต

การรันแบบ rootless ทำให้ container ปลอดภัยขึ้นจริงหรือไม่

มันช่วยลดความเสี่ยงเฉพาะจุดได้หนึ่งอย่าง คือหากกระบวนการใดหลุดออกมาจาก container แบบ rootless ได้ กระบวนการนั้นจะมีสิทธิ์เพียงเท่ากับผู้ใช้ทั่วไปของคุณ ไม่ใช่สิทธิ์ระดับ root ซึ่งเป็นข้อดีที่สำคัญ และเป็นเหตุผลว่าทำไมกลุ่ม docker ที่มีสิทธิ์เทียบเท่า root จึงไม่มีใน Podman แบบ rootless อย่างไรก็ตาม วิธีนี้ไม่ได้ป้องกันช่องโหว่ของ kernel และไม่ได้ปกป้องไฟล์ที่ผู้ใช้ของคุณเข้าถึงได้ ดังนั้นคุณยังคงต้องใช้มาตรการรักษาความปลอดภัยอื่นๆ ตามมาตรฐานการดูแลเซิร์ฟเวอร์ทั่วไป