Keamanan Container Docker — 7 Langkah yang Sering Dilewatkan Developer
Tahun 2021 lalu, saya diminta salah satu tim DevOps buat bantu investigasi insiden yang cukup bikin pusing. Jadi, container aplikasi mereka yang jalan di Kubernetes cluster tiba-tiba berperilaku aneh — CPU spike ke 100%, koneksi mencurigakan ke IP luar negeri, dan ada file binary aneh di dalam container. Setelah diinvestigasi, ternyata attacker berhasil masuk lewat dependency npm yang di-compromise (ini jaman-jaman maraknya supply chain attack), terus karena container mereka jalan sebagai root user, attacker bisa escape dari container dan akses ke host node Kubernetes-nya. Untungnya mereka cuma dapet akses ke node worker yang sifatnya ephemeral, tapi tetap aja ini insiden serius. Inilah kenapa keamanan container docker bukan cuma jargon — dampaknya nyata dan bisa menimpa tim mana aja.
Nah dari situ saya makin paham: keamanan container docker itu jauh lebih kompleks dari sekadar “scan image sebelum deploy”. Banyak developer — bahkan yang udah senior — masih suka ngelewatin langkah-langkah fundamental yang sebenernya simpel tapi dampaknya gede banget. Di artikel ini, saya bakal share 7 langkah yang paling sering saya temuin diabaikan, berdasarkan pengalaman 8 tahun ngebug bounty dan audit infrastruktur cloud.
Kenapa Container Security Berbeda dengan VM Security
Sebelum masuk ke langkah-langkahnya, penting buat ngerti dulu kenapa security container itu beda sama security VM tradisional. Banyak yang masih mikir container itu “VM mini” — padahal arsitekturnya fundamental beda.
Di virtual machine, tiap VM punya kernel sendiri, OS sendiri, dan isolated penuh lewat hypervisor. Kalau attacker compromise satu VM, mereka masih harus escape dari hypervisor (yang susah banget) buat bisa akses VM lain di host yang sama.
Di container, semua container di satu host share kernel yang sama. Isolasinya pake Linux kernel features — namespaces (buat isolasi process, network, mount, etc) dan cgroups (buat limit resource). Ini artinya kalau container jalan sebagai root, dan ada kernel vulnerability, attacker bisa break out dari container dan akses semua container lain di host yang sama — plus akses ke host-nya langsung. Inilah kenapa prinsip-prinsip container security sangat berbeda dan lebih ketat dibanding VM security.
7 Langkah Keamanan Container Docker yang Sering Dilewatkan
1. Selalu Gunakan Official Image (atau Build Sendiri dari Scratch)
Ini langkah pertama dan paling basic, tapi masih banyak yang melanggar. Sering banget saya liat developer nge-pull image random dari Docker Hub — misalnya john123/mysql:latest — dengan asumsi “yang penting jalan”. Padahal image random di Docker Hub itu nggak ada yang ngeverifikasi isinya.
Saya pernah nemuin image MySQL di Docker Hub yang katanya “optimized buat production” tapi ternyata mengandung mining script yang jalan di background. Si pembuat image mungkin nggak berniat jahat — bisa jadi environment development-nya sendiri yang udah kena malware, terus tanpa sadar dia push image yang terinfeksi ke Docker Hub. Ini yang dinamakan supply chain attack lewat container image. Dan percayalah, ini makin sering terjadi.
Aturan emasnya: selalu gunakan official image (mysql, nginx, node, python, alpine) atau verified publisher image (bitnami, linuxserver). Kalau kamu butuh custom image, build sendiri dari official base image. Jangan asal pull dari sumber yang nggak jelas.
2. Scan Image untuk Vulnerability Sebelum Production
Setelah yakin image-nya dari sumber tepercaya, langkah selanjutnya adalah scanning vulnerability. Tools yang paling saya rekomendasikan untuk ini adalah Trivy dari Aqua Security. Open source, ringan, updatenya cepet, dan bisa diintegrasikan ke CI/CD pipeline kamu.
Cara pakainya simpel banget:
# Scan image lokal
trivy image nginx:latest
# Scan image dari registry
trivy image myregistry.com/myapp:v1.0.0
# Scan dengan severity filter
trivy image --severity HIGH,CRITICAL myapp:latest
Yang sering bikin kaget saat pertama kali scan adalah banyaknya CVE yang ketemu. Image official aja kadang punya puluhan vulnerability kelas medium. Tapi tenang — nggak semua vulnerability itu exploit di konteks aplikasi kamu. Fokus ke yang HIGH dan CRITICAL dulu, dan pastiin kamu ngerti impact-nya sebelum mutusin buat abaikan atau fix.
Integrasiin scanning ke CI/CD pipeline kamu. Jangan sampai ada image baru yang naik ke production tanpa lewat vulnerability scan dulu. Kalau ketemu vulnerability CRITICAL, pipeline harus gagal — fail early, fail loud.
3. Jangan Jalankan Container sebagai Root
Ini THE most important rule di container security, dan juga yang paling sering dilanggar. Secara default, Docker container jalan sebagai root user. Artinya process di dalam container punya UID 0 — same privilege level dengan root di host (secara kernel, meskipun namespace membatasi apa yang bisa dilihat).
Langkah simpel yang sering banget di-skip:
# Jangan begini — ini default Docker
FROM node:18-alpine
COPY . /app
CMD ["node", "/app/index.js"]
# Tapi begini — bikin user non-root dulu
FROM node:18-alpine
RUN addgroup -g 1001 -S nodejs && \
adduser -S nodejs -u 1001
USER nodejs
COPY --chown=nodejs:nodejs . /app
CMD ["node", "/app/index.js"]
Atau kalau kamu males bikin user sendiri, banyak official image yang udah nyediain user bawaan — contohnya image nginx:alpine punya user nginx, node:alpine punya user node.
Kenapa ini penting banget? Karena kalau aplikasi kamu punya vulnerability (misalnya RCE atau command injection), attacker yang berhasil execute code di dalam container bakal dapet akses sebagai user apa pun yang kamu tentuin. Kalau kamu jalanin sebagai root, mereka langsung punya akses root di dalam container, dan dari situ mereka bisa nyoba berbagai teknik container escape — abuse capabilities, exploit kernel vulnerability, atau akses Docker socket kalau kamu (amit-amit) mount /var/run/docker.sock ke dalam container.
4. Batasi Resource Container dengan CPU dan Memory Limit
Ini lebih ke security-through-limitation. Tanpa resource limit, satu container yang kena DoS atau memory leak bisa nge-habis semua resource host dan bikin container lain ikut crash — ini namanya “noisy neighbor” problem.
Di Docker Compose:
services:
myapp:
image: myapp:latest
deploy:
resources:
limits:
cpus: '0.50'
memory: 256M
reservations:
cpus: '0.25'
memory: 128M
Atau di command line:
docker run --cpus="0.5" --memory="256m" myapp:latest
Selain mencegah noisy neighbor, resource limit juga ngebantu mencegah serangan cryptojacking — di mana attacker yang berhasil compromise container kamu langsung jalanin crypto miner yang nge-habis seluruh CPU host. Dengan CPU limit, paling nggak dampaknya terbatas.
5. Gunakan Read-Only Filesystem untuk Container Production
Container yang udah jalan di production idealnya nggak perlu nulis apa-apa ke filesystem container-nya (kecuali untuk temporary files atau caching yang emang sengaja). Semua persistent data harus disimpen di volume yang di-mount terpisah.
Dengan read-only filesystem, kalau attacker berhasil masuk ke container, mereka nggak bisa install tools tambahan, modifikasi binary sistem, atau nulis backdoor. Ini nambah friction yang signifikan buat attacker.
docker run --read-only \
--tmpfs /tmp \
--tmpfs /run \
myapp:latest
Perhatikan: kamu perlu bikin direktori seperti /tmp dan /run sebagai tmpfs (memory-backed temporary storage) karena banyak aplikasi yang butuh nulis di situ. Tapi selain kedua direktori itu dan direktori data yang kamu mount sebagai volume, semua file system container harusnya read-only.
6. Pahami dan Terapkan Network Segmentation
Default-nya, Docker bikin virtual bridge network (docker0) dan semua container bisa komunikasi satu sama lain di network itu. Ini artinya kalau satu container compromised, attacker bisa langsung scanning dan nyerang container lain di host yang sama lewat network bridge ini.
Best practice-nya:
- Bikin custom bridge network per aplikasi, jangan pakai default bridge
- Container yang nggak butuh komunikasi satu sama lain ditaruh di network terpisah
- Container yang butuh expose port ke internet sebaiknya ditaruh di belakang reverse proxy (nginx, traefik) dan jangan langsung expose port ke host
- Gunakan network policy (kalau pakai Kubernetes) atau iptables rules buat restrict komunikasi antar container
# Docker Compose — contoh network segmentation
networks:
frontend:
backend:
services:
nginx:
networks:
- frontend
app:
networks:
- frontend
- backend
database:
networks:
- backend
Di contoh di atas, si nginx cuma bisa komunikasi sama app (lewat frontend network), dan database cuma bisa komunikasi sama app (lewat backend network). Kalau nginx di-compromise, attacker nggak bisa langsung nyerang database karena mereka nggak di network yang sama.
7. Jangan Simpan Secret di Dockerfile atau Environment Variable
Ini jebakan yang paling klasik. Saya masih suka nemuin Dockerfile yang isinya:
# JANGAN PERNAH BEGINI
FROM python:3.10
ENV DATABASE_URL=postgres://admin:P4ssw0rd!@db:5432/mydb
ENV AWS_ACCESS_KEY_ID=AKIA1234567890ABCDEF
ENV AWS_SECRET_ACCESS_KEY=abcdef1234567890abcdef1234567890
Sekali secret masuk ke Dockerfile, dia bakal embedded di image layer selamanya. Mau kamu hapus line-nya di commit berikutnya, image layer sebelumnya tetep nyimpen secret itu. Dan kalau kamu push image ke public registry… ya udah deh, selamat tinggal keamanan.
Environment variable juga bukan tempat yang aman buat nyimpen secret. Kenapa? Karena ENV variable bisa diakses oleh siapa aja yang bisa execute command di dalam container (docker inspect, cat /proc/1/environ, atau sekadar env).
Gunakan Docker secrets (di Docker Swarm) atau Kubernetes secrets buat nyimpen credential. Untuk development local, gunakan .env file yang udah di .gitignore dan di-mount sebagai volume. Untuk production, gunakan secret manager seperti AWS Secrets Manager, HashiCorp Vault, atau Azure Key Vault, lalu inject secret sebagai file atau environment variable saat container start.
Bonus: Tools yang Wajib Kamu Coba
Selain langkah-langkah di atas, ada beberapa tools yang bisa bantu kamu audit keamanan container:
- Docker Bench Security — script dari Docker sendiri yang ngecek konfigurasi Docker daemon dan container kamu terhadap CIS Docker Benchmark. Jalankan sekali doang:
docker run -it --net host --pid host --userns host --cap-add audit_control -v /var/lib:/var/lib -v /var/run/docker.sock:/var/run/docker.sock --label docker_bench_security docker/docker-bench-security - Trivy — udah disebut di atas, buat vulnerability scanning
- Docker Scout — dari Docker Inc, integrated sama Docker Desktop dan Docker Hub, bagus buat analisis image dan rekomendasi perbaikan
- Hadolint — linter buat Dockerfile yang bisa nangkep misconfigurations kayak
USER rootatau apt-get tanpa cleanup - Falco — runtime security monitoring buat container, bisa deteksi perilaku mencurigakan kayak shell spawned di dalam container atau file system changes yang nggak diharapkan
Penutup
Keamanan container docker itu bukan something you bolt on after the fact. It’s something you bake in dari awal lifecycle development. Dari pemilihan base image, proses build, sampai deployment dan runtime monitoring — semuanya harus ada security consideration-nya.
7 langkah di atas bukan hal yang ribet kok. Sebagian besar cuma butuh beberapa baris konfigurasi tambahan di Dockerfile atau Compose file kamu. Tapi impact-nya? Gede banget. Satu container yang di-compromise tanpa proper hardening bisa jadi pintu masuk ke seluruh infrastruktur cloud kamu. Jadi daripada nunggu insiden kayak yang saya ceritain di atas, mending secure dari sekarang.