Cara Menyiapkan VPS untuk Pemula, dari Seorang Pemula (Bagian 3: Podman Rootless dan Kontainer Pertama)
Bagian 3 dari penyiapan VPS pertama saya di Debian 13. Podman rootless yang dimiliki satu pengguna, pemetaan UID, delegasi cgroup, lingering, dan menjalankan kontainer pertama sebagai layanan systemd dengan Quadlet.
Seri 5 bagian.
Sampai Mana Kita
Bagian 1 mengamankan login. Bagian 2 menutup port dan membuat server menambal dirinya sendiri.
Server-nya sudah aman, dan sama sekali tidak berguna, karena belum ada yang berjalan di atasnya. Bagian ini membuat kontainer bisa jalan.
Kenapa Kontainer, dan Kenapa Podman
Untuk satu aplikasi kecil, saya bisa pasang Node, clone repo, lalu jalankan sebagai layanan systemd. Itu jalan sampai aplikasi kedua butuh versi Node yang berbeda. Dengan kontainer, setiap aplikasi membawa dependensinya sendiri, dan menghapus satu aplikasi berarti menghapus semuanya. Image yang sama juga jalan dengan cara yang sama di laptop dan di server.
Untuk server ini saya pilih Podman, bukan Docker, karena tiga hal:
- Tanpa daemon. Docker menjalankan satu layanan latar sebagai root yang memiliki semua kontainer. Podman tidak punya itu; kontainer cuma proses biasa.
- Rootless itu cara normal memakainya. Kontainer berjalan sebagai pengguna biasa saya. Root di dalam kontainer bukan root di server.
- Cocok dengan systemd. Debian sudah menjalankan systemd, yang mengurus start saat boot, restart saat crash, dan mengumpulkan log. Dengan Quadlet (di bawah), kontainer jadi layanan systemd biasa.
Perintahnya sama dengan Docker (podman run, podman ps, podman build) dan image-nya juga sama, jadi tidak banyak yang perlu dipelajari ulang.
Memasang Podman
sudo apt install -y podman passt crun netavark aardvark-dns catatonit uidmap dbus-user-session systemd-containerKebanyakan ikut terpasang juga, tapi saya lebih suka menuliskannya supaya tahu apa saja yang ada:
passtmenyediakanpasta, jaringan yang dipakai Podman untuk kontainer rootless.netavarkmembuat jaringan kontainer, danaardvark-dnsmembuat kontainer di jaringan yang sama bisa saling menemukan lewat nama. Bagian 4 bergantung pada itu.uidmapberisi helper yang bikin rootless bisa jalan sama sekali (dibahas berikutnya).dbus-user-sessionmemberi pengguna saya session bus yang benar, yang dibutuhkan systemd per pengguna.systemd-containermemberi sayamachinectl, yang saya pakai di bawah.
Mengecek Pemetaan UID
Kontainer rootless bekerja dengan memetakan sederet user ID tambahan ke pengguna saya. Proses yang mengira dirinya root (UID 0) di dalam kontainer sebenarnya adalah UID tinggi tanpa hak istimewa di server. Rentang itu harus disiapkan untuk penggunanya. useradd di Debian melakukannya otomatis, tapi cek saja:
grep admin /etc/subuid /etc/subgid/etc/subuid:admin:100000:65536
/etc/subgid:admin:100000:65536Mulai dari 100000, sebanyak 65536. Kalau salah satu berkas tidak punya baris untuk pengguna kita, tambahkan:
sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 adminLalu tes cepat sebagai admin:
podman run --rm docker.io/library/alpine echo "rootless works"Trying to pull docker.io/library/alpine:latest...
Getting image source signatures
Copying blob 9824c27679d3 done
Writing manifest to image destination
rootless worksSaya selalu menulis nama image lengkap, dengan docker.io/library/. Podman tidak otomatis menganggap Docker Hub seperti Docker, dan nama lengkap juga bikin jelas dari mana image itu berasal.
Supaya Kontainer Bisa Dibatasi
Nanti saya mau membatasi memori dan CPU setiap kontainer, supaya satu aplikasi yang rusak tidak menjatuhkan seluruh server. Untuk kontainer rootless, batas itu diterapkan lewat systemd milik pengguna sendiri, dan secara default systemd tidak memberi pengguna kendali atas semuanya.
Drop-in ini menyerahkannya:
sudo mkdir -p /etc/systemd/system/user@.service.d
sudo nano /etc/systemd/system/user@.service.d/delegate.conf[Service]
Delegate=cpu cpuset io memory pidssudo systemctl daemon-reloadIni berlaku saat systemd milik pengguna dijalankan lagi, jadi logout lalu login lagi (atau reboot), lalu cek:
cat /sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllerscpuset cpu io memory pidsKalau memory tidak ada di daftar itu, batas memori kontainer diam-diam diabaikan.
Pengaturan yang Menentukan Segalanya
Ini yang paling mengejutkan saya. Layanan systemd milik seorang pengguna berjalan saat pengguna itu login dan berhenti saat ia logout. Jadi kontainer saya akan mati begitu SSH ditutup, dan setelah reboot jam 4 pagi tidak ada yang kembali.
Lingering membereskannya:
sudo loginctl enable-linger admin
loginctl show-user admin --property=LingerLinger=yesSekarang admin punya instance systemd sendiri sejak boot, ada yang login atau tidak. Tanpa baris ini, reboot otomatis dari Bagian 2 akan mematikan semua aplikasi.
Satu kebiasaan yang menyertainya: jalankan systemctl --user dari login sungguhan sebagai pengguna itu. sudo -u admin systemctl --user ... gagal dengan error D-Bus yang membingungkan, karena shell itu tidak tersambung ke sesi milik pengguna. Kalau kita sedang jadi root dan butuh sesi yang benar sebagai pengguna itu, machinectl shell admin@ memberikannya.
Pengaturan Kontainer
Podman membaca pengaturan per pengguna dari ~/.config/containers/containers.conf:
mkdir -p ~/.config/containers
nano ~/.config/containers/containers.conf[containers]
log_driver = "journald"
[engine]
events_logger = "journald"
[network]
default_rootless_network_cmd = "pasta"journald mengirim output kontainer ke journal, jadi batas ukuran log dari Bagian 2 juga berlaku untuk kontainer. Tidak ada lagi berkas log yang terus membesar di suatu tempat di penyimpanan kontainer.
Mengizinkan Port 80 dan 443
Port di bawah 1024 biasanya butuh root, dan tidak ada yang berjalan sebagai root di sini. Di Bagian 4, port web akan dibuka oleh systemd milik pengguna saya, jadi batasnya harus diturunkan:
sudo nano /etc/sysctl.d/90-rootless.confnet.ipv4.ip_unprivileged_port_start = 80
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 1024sudo sysctl --systemDi server yang dipakai bersama, membiarkan pengguna biasa mengambil port 80 itu ide buruk. Di sini satu-satunya pengguna biasa adalah saya, jadi risikonya tidak benar-benar ada. Baris inotify menaikkan jumlah berkas yang bisa dipantau perubahannya oleh seorang pengguna, karena reverse proxy di Bagian 4 memantau berkas config-nya.
Tempat Aplikasi Tinggal
Satu keputusan kecil lagi. Setiap aplikasi dapat direktori di bawah satu tempat di direktori home saya:
mkdir -p ~/appsBukan /var/www, bukan /opt. Semua yang penting di server ini berakhir di home milik admin: aplikasinya, pengaturannya, dan (karena rootless) data kontainernya. Mem-backup server berarti mem-backup satu direktori.
Quadlet: Kontainer sebagai Layanan systemd
Ide pertama saya adalah compose.yml, karena itu yang saya tahu. Tapi lalu siapa yang menyalakan kontainernya setelah reboot? Restart policy butuh daemon, dan Podman tidak punya.
Quadlet menjawab itu. Kita menulis berkas kecil yang mirip unit systemd, menaruhnya di ~/.config/containers/systemd/, dan systemd mengubahnya jadi layanan sungguhan. Start saat boot, restart saat gagal, urutan, dan log semuanya dari systemd.
mkdir -p ~/.config/containers/systemdKontainer Pertama
Sesuatu yang bisa dibuang, untuk belajar alurnya. whoami adalah web server mungil yang cuma mencetak detail request:
nano ~/.config/containers/systemd/hello.container[Unit]
Description=Quadlet test container
[Container]
ContainerName=hello
Image=docker.io/traefik/whoami
PublishPort=127.0.0.1:8080:80
[Service]
Restart=always
[Install]
WantedBy=default.target[Unit]dan[Install]itu bagian systemd biasa.WantedBy=default.targetyang membuatnya jalan saat boot.[Container]itu bagian milik Quadlet. Setiap baris jadi opsi untukpodman run.[Service]juga systemd biasa.Restart=alwaysmasuk ke sini, bukan ke[Container]. Pertama kali saya menaruhnya di tempat yang salah.
127.0.0.1:8080 menjaga port itu hanya di server sendiri. Firewall toh akan memblokir 8080 dari luar, tapi saya lebih suka tidak mempublikasikannya sama sekali.
Sebelum dimuat, Quadlet bisa mengecek berkasnya:
/usr/libexec/podman/quadlet -dryrun -userIa mencetak layanan yang akan dibuatnya, atau error yang menyebut baris mana yang tidak ia suka. Lalu:
systemctl --user daemon-reload
systemctl --user start hello
systemctl --user status hello● hello.service - Quadlet test container
Loaded: loaded (/home/admin/.config/containers/systemd/hello.container; generated)
Active: active (running) since Fri 2026-05-22 11:04:18 WIB; 5s agodaemon-reload adalah langkah yang menjalankan generator-nya. Setiap kali menambah atau mengubah berkas .container, reload dulu sebelum start. Mengedit berkas lalu bingung kenapa tidak ada yang berubah itu kesalahan Quadlet yang paling klasik.
Perhatikan tulisan generated. Layanan Quadlet tidak bisa di-systemctl --user enable; bagian [Install] ditambah reload itulah cara mengaktifkannya.
curl http://127.0.0.1:8080Hostname: 7d1e4b9a03c2
IP: 127.0.0.1
...
GET / HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/8.14.1
Accept: */*Ada balasan berarti kontainer rootless, jaringan, dan bagian systemd-nya jalan semua. Log-nya ada di tempat yang sama dengan log layanan lain:
journalctl --user -u hello -fSekarang tes yang sebenarnya. Apakah ia kembali setelah reboot, tanpa ada yang login?
sudo rebootLogin lagi:
systemctl --user is-active helloactiveKalau hasilnya active, lingering jalan dan setup-nya bertahan tanpa saya. Kalau tidak, cek loginctl show-user admin --property=Linger dulu.
Bersihkan:
systemctl --user stop hello
rm ~/.config/containers/systemd/hello.container
systemctl --user daemon-reloadMembersihkan Image Lama
Setiap deploy akan menarik image baru, dan yang lama menumpuk. Timer kecil di systemd milik pengguna saya menghapus image yang tidak dipakai selama seminggu:
mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/image-prune.service[Unit]
Description=Remove container images unused for a week
[Service]
Type=oneshot
ExecStart=/usr/bin/podman image prune --all --force --filter until=168hnano ~/.config/systemd/user/image-prune.timer[Unit]
Description=Weekly container image cleanup
[Timer]
OnCalendar=weekly
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.targetsystemctl --user daemon-reload
systemctl --user enable --now image-prune.timerPersistent=true berarti kalau server sedang mati saat timer-nya seharusnya jalan, ia jalan begitu server hidup lagi.
Posisi Server Sekarang
Podman rootless jalan, bisa menerapkan batas memori dan CPU, kontainer bertahan melewati logout dan reboot, dan pengguna saya bisa membuka port 80 dan 443.
Situsnya masih belum ada. Bagian 4 memasang Traefik di depan untuk HTTPS.