# Cara Mengamankan Container Docker: 7 Langkah untuk Pemula

> Cara mengamankan container Docker untuk pemula: user non-root, filesystem read-only, cap-drop, secret di luar image, pin versi, scan, plus tiap cek.

URL: https://www.matthewswong.com/id/blog/cara-mengamankan-container-docker/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2026-10-17
- Updated: 2026-10-17
- Category: DevOps

> **Key takeaway:** Cara mengamankan container Docker untuk pemula: jalankan sebagai user non-root lewat USER, pasang --read-only dengan --tmpfs untuk folder yang harus bisa ditulis, buang semua capability dengan --cap-drop ALL, jauhkan secret dari image, kunci versi base image, lalu scan dengan Docker Scout atau Trivy. Tiap kontrol punya satu perintah cek.

Container kamu jalan, aplikasi menjawab di port-nya, lalu kamu anggap selesai. Kemudian ada yang bertanya apakah container itu aman, dan kamu sadar belum pernah melihat proses itu berjalan sebagai siapa, apa yang boleh ditulisnya, atau apa saja yang tertanam di image. Pemula biasanya tersangkut di tiga titik: tidak tahu bahwa defaultnya adalah root, tidak tahu cara membuktikan sebuah pengaturan benar-benar aktif, dan menyalakan kontrol yang lalu membuat aplikasi error tanpa penjelasan.

Panduan ini mengeraskan satu container Node.js kecil dalam tujuh langkah, dan tiap langkah ditutup dengan perintah yang menunjukkan hasilnya. Setiap flag saya cocokkan dengan dokumentasi resmi Docker dan Trivy. Tujuannya bukan membuat container tak bisa ditembus, karena tidak ada satu kontrol pun yang bisa itu. Tujuannya memperkecil apa yang bisa dipakai penyerang kalau aplikasimu suatu hari diretas, sambil jujur soal apa yang masih belum ada. Langkah terakhir membandingkan hasilnya dengan file Compose situs ini.

Mau lihat gambaran besarnya dulu, lengkap dengan diagram interaktif tentang lapisan di sekeliling satu container? Ikuti pelajaran gratis

Hardening Container Docker: Non-Root, Image Kecil, Scanning

 di jalur DevOps from zero. Post ini adalah praktik langsung untuk tugasnya.

## Yang kamu butuhkan

Kamu butuh Docker yang sudah berjalan di komputermu dan sebuah terminal. Kalau belum pernah membangun image, mulai dari

panduan Docker dari nol

 dulu. Memisahkan build dan runtime ke beberapa stage adalah topik tersendiri yang dibahas di panduan multi-stage build di bagian akhir, jadi Dockerfile di sini sengaja satu stage.

Docker Engine atau Docker Desktop, dan sebuah terminal

Satu folder berisi satu file, server.js, seperti di langkah 1

Opsional untuk langkah 6: CLI Trivy. Docker Scout tidak butuh apa-apa lagi kalau Docker kamu sudah punya perintah scout

## Langkah 1: Buat container awal dan buktikan ia berjalan sebagai root

Mulai dari versi yang belum diamankan supaya kamu bisa melihat apa yang berubah. Aplikasinya satu file yang menjawab hello. Perhatikan port-nya: dipublikasikan sebagai 127.0.0.1:3000:3000, artinya hanya komputermu sendiri yang bisa menjangkaunya. Kalau hanya menulis 3000:3000, port itu mendengarkan di semua interface, kesalahan yang muncul lagi di tabel error.

Perintah id mencetak user yang menjalankan proses. Tanpa instruksi USER, image berjalan sebagai root, dan referensi Compose menyatakan hal yang sama: kalau image tidak mengatur apa pun, user-nya root. Root di dalam container tidak sama dengan root di host, tetapi kuasanya jauh lebih besar daripada yang dibutuhkan aplikasimu.

## Langkah 2: Jalankan sebagai user non-root

Buat user dan group di Dockerfile, beri ID eksplisit, lalu pindah dengan USER. Halaman best practice Docker menyarankan UID dan GID eksplisit karena ID yang dibagikan otomatis bisa berubah antar rebuild. Dua baris addgroup dan adduser di bawah sama dengan yang dipakai Dockerfile situs ini, dengan group nodejs dan user nextjs di ID 1001.

Urutan penting. File yang disalin sebelum USER dimiliki root, jadi user baru mungkin tidak bisa menulis ke sana. Pakai COPY --chown untuk apa pun yang harus diubah aplikasi, dan taruh USER sedekat mungkin ke akhir supaya langkah build sebelumnya tetap punya hak yang dibutuhkan.

> Cek setiap kontrol dengan cara yang sama: ubah satu hal, build ulang, lalu jalankan perintah yang membuktikannya. Pengaturan yang tidak bisa kamu verifikasi hanyalah pengaturan yang kamu harap aktif.

## Langkah 3: Buat filesystem menjadi read-only

