Service seed pada chapter sebelumnya memakai satu baris yang selama ini kita terima begitu saja: PGPASSWORD: ${POSTGRES_PASSWORD} — Compose mengganti teks ${POSTGRES_PASSWORD} dengan nilai yang dicari dari luar berkas sebelum konfigurasi dipakai untuk menciptakan container apa pun. Chapter Environment dan env File sudah memperkenalkan mekanisme ini sebatas kebutuhannya saat itu; sekarang saatnya membedahnya sebagai sistem utuh. Mengapa penting? Karena satu compose.yaml yang sama harus bisa melayani mesin Budi, mesin Siti, dan server produksi tanpa disunting satu per satu — port yang berbeda per orang, versi image yang berbeda per lingkungan, dan nilai yang wajib ada tanpa mana stack tidak boleh menyala. Interpolation adalah parametrisasi teks compose.yaml, dan chapter ini memetakan seluruh perangkatnya: dua sumber nilai beserta urutannya, lima pola penulisan, aturan penulisan dolar literal, sampai variabel COMPOSE_* yang mengendalikan CLI Compose itu sendiri.
Dua Sumber Nilai dan Urutannya #
Interpolation membaca nilai dari dua tempat, dan urutannya tegap: shell environment menang atas .env. Ketika Compose memproses compose.yaml, setiap ${VAR} dicari lebih dulu di environment proses Compose — variabel yang kita ekspor atau tempelkan di depan perintah — dan baru bila tidak ada, nilainya diambil dari berkas .env di root project yang dimuat otomatis sebagai sumber interpolasi. Perilaku penimpaan ini mudah dibuktikan dengan perintah render yang kita kenal dari Chapter Struktur compose.yaml:
$ POSTGRES_PASSWORD=uji-shell docker compose configOutput yang Dihasilkan (dipotong):
services:
web:
environment:
DATABASE_URL: postgres://katalog:uji-shell@db:5432/katalog
...Berkas .env di mesin Budi tetap memuat POSTGRES_PASSWORD=rahasia123, tetapi keluaran render menunjukkan uji-shell — nilai dari shell menimpa nilai berkas. Perintah yang sama tanpa awalan variabel akan kembali mencetak rahasia123. Urutan ini konsisten dengan perilaku COMPOSE_PROFILES pada chapter sebelumnya, dan konsekuensinya praktis: nilai di shell bersifat sementara untuk satu pemanggilan, sedangkan .env adalah nilai simpanan yang melekat pada folder project.
Lima Pola Interpolasi #
Tanda ${...} punya lima variasi penulisan yang semuanya diturunkan dari gaya parameter shell POSIX. Perbedaannya ada pada perlakuan terhadap dua keadaan — variabel terisi string kosong, dan variabel yang tidak ada sama sekali:
| Pola | Nilai terisi | Nilai kosong (VAR=) | Variabel tidak ada |
|---|---|---|---|
${VAR} | nilai | string kosong | string kosong |
${VAR:-default} | nilai | default | default |
${VAR-default} | nilai | string kosong | default |
${VAR:?pesan} | nilai | galat — perintah gagal | galat — perintah gagal |
${VAR?pesan} | nilai | string kosong | galat — perintah gagal |
Mari kita telaah tiga pola yang paling sering dipakai dari tabel di atas:
${VAR:-default}— tanda hubung dan titik dua berarti “bila kosong atau tidak ada, pakai default”. Pola ini ramah untuk nilai yang boleh berbeda per mesin:image: postgres:${TAG_DB:-18-alpine}memakai tag 18-alpine di mesin siapa pun, sementara mesin uji migrasi cukup menyetelTAG_DBke major sebelumnya tanpa menyunting berkas.${VAR-default}— tanpa titik dua, default hanya berlaku bila variabelnya tidak ada; string kosong yang sengaja disetel tetap dihormati sebagai nilai. Perbedaan satu karakter ini penting saat nilai kosong memang bermakna — menonaktifkan sebuah fitur, misalnya.${VAR:?pesan}— tanda tanya dan titik dua berarti “bila kosong atau tidak ada, gagalkan perintah dan cetak pesan ini”. Inilah senjata fail fast yang kita bahas pada bagian berikutnya.
Pola kelima, ${VAR?pesan}, adalah versi longgar dari :? — ia hanya menolak variabel yang tidak ada, tetapi meloloskan string kosong. Pemakaian :? jauh lebih umum karena string kosong hampir selalu merupakan tanda kesalahan setelan, bukan pilihan yang disengaja.
Nilai Wajib dan Kegagalan yang Jelas #
Stack katalog punya satu nilai yang tidak boleh tidak ada: password database. Selama ini bila .env lupa disiapkan, Compose diam-diam melanjutkan dengan string kosong — container tetap diciptakan, dan kegagalannya baru muncul menit-menit kemudian sebagai galat koneksi yang menjalar jauh dari akar masalahnya. Budi memperketat jalur itu dengan mengubah baris DATABASE_URL pada service web:
web:
environment:
DATABASE_URL: postgres://katalog:${POSTGRES_PASSWORD:?setel di .env}@db:5432/katalogSekarang hilangnya .env tidak lagi diam-diam. Budi mengujinya dengan menyimpan sementara berkas itu:
$ mv .env .env.simpan
$ docker compose configOutput yang Dihasilkan:
required variable POSTGRES_PASSWORD is missing a value: setel di .env$ mv .env.simpan .envMari kita telaah kegagalan yang barusan kita saksikan:
- Pesan galat memuat dua bagian yang kita tulis sendiri — nama variabel
POSTGRES_PASSWORDdan pesansetel di .env; format bawaannya berbunyirequired variable <nama> is missing a value: <pesan>, sehingga pesan kustom kita menjadi petunjuk berjalan bagi siapa pun yang menyalin repositori tanpa menyiapkan nilai. - Kegagalan terjadi sebelum satu container pun tersentuh — interpolasi dikerjakan saat berkas dibaca, jadi
docker compose up,docker compose config, dandocker compose buildsemuanya gugur di titik yang sama tanpa meninggalkan setengah stack di daemon. - Exit code perintah menjadi non-zero — perilaku yang membuat pola
:?cocok sebagai gerbang pemeriksaan di skrip CI, persis poladocker compose config --quietdari Chapter Struktur compose.yaml. - Inilah pengalaman yang dialami Siti — di mesin barunya yang baru menarik repositori,
docker compose up -dkini gagal dalam satu detik dengan pesan yang menyebut apa yang harus disalin, bukan denganECONNREFUSEDyang muncul lima menit kemudian.
Dolar Literal dengan $$ #
Interpolation membaca setiap ${...} pada teks compose.yaml — termasuk teks yang sebenarnya ditujukan untuk program lain di dalam container. Kasus klasiknya adalah variabel milik nginx sendiri. Bayangkan Budi suatu saat menempelkan konfigurasi proxy langsung ke dalam compose.yaml lewat kunci content pada deklarasi configs:
configs:
nginx-conf:
content: |
server {
listen 80;
location / {
proxy_pass http://web:3000;
proxy_set_header Host $$host;
}
}Mari kita telaah perlakuan dua tanda dolar di atas:
$$hostadalah tanda dolar yang dikandung dua kali — Compose membaca$$sebagai satu karakter dolar literal dan menyerahkannya apa adanya; setelah interpolasi selesai, nginx menerimaproxy_set_header Host $host;dan mengisi variabel$hostmiliknya sendiri saat melayani permintaan.- Tanpa penggandaan, baris itu rusak —
$hosttanpa pelindung akan dibaca Compose sebagai variabel bernamahost; bila variabel itu tidak ada, hasil render adalah string kosong dan konfigurasi proxy kehilangan headernya tanpa satu galat pun. - Berkas terpisah tidak memerlukan pelindung ini — katalog kita memasang
./nginx.conflewat kuncifile:pada Chapter *Config dan Secret, dan isi berkas yang dirujukfile:tidak pernah diinterpolasi; di dalamnginx.confyang terpisah itu,$host` ditulis apa adanya. - Pola yang sama berlaku untuk environment container — nilai seperti
TOKEN_PREFIX: produk$$123akan tiba di dalam container sebagaiproduk$123. Sebaliknya, nilai di dalam.envdan di dalam berkasenv_filetidak pernah diinterpolasi, sehingga dolar di sana selalu literal tanpa perlu digandakan.
Kita sebenarnya sudah pernah memakai penggandaan ini diam-diam: probe pg_isready -U $${POSTGRES_USER} pada Chapter Healthcheck dan Service Readiness menjaga variabel $POSTGRES_USER agar tetap milik shell container, bukan milik Compose.
Interpolasi di Banyak Tempat #
Interpolation bekerja pada nilai YAML apa pun, bukan hanya environment. Satu berkas dengan demikian bisa diparametrisasi menyeluruh — blok berikut adalah ilustrasi pola pada sebuah service, bukan suntingan baru pada katalog:
web:
build: .
image: katalog-web:${VERSI_APP:-latest}
ports:
- "${PORT_WEB:-8080}:3000"
volumes:
- ./log-${LINGKUNGAN:-dev}:/app/logMari kita telaah empat baris parametrisasi di atas:
- Tag image —
${VERSI_APP:-latest}membuat tag hasil penamaan build mengikuti versi yang sedang dikerjakan; mesin yang tidak menyetel nilainya jatuh kelatest. - Pemetaan port —
${PORT_WEB:-8080}:3000tetap ditulis sebagai satu string terkutip utuh, menjaga aturan tanda kutip dari Chapter *Port dan Network; Siti yang membutuhkan pintu lain cukup menyetelPORT_WEBdi.env` miliknya tanpa menyunting berkas bersama. - Path volume — sumber bind
./log-${LINGKUNGAN:-dev}menghasilkan./log-devpada kebanyakan mesin dan./log-ujipada mesin yang menyetelLINGKUNGAN=uji; folder log tiap lingkungan pun terpisah sejak baris pertama. - Gabungan dengan
builddanimage— pasangan keduanya tetap berlaku sesuai aturan Chapter Image, Build, dan Pull: image dibangun dari Dockerfile lalu hasilnya dinamai mengikuti nilaiimageyang sudah terinterpolasi.
Variabel Khusus Compose CLI #
Selain mengisi ${...} pada berkas, environment juga mengendalikan perilaku CLI Compose itu sendiri lewat keluarga variabel COMPOSE_*. Tiga anggotanya yang paling sering dipakai:
| Variabel | Peran | Padanan flag |
|---|---|---|
COMPOSE_PROFILES | profil yang aktif | --profile (boleh berulang) |
COMPOSE_PROJECT_NAME | nama project pengganti nama folder | -p |
COMPOSE_FILE | daftar berkas yang dimuat | -f (boleh berulang) |
COMPOSE_PROFILES sudah kita praktikkan pada Chapter *Profiles. Dua sisinya bisa dibuktikan dengan perintah render. Nama project yang selama ini diambil dari blok name` bisa dipaksa berubah tanpa menyunting berkas:
$ COMPOSE_PROJECT_NAME=katalog-uji docker compose configOutput yang Dihasilkan (dipotong):
name: katalog-uji
services:
db:
...Sedangkan COMPOSE_FILE melepaskan Compose dari folder tempat perintah dijalankan — Budi bisa menjalankan perintah dari direktori mana pun selama berkasnya ditunjuk:
$ cd ~
$ COMPOSE_FILE=/home/budi/katalog/compose.yaml docker compose configOutput yang Dihasilkan (dipotong):
name: katalog
services:
db:
...Kedua variabel ini akan kembali dimainkan peran penting pada Chapter Compose Override dan Environment — di sana satu stack yang sama perlu berjalan sebagai dua project berdampingan, dan COMPOSE_FILE menjadi cara memuat kombinasi berkas yang tidak otomatis dimuat.
Memverifikasi Hasil Render #
Seluruh mekanisme pada chapter ini bermuara pada satu kebiasaan: sebelum mempercayai hasil interpolasi, lihat dulu hasilnya. docker compose config mencetak konfigurasi final setelah seluruh ${...} diselesaikan, seluruh $$ dilipat menjadi satu dolar, dan seluruh bentuk tulis dinormalisasi — keluaran itulah yang sesungguhnya dibaca daemon.
Perintah render juga menjadi gerbang pemeriksaan yang murah untuk tiga hal sekaligus: memastikan pola ${...} tertulis benar (keluaran yang masih memuat ${ berarti ada yang salah tik), memastikan nilai cadangan terpakai sesuai keadaan mesin, dan memastikan pesan :? benar-benar menembak pada keadaan tanpa nilai. Kebiasaan render-dulu-jalankan-belakangan menghemat puluhan siklus up yang gagal di tengah jalan.
Berkas katalog kini mampu melayani mesin yang berbeda-beda tanpa disunting per mesin: nilai wajib dijaga :?, nilai opsional ditenangkan :-, dolar literal dilindungi $$, dan identitas project diatur COMPOSE_*. Namun ada satu ritual pengembangan yang tetap membosankan berapa pun canggihnya parametrisasi: setiap suntingan server.js masih menuntut docker compose up -d --build yang mengulang build dari layer yang berubah — delapan hingga sembilan detik per iterasi, dikali ratusan iterasi per hari. Compose punya jawaban yang mengubah siklus itu menjadi hitungan detik pecahan: develop.watch, mekanisme sinkronisasi dan rebuild otomatis yang kita bedah pada Chapter Compose Watch.
Referensi #
- Variable interpolation
— lima pola
${VAR}, nilai default, dan pesan galat yang bisa disetel. - Environment variables in Compose
— urutan sumber nilai interpolasi: shell sebelum
.env. - Explore the .env file
— daftar variabel
COMPOSE_*yang dibaca CLI Compose. - docker compose config
— verifikasi hasil render beserta flag
--no-interpolate.