Dasar-dasar Keamanan Container

Dasar-dasar Keamanan Container

Bitnesia Sep 12, 2026 9 EN

Container yang berjalan tanpa mempertimbangkan aspek keamanan pada dasarnya cuma proses biasa di host yang kebetulan dibungkus dengan namespace dan cgroup. Kalau proses di dalamnya berjalan sebagai root dengan seluruh capabilities default masih menempel, sebuah vulnerability pada aplikasi bisa berujung jauh lebih parah daripada sekadar crash, mulai dari attacker yang mengubah file sistem, membaca socket Docker, sampai dalam skenario terburuk keluar dari container menuju host. Sysadmin/DevOps Engineer yang menjalankan container di production dan Developer yang menulis Dockerfile untuk aplikasinya sama-sama punya peran dalam mengurangi risiko ini. Bab ini membahas praktik dasar keamanan container: mengapa isolasi container tidak boleh disamakan dengan isolasi virtual machine, bagaimana mengatur user privileges dan Linux capabilities agar container berjalan dengan hak akses seminim mungkin, cara mengunci filesystem container jadi read-only, dan cara mengaudit konfigurasi keamanan host Docker secara sistematis.

22.1 Prinsip Keamanan Container

Sebelum masuk ke konfigurasi teknis, penting memahami batasan model isolasi container supaya ekspektasi keamanannya tidak berlebihan. Bagian ini membahas prinsip dasar yang jadi fondasi seluruh praktik keamanan container di sub-bab berikutnya.

22.1.1 Container Berbagi Kernel dengan Host

Berbeda dari virtual machine yang punya kernel sendiri-sendiri, seluruh container di satu host Docker berbagi kernel Linux yang sama dengan host-nya. Isolasi antar container dan antara container dengan host dicapai lewat kombinasi namespace (membatasi apa yang bisa dilihat proses), cgroup (membatasi resource yang bisa dipakai), dan Linux capabilities (membatasi operasi privileged apa yang boleh dilakukan). Konsekuensinya, sebuah vulnerability pada kernel Linux itu sendiri berpotensi dieksploitasi dari dalam container untuk mempengaruhi host, sesuatu yang jauh lebih sulit terjadi pada isolasi tingkat virtual machine yang punya lapisan hypervisor terpisah. Karena itu, menjaga versi kernel host tetap ter-update dengan patch keamanan terbaru sama pentingnya dengan mengamankan konfigurasi container itu sendiri.

22.1.2 Prinsip Least Privilege dan Defense in Depth

Dua prinsip yang jadi acuan hampir semua praktik keamanan container di bab ini adalah least privilege dan defense in depth. Least privilege berarti container hanya diberi hak akses, capability, dan resource seminim mungkin yang benar-benar dibutuhkan aplikasinya untuk berjalan, bukan mengikuti konfigurasi default yang cenderung longgar demi kompatibilitas luas. Defense in depth berarti keamanan tidak digantungkan pada satu lapisan saja; kombinasi non-root user, capability yang di-drop, filesystem read-only, dan pemindaian rutin, jauh lebih tangguh dibanding mengandalkan satu kontrol tunggal, karena kalau satu lapisan gagal atau ditembus, lapisan lain tetap menahan dampaknya.

Di lapangan, penerapan dua prinsip ini biasanya dimulai dari hal yang paling murah untuk diterapkan dan paling besar dampaknya: memastikan proses aplikasi tidak berjalan sebagai root di dalam container. Sub-bab berikutnya membahas ini secara detail.

22.2 User Privileges dalam Container

Salah satu kesalahan konfigurasi paling umum ditemukan Sysadmin/DevOps Engineer saat audit keamanan adalah container yang menjalankan proses aplikasinya sebagai root, padahal aplikasinya sendiri tidak pernah butuh hak akses setinggi itu. Bagian ini membahas kenapa hal ini berisiko dan bagaimana menjalankan container dengan user non-root.

22.2.1 Masalah Root by Default

Kalau instruksi USER tidak dituliskan di Dockerfile, proses di dalam container berjalan sebagai root secara default. root di dalam container memang tidak otomatis sama dengan root di host berkat isolasi user ID lewat namespace, tapi tanpa konfigurasi tambahan, UID 0 di dalam container tetap terpetakan ke UID 0 di host. Artinya, kalau attacker berhasil mengeksploitasi celah pada aplikasi untuk mengeksekusi perintah arbitrer di dalam container, perintah itu berjalan dengan hak akses root penuh di dalam container tersebut, termasuk kemampuan membaca dan menulis file apa pun di dalam filesystem container, mengubah konfigurasi jaringan container, atau memanfaatkan capability yang masih menempel untuk mencoba keluar dari batas isolasi.