Flag --read-only memasang root filesystem container sebagai read-only, jadi penyerang tidak bisa menaruh skrip atau mengubah kodemu di tempat. Apa pun yang memang perlu menulis diberi tempat tulis sendiri: --tmpfs untuk file sementara, atau volume untuk data yang dipertahankan. Referensi Docker menunjukkan --tmpfs menerima opsi mount seperti rw,noexec,nosuid, yang juga melarang menjalankan program dari folder itu.

Ujinya dengan mencoba menulis. Filesystem read-only menolak di mana-mana kecuali folder yang kamu buka, dan aplikasimu tetap jalan karena ia hanya membaca kodenya sendiri.

## Langkah 4: Buang capability dan blokir privilege baru

Linux memecah kuasa root menjadi capability, dan Docker menjalankan container dengan set default yang sudah dibatasi. Halaman keamanan Docker menyebut praktik terbaiknya adalah membuang semua capability kecuali yang benar-benar dibutuhkan proses. Flag ini menerima nilai ALL, jadi kamu buang semuanya dan menambah kembali hanya yang diminta oleh error nyata. Server Node di port 3000 tidak butuh satu pun.

Flag kedua, no-new-privileges, menahan proses dari mendapatkan privilege lebih besar dari saat ia mulai, sehingga alat seperti su dan sudo tidak lagi berfungsi di dalam container. Pasangan ini tidak merugikan aplikasi web biasa, dan docker inspect membacakan pengaturannya kembali kepadamu.

> Jangan pernah memakai --privileged hanya untuk membungkam error. Dokumentasi Docker menyebut flag itu memberi semua capability ke container, yang membatalkan seluruh langkah ini. Cari satu capability yang disebut error dan tambahkan hanya itu dengan --cap-add.

## Langkah 5: Jauhkan secret dari image dan kunci versi base image

Image adalah tumpukan layer yang bisa dibaca siapa pun yang boleh menariknya. Secret yang ditulis dengan ENV di Dockerfile tetap ada di history image selamanya, walau baris berikutnya mencoba menghapusnya. Berikan secret saat container dimulai. Panduan lengkapnya ada di post tentang menyimpan secret Docker dengan aman, ditautkan di bagian akhir.

Bagian kedua adalah pinning. Halaman best practice Docker mencatat bahwa tag image bisa berubah, jadi node:22-alpine bisa menunjuk image lain bulan depan. Pin ke digest menjaga build tetap di image yang persis sama. Konsekuensi yang disebut halaman itu: kamu harus memperbarui digest sendiri dan melepas perbaikan keamanan otomatis, dan ia menyarankan Dependabot untuk menjaganya tetap baru.

## Langkah 6: Scan image untuk kerentanan yang sudah dikenal

Hardening container tidak memperbaiki paket rentan di dalam image. Scanner membandingkan isi image dengan database CVE yang sudah dikenal. Docker Scout dan Trivy sama-sama membaca image langsung dari Docker lokalmu, dan keduanya bisa menyaring berdasarkan severity supaya scan pertama tidak menenggelamkanmu dengan temuan berprioritas rendah.

Kedua alat juga bisa mengubah temuan menjadi exit code gagal, cara membuat pipeline CI menolak image yang buruk. Scout mengembalikan exit code 2 saat menemukan kerentanan. Untuk Trivy kamu menentukan sendiri exit code-nya dengan --exit-code. Perbaikan yang lazim untuk sebuah temuan adalah build ulang di atas base image yang lebih baru, lalu scan lagi.

## Langkah 7: Pindahkan pengaturan ke Compose

Baris docker run yang panjang tidak awet, jadi tulis kontrol yang sama ke file Compose. Setiap key di bawah ada di referensi services Compose: user, read_only, tmpfs, cap_drop, security_opt, mem_limit, dan pids_limit.

Bandingkan dengan file asli di balik situs ini. File Compose-nya sudah mengikat port ke loopback, membatasi memori di 512m dan satu CPU, mendefinisikan health check, dan Dockerfile-nya berjalan sebagai uid 1001. Ia belum mengatur read_only maupun cap_drop, dan tidak ada image scanner yang berjalan di repositori. Celah itu sengaja saya sebut: daftar hardening baru berguna kalau jujur soal apa yang belum dikerjakan.

## Error umum dan cara memperbaikinya

Kebanyakan masalah setelah hardening muncul saat satu kontrol bertemu satu asumsi. Cocokkan gejalamu dengan salah satu baris.

