Chapter Config dan Secret ditutup dengan satu janji yang sengaja digantung: Compose secrets di luar Swarm tidak mengenkripsi apa pun, dan keputusan arsitektur yang benar untuk produksi akan dibahas pada chapter ini. Sekarang saatnya menunaikan janji itu secara utuh. Password database, token API, kunci privat — sepanjang course ini semuanya berpindah tangan sebagai teks polos rahasia123 di environment variable, dan itu memang cukup untuk latihan; tetapi setiap tingkat kematangan infrastruktur menuntut perlakuan yang berbeda terhadap barang berbahaya ini. Chapter ini menyusun enam tingkat penyimpanan rahasia sebagai tangga — dari hardcode yang harus tidak pernah dilakukan sampai secret manager produksi — lalu menutupnya dengan dua keterampilan yang sering terlupakan: rotasi terjadwal dan pemburuan rahasia yang lolos ke dalam image.

Tangga Enam Tingkat Penyimpanan Rahasia #

TingkatContoh bentuknyaNilai keamanan
1 — Hardcode di DockerfileENV POSTGRES_PASSWORD=rahasia123tidak ada: tertanam permanen di layer image
2 — Environment variable runtimedocker run -e POSTGRES_PASSWORD=...rendah: terbaca docker inspect
3 — Berkas .env + interpolasi${POSTGRES_PASSWORD} di compose.yamlsedang: nilai keluar dari repository, tetap teks polos di disk
4 — Compose secretsPOSTGRES_PASSWORD_FILE=/run/secrets/db_passwordbaik untuk development: berkas, per-service, read-only
5 — Secret saat builddocker build --secret id=token_npm,env=TOKEN_NPMbaik untuk CI: hangus setelah build
6 — Secret managerVault, SOPS, sidecar penyuntikproduksi: terpusat, terenkripsi, diaudit, bisa dirotasi

Enam baris tabel itu bukan pilihan yang setara — tabelnya adalah tangga yang dinaiki sesuai kematangan lingkungan, dan tiga tingkat pertama punya cacat yang perlu kita buktikan supaya alasan menaikinya bukan dogma.

Tingkat 1 — Hardcode di Dockerfile #

Tingkat paling bawah sekaligus paling berbahaya: menuliskan rahasia langsung di Dockerfile, baik lewat ENV maupun ARG. Kita sudah menyaksikan nasibnya pada Chapter Secret dan SSH Mount: docker history menampilkan setiap instruksi build secara terbuka termasuk nilai ARG dan ENV — token npm rahasia123 terpampang sebagai baris riwayat tersendiri. Layer image bersifat permanen: meski baris berikutnya menghapus berkasnya, layer yang memuat nilai rahasia tetap ada dan bisa diekstrak siapa pun yang memegang image — bahkan setelah image didorong ke registry.

Tingkat 2 — Environment Variable Saat Runtime #

Naik satu anak tangga: nilai tidak lagi tertanam di image, melainkan disuntikkan saat container diciptakan lewat -e. Masalahnya, nilai itu menempel pada konfigurasi container — dan konfigurasi container terbaca siapa pun yang memiliki akses Docker. Kita buktikan dengan container db yang menyala dengan password polos:

bash
$ docker run -d --name db \
    -e POSTGRES_USER=katalog -e POSTGRES_PASSWORD=rahasia123 -e POSTGRES_DB=katalog \
    postgres:18-alpine

Output yang Dihasilkan:

text
3f9c1e7a2b84d5c06e8f1a3b7d9c2e4f5a6b8d0c2e4f6a8b1d3c5e7f9a0b2d4c
bash
$ docker inspect db --format '{{range .Config.Env}}{{println .}}{{end}}'

Output yang Dihasilkan:

text
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PGDATA=/var/lib/postgresql/data
POSTGRES_USER=katalog
POSTGRES_PASSWORD=rahasia123
POSTGRES_DB=katalog