22.2.2 Menjalankan Container sebagai Non-Root

Cara paling langsung menghindari masalah ini adalah mendefinisikan user non-root langsung di dalam Dockerfile lewat instruksi USER, sehingga image yang dihasilkan secara default sudah berjalan dengan hak akses terbatas tanpa bergantung pada operator mengingat untuk menambahkan flag tertentu setiap kali menjalankan container.

FROM node:20-alpine

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app
COPY --chown=appuser:appgroup . .

USER appuser

CMD ["node", "server.js"]

Perintah addgroup dan adduser di atas memakai sintaks Alpine Linux; distro base image lain seperti Debian atau Ubuntu memakai perintah groupadd dan useradd yang setara. Perhatikan penggunaan --chown pada COPY supaya file aplikasi langsung dimiliki user non-root tersebut, bukan tetap dimiliki root yang justru membuat appuser tidak bisa menulis ke direktori aplikasinya sendiri.

Kalau image yang dipakai tidak bisa diubah Dockerfile-nya, misalnya base image pihak ketiga, user non-root juga bisa dipaksakan langsung saat menjalankan container lewat flag --user pada docker run, asalkan user atau UID tersebut memang sudah ada di dalam image.

docker run -d --user 1000:1000 payment-service:1.4.2

Verifikasi user yang benar-benar dipakai proses di dalam container lewat docker exec, karena mengandalkan asumsi dari Dockerfile saja tidak cukup meyakinkan kalau image sempat diubah oleh pihak lain atau kalau flag --user lupa disertakan saat deployment.

docker exec payment-service whoami
docker exec payment-service id

22.2.3 User Namespace Remapping

Untuk lapisan proteksi tambahan yang berlaku di seluruh container pada satu host, Docker Engine mendukung user namespace remapping lewat opsi userns-remap di daemon.json. Fitur ini memetakan UID 0 (root) di dalam container ke UID non-privileged di host, sehingga meskipun sebuah proses berjalan sebagai root di dalam container, saat dilihat dari sisi host proses tersebut sebenarnya berjalan dengan UID biasa yang tidak punya hak istimewa apa pun. Ini jadi lapisan pertahanan tambahan yang tetap berguna bahkan kalau instruksi USER di Dockerfile lupa diterapkan.

{
  "userns-remap": "default"
}
sudo systemctl restart docker

Nilai default membuat Docker Engine otomatis membuat user dan rentang subordinate UID/GID khusus untuk keperluan remapping ini. Setelah daemon di-restart, periksa direktori data Docker untuk memastikan remapping aktif; direktori data container yang biasanya dimiliki root sekarang dimiliki oleh UID hasil remapping.

