Stack katalog kini sehat secara terukur: data bertahan di volume, urutan start terjaga gerbang service_healthy, dan dua probe menjunjung kejujuran status. Dua kebutuhan terakhir masih memakai jalan yang kurang rapi. Berkas konfigurasi utuh — kelak dibutuhkan begitu Budi menaruh reverse proxy nginx di depan web sebagai satu-satunya pintu masuk — selama ini hanya bisa dibind manual seperti pola Chapter Bind Mount, bukan dideklarasikan sebagai bagian project. Sementara itu password database masih berpindah sebagai string polos di environment: terbaca printenv, terbaca docker inspect, dan tercatat di .env — Siti sampai mengingatkan bahwa siapa pun yang membuka mesin development-nya bisa membaca rahasia123 dalam satu perintah. Compose menyediakan dua mekanisme khusus untuk keduanya: configs untuk menyuntikkan berkas konfigurasi, dan secrets untuk membagikan data sensitif sebagai berkas — keduanya dibedah tuntas di chapter ini.
configs: Berkas Konfigurasi sebagai Objek Project #
Mekanisme configs memperlakukan satu berkas di host sebagai objek milik project — dideklarasikan di level teratas, lalu dipasang ke service yang membutuhkannya sebagai berkas read-only di dalam container. Kita mulai dari konfigurasinya sendiri: satu nginx.conf minimal yang meneruskan seluruh permintaan ke service web:
server {
listen 80;
location / {
proxy_pass http://web:3000;
}
}Mari kita telaah tiga baris inti berkas di atas:
server { listen 80; ... }— satu blok server yang menerima koneksi di port 80 milik container proxy; port inilah yang akan kita terbitkan ke host.location / { ... }— blok yang cocok untuk seluruh jalur permintaan, dari/sampai/healthz; tidak ada pengecualian.proxy_pass http://web:3000;— jantung konfigurasi ini: nginx meneruskan permintaan ke alamatweb:3000; nama servicewebdi-resolve oleh DNS networkkatalog_default, sehingga proxy tidak pernah perlu tahu alamat IP container yang bisa berganti-ganti.
Service proxy dan deklarasi teratasnya berbentuk sebagai berikut:
proxy:
image: nginx:1.29-alpine
ports:
- "8090:80"
configs:
- source: nginx-conf
target: /etc/nginx/conf.d/default.conf
depends_on:
- web
configs:
nginx-conf:
file: ./nginx.confMari kita telaah bagian-bagian yang bekerja sama di atas:
configslevel teratas — mendeklarasikan objek bernamanginx-confyang isinya diambil dari./nginx.conf; path dihitung relatif terhadap lokasi compose.yaml, sama seperti aturan path pada bind mount.source: nginx-confdi service — merujuk objek teratas tersebut; service boleh memasang config yang sama berkali-kali dengan target berbeda, dan service lain boleh ikut memakainya tanpa mendeklarasikan ulang isinya.target: /etc/nginx/conf.d/default.conf— path LENGKAP berkas di dalam container, bukan direktorinya; image nginx memuat seluruh*.confdari direktori/etc/nginx/conf.dke dalam blokhttp, dan berkasdefault.confbawaan image berisi server halaman-statis yang kita ganti sepenuhnya dengan proxy ini.- Bila
targettidak ditulis — berkas dipasang di root container dengan nama objeknya (/nginx-conf), path yang tidak dibaca nginx sama sekali; karena itu target lengkap bukan gaya penulisan, melainkan syarat kerja. ports: "8090:80"— port host 8090 dipilih agar tidak bertabrakan dengan 8080 yang sudah diterbitkanweb; dua service tidak boleh merebut port host yang sama.depends_onbentuk daftar —proxymenyala setelah containerwebberjalan; gerbangservice_healthytidak diperlukan di sini karena nginx tetap hidup meski upstream-nya sesaat belum siap — ia menjawab502 Bad Gatewaysebentar lalu mulus begituwebangkattelepon.
Waktunya menyaksikan proxy bekerja:
$ docker compose up -d proxyOutput yang Dihasilkan:
[+] Running 4/4
✔ Network katalog_default Running
✔ Container katalog-db-1 Running
✔ Container katalog-web-1 Running
✔ Container katalog-proxy-1 Started$ curl localhost:8090Output yang Dihasilkan:
<h1>Katalog Produk</h1>
<ul>
<li>Kopi Arabika Gayo — Rp85.000</li>
...Isi yang sama dengan curl localhost:8080 kini tiba lewat jalur baru: permintaan masuk ke host 8090, diterima nginx di port 80 container proxy, diteruskan lewat proxy_pass ke web:3000, lalu jawabannya menempuh jalan pulang yang sama. Validasi konfigurasi dari dalam container memastikan berkas yang terbaca nginx memang berkas kita:
$ docker compose exec proxy nginx -tOutput yang Dihasilkan:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: the configuration file /etc/nginx/nginx.conf test is successful$ docker compose exec proxy cat /etc/nginx/conf.d/default.confOutput yang Dihasilkan:
server {
listen 80;
location / {
proxy_pass http://web:3000;
}
}Berkas yang kita simpan di host tampil utuh di dalam container — tanpa COPY saat build, tanpa bind mount yang path-nya harus dijaga. Config dipasang read-only persis seperti bind mount read_only: true, sehingga container tidak dapat mengubah konfigurasi yang diberikan kepadanya. Peran reverse proxy yang lebih dewasa — TLS, beberapa upstream, kompresi — menjadi petualangan tersendiri di Chapter Reverse Proxy pada Section Multi-Container Application.
secrets: Rahasia sebagai Berkas #
Config menjawab kebutuhan berkas; kini giliran data yang tidak boleh terpampang. Password di environment variable memang praktis, tetapi nilai polosnya terbaca printenv, docker inspect, dan docker compose config — kelemahan yang sudah kita tandai sejak Chapter Environment Variable. Mekanisme secrets mengubah bentuk pembawaannya: nilai hidup di dalam sebuah berkas, berkas itu dipasang read-only di path bawaan /run/secrets/<nama>, dan environment variable yang tersisa hanyalah alamat berkasnya — bukan isinya.
Budi memindahkan password database ke berkas db_password.txt:
$ echo "rahasia123" > db_password.txtDeklarasi teratas dan perubahan pada service db:
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: katalog
POSTGRES_DB: katalog
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db-data:/var/lib/postgresql/data
secrets:
db_password:
file: ./db_password.txtMari kita telaah bagian-bagian di atas:
secretslevel teratas — objek bernamadb_passwordyang isinya dibaca dari./db_password.txtpada saat container diciptakan; strukturnya seiman denganconfigs, dan perbedaan keduanya kita rangkum di tabel di akhir chapter.secrets: - db_passworddi service — bentuk pendek tanpatarget; secret dipasang di path bawaan/run/secrets/db_password, konvensi path yang sama di mana pun container berjalan.POSTGRES_PASSWORD_FILE: /run/secrets/db_password— pola*_FILEyang didukung entry point image resmi postgres untuk kredensialnya; entry point membaca isi berkas itu sebagai penggantiPOSTGRES_PASSWORD, sehingga string polos tidak pernah lagi menempel pada environment container.- Volume
db-datayang sudah terisi — password hanya dibaca saatinitdbpertama pada volume kosong, perilaku yang sudah kita catat pada Chapter Volume; perubahan ini bermakna penuh pada pemasangan baru di mesin lain, meskipun cluster yang ada tidak tersentuh.
$ docker compose up -dOutput yang Dihasilkan (dipotong):
[+] Running 4/4
✔ Network katalog_default Running
✔ Container katalog-db-1 Recreated
✔ Container katalog-db-1 Healthy
✔ Container katalog-web-1 Recreated
✔ Container katalog-web-1 Started
...Bukti bahwa secret benar-benar tiba di dalam container:
$ docker compose exec db cat /run/secrets/db_passwordOutput yang Dihasilkan:
rahasia123Sedangkan upaya menulis balik ditolak keras:
$ docker compose exec db sh -c 'echo uji >> /run/secrets/db_password'Output yang Dihasilkan:
sh: can't create /run/secrets/db_password: Read-only file systemconfigs versus secrets #
Kedua mekanisme itu terlihat kembar — sama-sama berkas dari host, sama-sama dipasang read-only — tetapi tujuannya berbeda arah. Tabel berikut memisahkan keduanya:
| Aspek | configs | secrets |
|---|---|---|
| Isi yang layak | Konfigurasi non-sensitif — nginx.conf, template | Data sensitif — password, token, kunci TLS |
| Path bawaan di container | /<nama> bila target tidak ditulis | /run/secrets/<nama> |
| Izin | Dipasang read-only | Read-only; mode bawaan 0444 di Swarm, dapat dipersempit lewat mode |
| Nasib di produksi | Aman dibagikan sebagai berkas biasa | Di luar Swarm tetap berkas polos di host — produksi sungguhan memakai secret manager |
Pembeda terpenting bukan mekanisme, melainkan makna: config boleh dibaca siapa pun tanpa kerugian, secret dirancang untuk dibaca hanya oleh proses yang berhak. Di Swarm, perbedaan itu diterjemahkan menjadi perlakuan distribusi yang berbeda — secret dienkripsi dalam perjalanan dan hanya dikirim ke node yang menjalankan service yang memintanya, sedangkan config didistribusikan sebagai berkas biasa.
Dengan configs dan secrets, dua jalan pintas terakhir pun tertutup: konfigurasi utuh kini berstatus objek project, dan kredensial berpindah bentuk dari string yang terpampang menjadi berkas yang terpasang read-only. Berkas katalog kita berakhir dengan empat deklarasi teratas — services, volumes, configs, secrets — masing-masing menjaga kelas barangnya sendiri. Namun masih ada satu pola yang belum terwakili: bagaimana bila kita ingin service yang hanya menyala saat dibutuhkan — misalnya utilitas browsing database untuk mengintip isi tabel produk ketika insiden terjadi, yang tidak boleh ikut menyala pada setiap docker compose up produksi? Compose menyimpan jawabannya di kunci profiles, pembuka Chapter Profiles.
Referensi #
- Configs — Compose File Reference
— deklarasi teratas
configs, kuncifile,source, dantarget. - Secrets — Compose File Reference
— deklarasi teratas
secrets, path bawaan/run/secrets/<nama>, dan kuncimode. - PostgreSQL
— dokumentasi image resmi termasuk dukungan pola
POSTGRES_PASSWORD_FILE. - nginx Documentation
— directive
proxy_passdan pemuatan berkas dari/etc/nginx/conf.d.