Flag --privileged pada chapter sebelumnya melepaskan empat pembatas sekaligus; sekarang kita bergerak ke kutub yang berlawanan — pembatas yang ditegakkan sampai ke butir terakhir. Sepanjang course ini, setiap tulisan yang dilakukan container — berkas baru, backdoor yang dijatuhkan penyerang, cache yang menumpuk — selalu mendarat di satu tempat: writable layer di puncak tumpukan image, mekanisme yang kita bedah tuntas pada Chapter Writable Container Layer. Chapter ini membahas flag yang membuang lapisan itu seluruhnya: --read-only membekukan akar filesystem container sehingga tidak ada satu path pun yang bisa ditulis — oleh user biasa, oleh root, sekalipun oleh proses dengan seluruh capability. Aplikasi nyata tetap butuh menggores di beberapa titik, jadi kita belajar memilih lubang tulis yang diberikan secara sengaja lewat tmpfs, lalu menutup chapter dengan kombinasi emas yang merangkai seluruh pelajaran section ini menjadi satu perintah.
Membuang Writable Layer Seluruhnya #
Tanpa flag tambahan, container lahir dengan writable layer yang siap menampung semua tulisan. Dengan --read-only, Docker memasang seluruh rootfs — image beserta tumpukan layernya — sebagai read-only mount: writable layer tidak disediakan, dan setiap upaya menulis ditolak kernel dengan galat EROFS (Error: Read-Only File System). Bukti paling ringkasnya cuma satu baris:
$ docker run --rm --read-only alpine:3.20 touch /ujiOutput yang Dihasilkan:
touch: /uji: Read-only file systemPenolakan itu berlaku tanpa memandang identitas — proses root pada container --read-only menemui pesan yang sama. Sebelum flag ini dipasang pada aplikasi sungguhan, ada satu kejujuran yang harus kita terima lebih dulu: aplikasi nyata menulis lebih banyak daripada yang kita kira.
Aplikasi Nyata Butuh Menulis #
Chapter Mount Read-Only pernah memperingatkan bahwa kombinasi penguncian ini menuntut pemetaan kebutuhan tulis lebih dulu — dan pemetaan itu bukan teori. Nginx tanpa area tulis akan menolak menyala dengan galat nginx: [emerg] mkdir() /var/cache/nginx/client_temp failed (30: Read-only file system); aplikasi Node menulis pid file; runtime bahkan yang paling sopan pun kerap menyentuh /tmp.
| Kebutuhan aplikasi | Solusi pada pola read-only |
|---|---|
| Berkas sementara | --tmpfs /tmp — hidup di RAM, lenyap saat container berhenti |
| Cache internal | --tmpfs pada path cache, lengkap dengan kuota size |
| Berkas pid dan runtime | --tmpfs /run — pola baku service Unix |
| Log aplikasi | arahkan ke stdout agar terbaca docker logs — pola Chapter Docker Logs |
| Data yang harus bertahan | volume dipasang rw — mount tetap dapat ditulis walau rootfs beku |
| Konfigurasi | dibakal ke image, atau bind mount :ro seperti Chapter Mount Read-Only |
Dua baris terakhir tabel adalah pengecualian yang sering terlewat: --read-only membekukan akar filesystem, tetapi volume dan bind mount yang dipasang setelahnya membawa kebijakan izinnya sendiri — volume rw tetap bisa ditulis, dan penguncian per-mount tetap diatur suffix :ro sebagaimana dibedah pada Chapter Mount Read-Only.
Resep Lengkap: nginx dengan Dua Lubang tmpfs #
Sekarang kita rakit nginx sungguhan di atas filesystem beku. Dua lubang tulis dibuka dengan sengaja: /var/cache/nginx untuk direktori kerja internalnya, dan /run untuk berkas pid:
$ docker run -d --name web-kekal --read-only \
--tmpfs /var/cache/nginx:rw,noexec,nosuid,size=16m \
--tmpfs /run \
-p 8083:80 \
nginx:1.29Output yang Dihasilkan:
c4e8a1f7b3d59c2e6a0f4b8d1c5e7a3f9b0d2c4e6a8f1b3d5c7e9a0f2b4d6c8eMari kita telaah opsi yang ditempelkan pada tmpfs pertama:
rw— lubang ini boleh ditulis; inilah alasan lubang dibuka sama sekali.noexec— berkas yang ditulis ke sini tidak boleh dieksekusi sebagai program; payload yang dijatuhkan penyerang bisa mendarat tetapi tidak bisa dijalankan dari lokasi ini.nosuid— bit setuid pada berkas apa pun di lubang ini diabaikan kernel; trik eskalasi klasik lewat berkas setuid hasil unduhan langsung mati.size=16m— kuota RAM untuk lubang ini; aplikasi yang liar menulis cache dihentikan oleh kuota, bukan oleh RAM host yang habis./rundipasang polos — Docker memasang tmpfs tanpa opsi dengan pengaturan bawaan yang setara —rw,noexec,nosuid— dan batas ukuran 64 MB; format opsi yang sama dengan/var/cache/nginx, cukup dengan ukuran berbeda.
Tiga verifikasi menutup resep: akar beku, lubang hidup, dan layanan melayani.
$ docker exec web-kekal touch /berkas-ujiOutput yang Dihasilkan:
touch: cannot touch '/berkas-uji': Read-only file system$ docker exec web-kekal touch /var/cache/nginx/uji
$ docker exec web-kekal ls /var/cache/nginxOutput yang Dihasilkan:
client_temp fastcgi_temp proxy_temp scgi_temp uji uwsgi_temp$ curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8083Output yang Dihasilkan:
200Mari kita telaah ketiga verifikasi tersebut:
- Tulisan ke akar filesystem ditolak —
touch /berkas-ujigagal meskidocker execbawaan berjalan sebagai root; pembekuan berlaku untuk semua identitas. - Lubang tmpfs hidup dan terpakai — berkas
ujitercipta di samping lima direktori kerja yang dibuat nginx sendiri saat menyala; tanpa lubang ini,mkdir client_temppada proses startup-lah yang akan gagal lebih dulu. - Layanan tetap sehat ujung ke ujung — curl menerima
200; penguncian filesystem tidak mengurangi satu pun fungsi yang memang dibutuhkan pengguna.
Status penguncian terbaca dari docker inspect, sebelum container uji dibersihkan dengan docker rm -f web-kekal:
$ docker inspect --format '{{.HostConfig.ReadonlyRootfs}}' web-kekalOutput yang Dihasilkan:
trueRead-Only di Compose #
Pola ini menjadi konfigurasi tetap stack lewat compose.yaml — kunci read_only dan daftar tmpfs:
services:
web:
image: nginx:1.29
ports:
- "8083:80"
read_only: true
tmpfs:
- /run
- /var/cache/nginxBentuk daftar di atas tidak menyediakan opsi tambahan; kuota dan pengaturan lain memakai sintaks panjang volumes bertipe tmpfs:
services:
web:
image: nginx:1.29
read_only: true
volumes:
- type: tmpfs
target: /run
- type: tmpfs
target: /var/cache/nginx
tmpfs:
size: 16mMari kita telaah pemetaan kedua bentuk tersebut:
read_only: true— padanan--read-onlypadadocker run; seluruh rootfs service ini dibekukan.- Daftar
tmpfs— padanan paling ringkas dari--tmpfs; setiap baris satu lubang tulis. - Bentuk panjang dengan
tmpfs.size— sintaksvolumesbertipe tmpfs membuka opsi seperti kuota ukuran; perilakunoexecdannosuidtetap menjadi pengaturan bawaan kedua bentuk.
Menyalakan stack uji dan membersihkannya kembali simetris: docker compose up -d lalu docker compose down — container yang dibangkitkan Compose membawa penguncian yang sama persis dengan versi CLI-nya.
Keuntungan Imutabilitas #
Apa yang sebenarnya kita beli dengan seluruh kerumitan di atas? Tiga hal. Pertama, malware kehilangan tempat menulis: sebagian besar implant butuh menjatuhkan berkas — payload, backdoor, alat eskalasi — untuk bertahan, dan kernel kini menolak setiap penulisan itu di seluruh akar filesystem; lubang tmpfs yang tersisa hidup di RAM dan hilang saat container berhenti. Kedua, restart selalu bersih: state container kembali persis ke isinya saat menyala, karena memang tidak ada state yang bisa tersimpan — kejanggalan konfigurasi yang “muncul sendiri” di production jadi mustahil secara struktural, bukan sekadar dilarang secara kebijakan. Ketiga, kepastian isi: dikombinasikan dengan pematokan image berdasarkan digest — sha256 yang kita kenal dari Chapter Image Digest — kita memegang jaminan berlapis bahwa yang berjalan hari ini adalah bytenya byte yang sama dengan yang diaudit; kombinasi read-only plus digest pinning adalah fondasi pola immutable infrastructure yang kita rangkai penuh pada Chapter Reproducible Build.
Kejujuran tetap perlu disampaikan: read-only bukan antivirus. Proses yang sudah berjalan tetap bisa berbuat banyak di dalam memori dan jaringan, dan lubang tmpfs tetap bisa dipakai menulis sementara. Yang dibeli flag ini adalah penghapusan satu kelas daya rusak yang paling sering dipakai — persistensi di disk — dengan harga satu flag dan pemetaan beberapa lubang tulis.
Kombinasi Emas #
Seluruh pelajaran section ini — identitas, capability, filesystem — bisa dirangkai menjadi satu baris uji yang layak dihafal:
$ docker run --rm --read-only --user 1000:1000 --cap-drop=ALL \
alpine:3.20 sh -c 'id && touch /uji'Output yang Dihasilkan:
uid=1000 gid=1000
touch: /uji: Read-only file systemUntuk service sungguhan, satu kebutuhan tulis hampir pasti muncul lebih dulu dari yang lain: /tmp. Runtime Python menyimpan berkas sementara di sana, demikian pula JVM dan toolchain Go; pola --tmpfs /tmp karenanya menjadi default yang nyaris otomatis pada setiap container read-only, persis mekanisme tmpfs yang kita bedah pada Chapter tmpfs Mount.
Dari lima lapisan isolasi pada diagram Chapter Prinsip Keamanan Container, tiga lapisan terluar kini kita tegakkan secara sadar: identitas diturunkan, privilege dicabut sampai sisa yang terbukti dibutuhkan, dan filesystem dibekukan dengan lubang-lubang yang dipilih. Dua lapisan terdalam — seccomp dan LSM — masih bekerja dengan pengaturan bawaannya: profil default yang baik, tetapi belum kita kenal dan belum kita kendalikan. Padahal dari semua lapisan itu, keduanyalah yang tetap bekerja pada detik terburuk: ketika penyerang sudah memegang shell di dalam container. Chapter Seccomp, AppArmor, dan SELinux membuka lapisan tersebut — cara kernel menyaring syscall yang boleh lewat, profil AppArmor yang menempel pada container, dan bagaimana keduanya dikustomisasi untuk aplikasi kita.
Referensi #
- docker container run — CLI Reference
— referensi flag
--read-onlydan--tmpfsbeserta opsinya. - tmpfs mounts
— mekanisme dan opsi tmpfs:
rw,noexec,nosuid,size. - Compose file reference
— kunci
read_only,tmpfs, danvolumesbertipe tmpfs. - Docker security — posisi filesystem read-only dalam kerangka isolasi berlapis.