VPS dikerahkan dengan runbook empat langkah, stack dirawat dengan kalender tri-ritme, dan toko melayani pelanggan lewat HTTPS dengan log yang bisa dijahit ujung ke ujung. Namun “semua bab sudah dibaca” dan “produksi sudah aman” adalah dua kalimat yang berbeda jaraknya sejauh langit dan tanah — yang pertama perasaan, yang kedua harus bisa dibuktikan kepada orang lain: rekan tim baru, auditor, bahkan diri kita sendiri enam bulan lagi saat lupa detail. Chapter penutup section ini mengubah seluruh keputusan sepanjang perjalanan menjadi satu checklist tujuh kategori yang tiap barisnya bisa dieksekusi dari terminal, lengkap dengan cara memakainya saat go-live dan saat audit bulanan. Bentuknya sengaja dibuat kasar dan bisa dicetak: kolom item, kolom alasan satu kalimat, dan kolom rujukan chapter tempat keputusan itu lahir — karena checklist yang tidak menjelaskan dirinya hanya melatih kepatuhan, bukan pemahaman.

Tujuh Kategori Checklist #

Image #

ItemMengapaRujukan
Image dibangun multi-stage dan rampingtoolchain build tidak ikut terbang ke produksi — ukuran adalah permukaan seranganMulti-Stage Build, Persiapan Production Image
Deployment menunjuk tag eksplisit; rilis kritis menunjuk digesttag bisa ditimpa siapa pun yang berhak push, digest tidak pernah berubahImage Digest, Strategi Versioning Image
Aplikasi berjalan sebagai USER non-rootproses UID 0 di dalam container adalah kegagalan desain yang menunggu satu eksploitasiMenjalankan Container Non-Root, Workdir dan User
Base image minimal — tanpa shell bila aplikasi mengizinkanpenyerang tanpa shell tidak punya ruang jelajah setelah masukMengurangi Ukuran Image, Persiapan Production Image
Scan image lolos ambang — tanpa critical/highimage mewarisi CVE dependensinya; lubang yang diketahui adalah pintu termurahImage Scanning, Image Provenance

Container #

ItemMengapaRujukan
Root filesystem read-only dengan tmpfs untuk state kecilaplikasi yang tidak bisa menulis berkas tak bisa ditulisi balikRead-Only Filesystem
Capabilities di-drop semaksimal yang aplikasi izinkandua belas kapabilitas bawaan jauh lebih banyak dari kebutuhan aplikasi webLinux Capabilities
Tidak ada satu pun container berjalan –privilegedprivileged melepas seccomp, seluruh device host, dan semua pagar kernel sekaligusPrivileged Container
Resource limit CPU dan memory terpasang di seluruh servicetetangga yang rakus dan OOM killer tidak boleh menghakimi seluruh hostCPU dan Memory Limit, Resource di Compose
pids-limit mengikat percabangan prosesfork bomb mati di dalam container, bukan di lantai mesinPIDs Limit dan Ulimit

Jaringan #

ItemMengapaRujukan
Hanya proxy yang menerbitkan port host (80/443)satu pintu publik berarti satu permukaan yang diaudit dan satu sertifikat yang dirawatReverse Proxy dan TLS, Port Mapping
Pemetaan debug internal terikat 127.0.0.1loopback tidak menembus ufw, docker-proxy, maupun internetPort Publishing, Docker di VPS
Stack hidup di custom network dan memanggil nama serviceDNS embedded menjawab dan stack terisolasi dari tetangga di bridge defaultMembuat Custom Network, Container DNS

Data #

ItemMengapaRujukan
Volume produksi berlabel env=prod dan dideklarasikan externaldikelola oleh operator, selamat dari down -v maupun prune yang salah waktuPersistent Storage Production
Backup terbukti bisa dipulihkan pada drill terakhirbackup yang tak teruji adalah harapan dengan nama berkasBackup dan Recovery
DRP tertulis dengan angka RTO dan RPOmusibah dikerjakan dari lembar runbook, bukan dari improvisasi panikBackup dan Recovery, Backup dan Restore Volume
Retensi berjalan: staging tujuh hari, off-site lebih panjangarsip tanpa rotasi hanya memindahkan musibah dari “data hilang” ke “disk penuh”Backup dan Restore Volume, Backup Database

Log #

ItemMengapaRujukan
Driver log dengan rotasi terpasang di daemon.jsonlog tanpa batas adalah bom waktu yang meledak pelan di diskLogging Production, Konfigurasi Docker Engine
Log aplikasi terstruktur: satu event satu baris JSONbaris yang bisa diurai mesin bisa disaring tanpa menebak-tebakDocker Logs
Korelasi lintas service: nama container, NTP sinkron, trace idsatu permintaan bisa dijahit dari proxy sampai db dalam satu grepLogging Production