| Gejala | Penyebab | Perbaikan |
| --- | --- | --- |
| Aplikasi crash saat start dengan Read-only file system setelah menambah --read-only | Aplikasi menulis file sementara, cache, atau log ke folder yang sekarang terkunci | Cari path di pesan error, lalu buka hanya path itu dengan --tmpfs, atau volume kalau datanya harus bertahan setelah restart |
| permission denied saat user non-root menulis ke folder aplikasi | File disalin sebelum USER, sehingga dimiliki root | Pakai COPY --chown=nextjs:nodejs untuk file yang harus diubah aplikasi, atau tulis ke tmpfs atau volume |
| Volume yang di-mount tidak bisa ditulis setelah pindah ke user non-root | Folder host yang di-bind-mount ke container dimiliki root | Pakai named volume, yang kepemilikannya diisi Docker dari image, atau chown folder host ke UID container |
| operation not permitted setelah --cap-drop ALL | Aplikasi, atau skrip startup, butuh satu capability tertentu | Tambahkan kembali hanya capability itu dengan --cap-add. Jangan pakai --privileged |
| Scan menampilkan ratusan temuan dan kamu bingung mulai dari mana | Tidak ada filter severity, jadi semua CVE berprioritas rendah ikut tampil | Saring dengan --only-severity critical,high untuk Scout atau --severity HIGH,CRITICAL untuk Trivy, lalu build ulang di atas base image yang lebih baru |
| Container bisa diakses dari mesin lain padahal aplikasinya sudah di-harden | Port dipublikasikan sebagai 3000:3000, yang mendengarkan di semua interface. EXPOSE di Dockerfile hanya mendokumentasikan port dan tidak mempublikasikan apa pun | Publikasikan sebagai 127.0.0.1:3000:3000 dan biarkan reverse proxy di mesin yang sama yang menghadap internet |

## Checklist sebelum kamu mengirim container

docker exec menunjukkan uid non-root, bukan 0

Menulis ke folder aplikasi gagal dengan Read-only file system, sementara folder tmpfs tetap bisa

docker inspect menampilkan ALL di CapDrop dan no-new-privileges di SecurityOpt

docker history tidak menampilkan secret, dan baris FROM sudah dipin ke digest

Scan untuk temuan critical dan high sudah dijalankan, dan port terikat ke 127.0.0.1

## Belajar lebih dalam

Pelajaran gratis

 menggambar lapisan-lapisannya dan menunjukkan mana yang diatur situs ini. Untuk image yang lebih kecil untuk di-scan, baca

 dan

. Berikutnya di seri ini: pisahkan build dari runtime di

panduan multi-stage build

, dan jauhkan secret dari image di

panduan secret Docker

.

Aturan yang dibawa pulang: keraskan container satu kontrol demi satu kontrol, dan jangan simpan kontrol yang tidak bisa kamu buktikan dengan perintah. Non-root, read-only, tanpa capability, tanpa secret, dipin, dan di-scan tidak membuatmu kebal, tetapi masing-masing menghilangkan sesuatu yang kalau tidak akan didapat penyerang dengan gratis.

## Sumber

- Dokumentasi Docker: referensi docker container run
- Dokumentasi Docker: menjalankan container, privilege dan capability
- Dokumentasi Docker: referensi services Compose
- Dokumentasi Docker: best practice Dockerfile
- Dokumentasi Docker: referensi docker scout cves
- Dokumentasi Trivy: referensi CLI trivy image

## Source links

- https://docs.docker.com/reference/cli/docker/container/run/
- https://docs.docker.com/engine/containers/run/
- https://docs.docker.com/reference/compose-file/services/
- https://docs.docker.com/build/building/best-practices/
- https://docs.docker.com/reference/cli/docker/scout/cves/
- https://trivy.dev/docs/latest/guide/references/configuration/cli/trivy_image/

## FAQ

### Kenapa container Docker saya berjalan sebagai root secara default?

Docker tidak mengganti user untukmu. Kalau image tidak punya instruksi USER, proses berjalan sebagai root di dalam container. Buat user dengan ID eksplisit, tambahkan baris USER, lalu cek dengan docker exec NAMA_CONTAINER id.

### Apakah menjalankan container sebagai root benar-benar berbahaya?

Root di dalam container tidak sama dengan root di host, dan Docker membuang sebagian capability secara default. Tetap saja, kalau aplikasi diretas, root memberi penyerang jauh lebih banyak bahan daripada user biasa. Halaman keamanan Docker sendiri menyebut container lebih aman bila proses berjalan sebagai user tanpa hak istimewa.

### Apa fungsi docker run --read-only?

Flag ini memasang root filesystem container sebagai read-only, sehingga tidak ada yang bisa ditulis kecuali ke volume atau tmpfs yang sengaja kamu buka. Penyerang tidak bisa mengubah kodemu atau menaruh file di folder aplikasi. Kalau aplikasimu butuh folder sementara, tambahkan --tmpfs untuk path itu.

### Kenapa aplikasi saya error operation not permitted setelah cap-drop ALL?

Prosesnya butuh satu capability Linux yang sekarang sudah dibuang. Baca pesan error untuk tahu yang mana, lalu tambahkan kembali hanya itu dengan --cap-add, bukan mencabut pembatasan atau memakai --privileged. Kebanyakan aplikasi web di port di atas 1024 tidak butuh capability apa pun.

### Bagaimana cara scan image Docker untuk mencari kerentanan secara gratis?

Jalankan docker scout cves IMAGE dengan --only-severity critical,high, atau trivy image --severity HIGH,CRITICAL IMAGE. Keduanya bisa membaca image dari Docker lokalmu. Build ulang di atas base image yang lebih baru kalau ada temuan serius, lalu scan lagi.

---

- HTML version: https://www.matthewswong.com/id/blog/cara-mengamankan-container-docker/
- English: https://www.matthewswong.com/en/blog/cara-mengamankan-container-docker/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