Mari kita telaah kebocoran di dalam output tersebut:

  1. Format template mengiterasi .Config.Env — range menyusuri daftar environment container dan println mencetak satu entri per baris; tanpa satu pun enkripsi, karena memang tidak ada yang bisa dienkripsi di sini.
  2. POSTGRES_PASSWORD=rahasia123 terpampang polos — siapa pun yang bisa menjalankan docker inspect membaca password dalam sekejap; dan mengingat keanggotaan grup docker setara root host pada Chapter Mengatur Akses Docker, “siapa pun” berarti semua orang yang layak kita curigai.
  3. Environment diwariskan ke seluruh proses turunan — skrip diagnostik, tool pihak ketiga, sampai crash dump ikut membawa nilai itu; environment variable adalah medium yang paling mudah bocor secara tidak sengaja.

Container uji ini kita bersihkan dengan docker rm -f db — penelitiannya selesai, tidak ada alasan membiarkannya menyala.

Tingkat 3 — Berkas .env dan Interpolasi Compose #

Tangga berikutnya memindahkan nilai keluar dari perintah dan berkas project: nilai hidup di berkas .env, dan compose.yaml hanya memuat interpolasi ${POSTGRES_PASSWORD} — mekanisme yang sudah kita bedah tuntas pada Chapter Environment dan env File. Nilai tidak pernah tertulis di repository, dan berbagi konfigurasi antar anggota tim menjadi soal berbagi satu berkas kecil. Cacatnya tetap sama: isi .env masih teks polos di disk, dan justru karena itu berkas ini mesti dijaga ketat.

Tingkat 4 — Compose Secrets #

Mekanisme secrets Compose mengubah pembawa rahasia dari string environment menjadi berkas read-only di /run/secrets/<nama> — pola yang sudah kita bangun pada Chapter Config dan Secret. Bentuk panjang deklarasinya membuka tiga pengencangan tambahan:

yaml
services:
  db:
    image: postgres:18-alpine
    environment:
      POSTGRES_USER: katalog
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
      POSTGRES_DB: katalog
    secrets:
      - source: db_password
        target: db_password
        mode: 0400

secrets:
  db_password:
    file: ./db_password.txt

Mari kita telaah tiga pengencangan tersebut:

  1. POSTGRES_PASSWORD_FILE — pola *_FILE pada image resmi postgres: entry point membaca isi berkas sebagai password, sehingga string polos tidak pernah menempel pada environment container dan lolos dari docker inspect seperti demo tingkat dua.
  2. mode: 0400 — bit izin pemasangan: hanya pemilik berkas yang boleh membaca; tanpa kunci ini mode bawaan lebih longgar dan seluruh proses di dalam container bisa membacanya.
  3. Hanya service yang mendeklarasikan — web yang tidak memuat blok secrets tidak akan pernah melihat /run/secrets/db_password; rahasia berhenti menyebar ke service yang tidak membutuhkannya, tidak seperti .env yang cenderung dibaca semua service.

Tingkat 5 — Secret yang Hangus Setelah Build #

Rahasia bertipe lain muncul saat build: token npm untuk registry privat, kunci SSH untuk repositori privat. Solusinya adalah --mount=type=secret yang sudah kita praktikkan pada Chapter Secret dan SSH Mount: nilai dikirim dari mesin build ke instruksi RUN tertentu sebagai berkas sementara, tidak pernah meninggalkan jejak di layer maupun docker history, dan hangus begitu instruksi selesai. Pola docker build --secret id=token_npm,env=TOKEN_NPM inilah standar untuk CI: token hidup sebagai environment variable rahasia di runner, bukan di image hasilnya.

Tingkat 6 — Secret Manager untuk Produksi #

Tingkat teratas menyerahkan penyimpanan sepenuhnya ke sistem yang memang dibangun untuk itu: Vault menyimpan rahasia terpusat dengan enkripsi, kebijakan akses, dan audit log; SOPS mengenkripsi berkas rahasia langsung di repository sehingga aman di-commit; sidecar atau init container menyuntikkan nilai ke aplikasi tepat sebelum menyala. Pola yang sama menyelesaikan dua masalah yang tingkat di bawahnya tidak sanggup: siapa yang mengakses rahasia menjadi tercatat, dan rotasi menjadi operasi satu titik, bukan suntingan berkas di lusinan mesin.

docker secret di Swarm #