Keamanan #

ItemMengapaRujukan
Rahasia hidup di compose secrets atau vault, bukan environment polosdocker inspect menampilkan environment apa adanya kepada siapa pun yang bisa membacanyaSecrets dan Environment, Secret Management
Rotasi rahasia terjadwal dan tercatat tanggalnyakata sandi berumur panjang adalah kunci pintu yang tak pernah digantiSecrets dan Environment
Socket Docker tidak di-mount ke container mana punsocket yang terpasang adalah akar host yang dipinjamkan tanpa auditDocker Socket Security
Engine dan base image diperbarui lewat jadwal rilis yang disiplinlubang yang sudah diketahui publik paling sering dipakai justru karena tak pernah ditutupDocker di VPS, Image Scanning

Operasional #

ItemMengapaRujukan
Healthcheck tiga lapis: di image, diinjak compose, digerbangi depends_on“proses hidup” dan “siap melayani” adalah dua kalimat yang berbedaHealthcheck dan Restart Policy, HEALTHCHECK
Restart policy unless-stopped pada seluruh servicerestart daemon dan reboot server tidak boleh menjadi pemadaman manualRestart Policy, Docker di VPS
Runbook rilis dan rollback tertulis dan pernah dilatihtujuh langkah dipegang saat kepala tenang, bukan disusun saat daruratUpdate dan Rollback
RTO dan RPO diambil dari durasi drill sungguhanangka yang diukur bisa dipercaya; angka yang dikira menagih bungaBackup dan Recovery

Cara Memakai Checklist #

Status tiap baris hanya mengenal dua nilai: ✅ berarti berlaku dan punya bukti, ⬜ berarti belum atau tidak lagi — tidak ada kolom “hampir”, karena “hampir” adalah ⬜ yang berusaha menyelamatkan muka. Dua momen pemakaian mengatur ritmenya. Saat go-live: seluruh dua puluh delapan baris harus ✅ tanpa kecuali; satu ⬜ pada kategori Keamanan atau Data bukan catatan kaki melainkan penundaan — toko tidak boleh melayani pelanggan dengan pintu yang diketahui terbuka. Saat audit bulanan: cetak checklist, jalankan perintah audit pada bagian berikutnya, tandai hasilnya, dan simpan lembar hasilnya sebagai berkas bukti; item yang bertahan ⬜ selama dua audit berturut-turut naik kelas dari catatan menjadi pekerjaan berjadwal. Checklist yang ditandai tanpa menjalankan audit terminal adalah dongeng — dan bagian berikutnya memastikan kita tidak bercerita.

Audit Cepat dari Terminal #

Lima perintah membaca keadaan yang sungguhan, bukan keadaan yang dideklarasikan. Perintah pertama menguji tiga kebijakan container sekaligus pada seluruh stack:

bash
$ cd /srv/toko
$ docker inspect --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}} read_only={{.HostConfig.ReadonlyRootfs}} cap_drop={{.HostConfig.CapDrop}}' $(docker compose ps -q)

Output yang Dihasilkan:

text
/toko-proxy-1 restart=unless-stopped read_only=false cap_drop=[NET_RAW]
/toko-frontend-1 restart=unless-stopped read_only=true cap_drop=[NET_RAW]
/toko-backend-1 restart=unless-stopped read_only=true cap_drop=[NET_RAW]
/toko-db-1 restart=unless-stopped read_only=false cap_drop=[NET_RAW]
/toko-cache-1 restart=unless-stopped read_only=false cap_drop=[NET_RAW]

Lima baris itu memberi nilai cepat: restart=unless-stopped seragam ✅, cap_drop=[NET_RAW] seragam ✅, sedangkan read_only membagi dua — frontend dan backend sudah bersih, sementara proxy, db, dan cache masih false. Ketiganya punya alasan historis (nginx menulis berkas pid, postgres menulis PGDATA, redis menulis dump), dan ketiganya juga punya solusi yang sama: tmpfs untuk path kecil sebagaimana resep Chapter Read-Only Filesystem — itulah cara kerja audit yang sehat: menemukan lubang yang sah untuk ditemukan. Perintah kedua membaca permukaan jaringan dari sudut pandang daemon:

bash
$ for c in $(docker compose ps -q); do docker inspect --format '{{.Name}} {{json .HostConfig.PortBindings}}' "$c"; done

Output yang Dihasilkan:

text
/toko-proxy-1 {"443/tcp":[{"HostIp":"","HostPort":"443"}],"80/tcp":[{"HostIp":"","HostPort":"80"}]}
/toko-frontend-1 {}
/toko-backend-1 {}
/toko-db-1 {"5432/tcp":[{"HostIp":"127.0.0.1","HostPort":"5432"}]}
/toko-cache-1 {}

Baris proxy menunjukkan HostIp kosong — berarti terikat 0.0.0.0 dan memang disengaja untuk pintu publik; baris db menunjukkan pemetaan debug yang terkurung di 127.0.0.1 — lolos aturan kategori Jaringan, dengan catatan pencabutan bila pemetaan itu tak lagi dibutuhkan; tiga sisanya {} — murni internal. Bila suatu hari baris baru muncul dengan HostIp kosong pada service selain proxy, itulah alarm yang pertama. Perintah ketiga menagih janji kategori Image:

bash
$ docker scout cves registry.example.com/toko/backend:1.5.0

Output yang Dihasilkan (dipotong):

text
i registry.example.com/toko/backend:1.5.0
    0C 1H 4M 12L
...
ID             Severity    Package      Version   Fixed in
CVE-2026-3928  High        npm/semver   5.7.1     5.7.2
...

Ringkasan 0C 1H 4M 12L berarti nol critical, satu high, empat medium, dua belas low — dan baris high itu menjatuhkan nilai item “scan lolos ambang” menjadi ⬜ sampai dependensi dinaikkan ke 5.7.2 dan image di-rebuild; audit mencatatnya sebagai temuan, bukan mengkalimkannya sebagai kelulusan. Dua perintah terakhir menutup lingkaran data dan disk:

bash
$ docker system df
$ docker volume ls --filter label=env=prod

Output yang Dihasilkan:

text
TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          6         5         1011MB    142MB (14%)
Containers      5         5         36.7MB    0B (0%)
Local Volumes   2         2         489MB     0B (0%)
Build Cache     0         0         0B        0B
DRIVER    VOLUME NAME
local     toko_db-data
local     toko_uploads

Enam image dengan lima aktif — satu generasi cadangan untuk rollback, rencana yang justru sehat; dua volume berlabel menjawab item kategori Data; dan RECLAIMABLE 142MB adalah generasi 1.3.0 yang menunggu kebijakan retensi, bukan panic prune.

bash
#!/usr/bin/env bash
set -euo pipefail
cd /srv/toko

echo "== kebijakan container =="
docker inspect --format '{{.Name}} restart={{.HostConfig.RestartPolicy.Name}} read_only={{.HostConfig.ReadonlyRootfs}} cap_drop={{.HostConfig.CapDrop}}' $(docker compose ps -q)

echo "== port yang diterbitkan =="
for c in $(docker compose ps -q); do
  docker inspect --format '{{.Name}} {{json .HostConfig.PortBindings}}' "$c"
done

echo "== konsumsi disk =="
docker system df

echo "== volume produksi =="
docker volume ls --filter label=env=prod

echo "== ringkasan kerentanan =="
docker scout cves registry.example.com/toko/backend:1.5.0 | head -n 4

Skrip audit toko pada Jumat terakhir bulan Oktober menghasilkan tiga temuan yang semuanya jujur: tiga rootfs masih writable dengan rencana tmpfs, satu high pada semver dengan rencana rebuild, dan pemetaan debug 5432 yang menunggu keputusan pencabutan. Tidak satu pun menutup go-live — semuanya masuk daftar pekerjaan berjadwal — dan itulah hasil akhir yang benar dari sebuah audit: bukan pernyataan kesempurnaan, melainkan daftar yang bisa dikerjakan.

Produksi toko kini berdiri di atas fondasi yang bisa dipertanggungjawabkan baris demi baris — image yang ramping dan teridentifikasi digest, container yang dipagari kernel, jaringan yang bermuara ke satu pintu TLS, data yang punya tiga salinan dan runbook pulang, log yang terukur dan terkorelasi, dan ritual operasional yang tertulis serta terlatih. Namun perhatikan bentuk dari seluruh kerja ini: build, scan, tag, catat digest, deploy, verifikasi, audit — urutan langkah yang sama, setiap kali, tanpa improvisasi. Urutan yang teratur seperti itu adalah pekerjaan yang paling layak diserahkan kepada mesin, dan penyerahan itulah agenda section berikutnya: CI/CD dan Automation, dibuka dari Chapter Konsep Container di CI/CD, tempat seluruh ritual manual section ini berubah menjadi pipeline yang berjalan sendiri setiap commit.

Referensi #