sudo ls -ld /var/lib/docker/*/

Perlu dicatat, userns-remap punya sejumlah keterbatasan yang perlu diperiksa dulu sebelum diaktifkan di production, di antaranya tidak kompatibel dengan container yang berjalan dengan --privileged, dan bisa mempengaruhi container yang memakai fitur berbagi namespace seperti --pid=host atau --net=host. Docker daemon sendiri tetap berjalan sebagai root penuh meski userns-remap aktif; kalau tujuannya menjalankan daemon dan seluruh container tanpa privilege root sama sekali, opsi yang perlu dipertimbangkan adalah Rootless mode, yang konfigurasinya berbeda dan di luar cakupan bab ini.

22.3 Linux Capabilities

Selain mengatur user yang menjalankan proses, lapisan kontrol lain yang lebih granular adalah Linux capabilities, yaitu pemecahan hak istimewa root tradisional menjadi unit-unit kecil yang bisa diberikan atau dicabut satu per satu.

22.3.1 Capabilities Default dan Risikonya

Linux capabilities memecah hak istimewa yang dulunya melekat sepenuhnya pada user root menjadi unit-unit terpisah, misalnya NET_BIND_SERVICE untuk mem-bind socket ke port di bawah 1024, atau SYS_CHROOT untuk memanggil chroot(). Docker secara default sudah menjalankan container dengan sebagian kecil capability saja dari keseluruhan capability yang ada di Linux, bukan seluruhnya, tapi daftar default ini masih mengandung capability seperti CHOWN, DAC_OVERRIDE, SETUID, dan SETGID yang kalau disalahgunakan lewat aplikasi yang punya vulnerability, tetap bisa membahayakan integritas container. Kebanyakan aplikasi web atau service backend biasa sebenarnya tidak butuh capability apa pun dari daftar default tersebut untuk berjalan normal.

22.3.2 Mencabut dan Menambah Capability

Praktik terbaik yang direkomendasikan dokumentasi resmi Docker adalah mencabut seluruh capability lebih dulu lewat --cap-drop=ALL, lalu menambahkan kembali hanya capability spesifik yang benar-benar dibutuhkan aplikasi lewat --cap-add.

docker run -d \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  -p 80:80 \
  nginx:1.27

Pada contoh di atas, nginx yang perlu mem-bind ke port 80 tetap bisa berjalan normal meski seluruh capability lain sudah dicabut, karena satu-satunya capability yang benar-benar dibutuhkan untuk skenario tersebut, NET_BIND_SERVICE, ditambahkan kembali secara eksplisit. Untuk mengetahui capability mana saja yang sebenarnya dipakai sebuah aplikasi, cara paling praktis di lapangan adalah memulai dari --cap-drop=ALL lalu memperhatikan error apa yang muncul saat aplikasi gagal berjalan atau gagal menjalankan operasi tertentu; pesan error permission denied pada operasi yang berhubungan dengan jaringan atau file sistem biasanya mengarah langsung ke capability mana yang perlu ditambahkan kembali.

22.3.3 Risiko Flag --privileged

Flag --privileged memberikan seluruh capability ke container sekaligus mengizinkan akses ke semua device di host dan melonggarkan pembatasan AppArmor atau SELinux, membuat proses di dalam container punya akses yang nyaris setara dengan proses yang berjalan langsung di host. Docker sendiri secara eksplisit memperingatkan agar flag ini dipakai dengan hati-hati. Attacker yang berhasil mengeksekusi kode di dalam container dengan --privileged aktif punya jalan yang jauh lebih mudah untuk melakukan container escape menuju host dibanding container dengan capability terbatas.

Kebutuhan yang sekilas terlihat mengharuskan --privileged, misalnya container yang perlu mengakses device tertentu di host, biasanya bisa dipenuhi dengan cara yang jauh lebih terbatas lewat flag --device yang memberi akses ke device spesifik saja tanpa membuka seluruh device host.

docker run -d --device=/dev/ttyUSB0 iot-gateway:1.0

Sebelum mengaktifkan --privileged di production, cek dulu apakah kebutuhan sebenarnya bisa dipenuhi kombinasi --cap-add yang spesifik dan --device, karena keduanya memberi akses yang jauh lebih sempit dan terukur dibanding memberi seluruh privilege sekaligus.

22.4 Read-Only Filesystem

Selain membatasi siapa yang menjalankan proses dan operasi apa yang boleh dilakukan, lapisan kontrol berikutnya adalah membatasi apa yang boleh diubah di dalam filesystem container itu sendiri.

22.4.1 Mengunci Filesystem dengan Flag --read-only

Kebanyakan aplikasi, terutama service backend yang stateless, sebenarnya tidak perlu menulis apa pun ke filesystem container-nya sendiri selain ke direktori temporary tertentu seperti /tmp. Mengunci filesystem container jadi read-only lewat flag --read-only membuat attacker yang berhasil masuk ke dalam container tidak bisa menulis file baru, mengubah binary yang sudah ada, atau menanam backdoor persisten di dalam filesystem container tersebut.

docker run -d --read-only payment-service:1.4.2

Kalau aplikasi memang butuh menulis ke direktori tertentu, misalnya file cache sementara atau socket runtime, pasangkan --read-only dengan tmpfs mount yang menyediakan direktori tulis berbasis memori khusus untuk keperluan tersebut, tanpa membuka seluruh filesystem jadi writable.

docker run -d \
  --read-only \
  --tmpfs /tmp \
  --tmpfs /var/run \
  payment-service:1.4.2

Kombinasi --read-only dengan --tmpfs ini juga sering dipasangkan sekaligus dengan --user dan --cap-drop=ALL dalam satu perintah docker run yang sama untuk mendapatkan beberapa lapisan proteksi sekaligus, sesuai prinsip defense in depth yang sudah dibahas sebelumnya.

docker run -d \
  --user 1000:1000 \
  --cap-drop=ALL \
  --read-only \
  --tmpfs /tmp \
  payment-service:1.4.2

22.4.2 Verifikasi dan Troubleshooting

Pastikan flag --read-only benar-benar aktif dan berfungsi lewat docker inspect, lalu coba tulis file uji ke dalam container untuk memastikan operasi tersebut ditolak sistem.

docker inspect --format='{{.HostConfig.ReadonlyRootfs}}' payment-service
docker exec payment-service sh -c "echo test > /app/test.txt"

Perintah di atas seharusnya gagal dengan pesan error read-only file system kalau konfigurasi sudah benar. Kalau aplikasi tiba-tiba crash atau gagal start setelah --read-only diaktifkan, penyebab paling umum adalah aplikasi mencoba menulis ke direktori yang belum di-mount sebagai tmpfs, misalnya direktori log atau direktori cache milik framework tertentu; periksa log error aplikasi untuk menemukan path mana yang gagal ditulis, lalu tambahkan tmpfs mount untuk path tersebut.

22.5 Audit Keamanan dengan Docker Bench

Menerapkan satu per satu praktik di atas secara manual pada setiap container cukup memakan waktu dan rawan terlewat, terutama di host dengan banyak container berjalan sekaligus. Bagian ini membahas cara mengaudit konfigurasi keamanan Docker Engine dan container yang berjalan di atasnya secara sistematis.

22.5.1 CIS Docker Benchmark

CIS Docker Benchmark adalah kumpulan rekomendasi konfigurasi keamanan Docker yang diterbitkan oleh Center for Internet Security (CIS), mencakup rekomendasi mulai dari konfigurasi host, konfigurasi Docker daemon, image dan Dockerfile, sampai konfigurasi container saat runtime. Benchmark ini jadi acuan standar industri yang dipakai banyak Sysadmin/DevOps Engineer untuk menilai seberapa aman konfigurasi Docker di lingkungan mereka, dan beberapa poin di dalamnya sejalan langsung dengan praktik yang sudah dibahas di bab ini, seperti larangan menjalankan container dengan --privileged tanpa alasan kuat dan anjuran menjalankan container sebagai non-root.

22.5.2 Menjalankan Docker Bench for Security

Docker Bench for Security adalah script audit open source dari Docker yang mengimplementasikan sebagian besar pemeriksaan pada CIS Docker Benchmark secara otomatis, memeriksa konfigurasi host, daemon, dan container yang sedang berjalan lalu melaporkan hasilnya sebagai daftar pemeriksaan yang lolos, gagal, atau perlu perhatian manual. Image resmi docker/docker-bench-security yang pernah tersedia di Docker Hub sudah lama tidak di-maintain dan berpotensi membawa vulnerability dari dependency yang usang, jadi dokumentasi resminya menganjurkan meng-clone repository lalu mem-build sendiri image-nya sebelum dipakai.

git clone https://github.com/docker/docker-bench-security.git
cd docker-bench-security
docker build --no-cache -t docker-bench-security .

Setelah image lokal berhasil dibuat, jalankan pemeriksaan lewat perintah berikut.

docker run --rm --net host --pid host --userns host --cap-add audit_control \
  -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
  -v /etc:/etc:ro \
  -v /usr/bin/containerd:/usr/bin/containerd:ro \
  -v /usr/bin/runc:/usr/bin/runc:ro \
  -v /usr/lib/systemd:/usr/lib/systemd:ro \
  -v /var/lib:/var/lib:ro \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  --label docker_bench_security \
  docker-bench-security

Banyaknya opsi -v pada perintah di atas diperlukan supaya script di dalam container bisa membaca berbagai file dan direktori konfigurasi di host secara read-only untuk keperluan pemeriksaan, tanpa memberinya hak menulis apa pun ke sistem host. Hasil pemindaian ditampilkan langsung di terminal dalam bentuk daftar pemeriksaan berkode [PASS], [WARN], [INFO], dan [NOTE] untuk masing-masing item benchmark.

22.5.3 Menindaklanjuti Hasil Audit

Tidak semua temuan [WARN] dari Docker Bench for Security berarti harus langsung ditindaklanjuti tanpa pertimbangan konteks; beberapa rekomendasi bersifat sangat ketat dan bisa saja tidak relevan untuk setiap skenario, misalnya rekomendasi mengaktifkan userns-remap yang punya keterbatasan kompatibilitas seperti sudah dibahas sebelumnya. Sysadmin/DevOps Engineer perlu meninjau setiap temuan berdasarkan konteks lingkungan yang sebenarnya, memprioritaskan perbaikan pada temuan yang berhubungan langsung dengan container yang benar-benar menghadap ke jaringan publik atau menyimpan data sensitif, dan mendokumentasikan alasan kalau ada rekomendasi yang sengaja tidak diterapkan supaya keputusan itu bisa ditinjau ulang di kemudian hari. Menjalankan Docker Bench for Security secara berkala, bukan cuma sekali saat setup awal, membantu menangkap konfigurasi yang bergeser dari kondisi aman seiring waktu, misalnya container baru yang tanpa sengaja dijalankan dengan --privileged saat proses debugging lalu lupa dikembalikan ke konfigurasi semula.