Docker sendiri membawa mekanisme tingkat enam lahirnya: pada cluster Swarm, objek docker secret disimpan terenkripsi di log Raft para manajer, didistribusikan hanya ke node yang menjalankan service penerimanya, dan dipasang sebagai berkas di /run/secrets/<nama>:

bash
$ docker secret create db_password db_password.txt

Output yang Dihasilkan:

text
2e48e91c2d8169d71aff774c97636d428aa7313d0077fe9a832db39f37cc04ee

Output tersebut adalah ID secret yang baru diciptakan — satu-satunya jejak yang terlihat di terminal, karena isinya memang tidak pernah ditampilkan lagi. Service kemudian menerima secret itu lewat flag --secret pada docker service create. Seluruh upacara orchestration-nya — Raft, node manajer, service — adalah wilayah Chapter Docker Swarm di Section Orchestration dan Ekosistem.

Rotasi Rahasia #

Rahasia yang tidak pernah berganti adalah kredensial yang bocor diam-diam bertahun-tahun. Rotasi yang sehat punya jadwal — misalnya setiap 90 hari, dan seketika setelah indikasi kebocoran — dengan prosedur tiga langkah. Pertama, nilai baru disiapkan di sumbernya: berkas db_password.txt ditulis ulang, atau versi baru dibuat di secret manager. Kedua, container diciptakan ulang agar secret terpasang ulang:

bash
$ docker compose up -d db

Output yang Dihasilkan (dipotong):

text
[+] Running 1/1
 ✔ Container katalog-db-1  Recreated
...

Ketiga, database diajak berganti kata sandinya sendiri, karena password yang tersimpan di cluster tidak ikut terganti hanya karena berkas secret berganti:

bash
$ docker compose exec db psql -U katalog -c "ALTER ROLE katalog WITH PASSWORD 'rahasia456';"

Output yang Dihasilkan:

text
ALTER ROLE

Padanan untuk cluster yang memakai superuser bawaan adalah ALTER ROLE postgres WITH PASSWORD '...'; — mekanismanya identik, hanya nama perannya yang berbeda.

Blast radius rotasi perlu dipikirkan sebelum tombol ditekan, bukan sesudahnya. Ketika ALTER ROLE dijalankan, aplikasi yang masih memegang password lama langsung gagal autentikasi — dan bila yang dirotasi adalah token yang dipakai lima service, lima service jatuh bersamaan. Karena itu rotasi produksi dilakukan bertahap: ganti satu service dalam satu waktu, dan pastikan alarm kegagalan autentikasi menyala sebelum pelanggan melakukannya. docker compose up -d db pada contoh kita hanya menciptakan ulang satu container, dan justru itu intinya: desain yang baik membuat rotasi menyinggung permukaan sekecil mungkin.

Memburu Rahasia yang Lolos #

Tangga di atas mengatur rahasia yang kita ketahui; musuh yang sebenarnya adalah yang lolos tanpa sepengetahuan kita — image lama kolega, layer warisan, token di log build tahun lalu. Kebiasaan auditnya dua lapis: docker history image:tag dibaca teliti untuk nilai yang mencurigakan pada baris ENV dan ARG, dan pemindai seperti Trivy atau Docker Scout — bahasan penuh chapter berikutnya — memeriksa image secara otomatis.

Rekomendasi per Lingkungan #

LingkunganTingkat yang layakCatatan
Latihan pribadi2-3nyaman, tanpa data nyata
Development tim3-4.env dijaga gitignore, secret per-service
Build CI5token hidup sebagai environment rahasia runner
Staging4, menyiapkan 6perlakuan mendekati produksi
Produksi6 + rotasi terjadwalaudit log dan blast radius menjadi wajib

Rahasia kini tertata di seluruh daur hidupnya: tidak tertanam di image, tidak terpampang di environment, dan berganti secara terjadwal. Namun isi image itu sendiri belum pernah kita periksa. Image katalog-web:2.0 yang kita bangun dari node:22-alpine membawa ratusan paket, dan sebagian dari mereka mungkin membawa cacat yang sudah dikenal dunia dengan nomor dan tingkat keparahannya. Chapter Image Scanning memperkenalkan CVE, SBOM, dan cara Docker Scout membaca isi image sebelum image itu menyentuh server produksi.

Referensi #