Data katalog kini berumur panjang di dalam volume katalog_db-data, dan docker compose down tidak lagi membawa kabar buruk. Namun stack kita masih menyimpan satu kekosongan yang justru makin terasa: service web belum tahu apa-apa tentang tetangga db-nya — alamat host, port, nama database, sampai kredensial user semuanya belum pernah diberitahukan. Kebutuhan semacam ini tidak diselesaikan dengan mount, melainkan dengan environment variable yang disuntikkan Compose ke container pada saat diciptakan — mekanisme yang kita kenal dari flag -e pada Chapter Environment Variable, kini dalam bentuk deklaratif. Chapter ini membedah tiga pintunya sekaligus: kunci environment dengan dua bentuk tulisnya, env_file untuk daftar panjang dalam berkas terpisah, dan berkas .env yang — rawan membingungkan — ternyata bisa memainkan dua peran yang sama sekali berbeda.
Dua Bentuk Kunci environment #
Kunci environment di dalam service menerima dua tata tulis. Bentuk daftar menuliskan setiap variabel sebagai string KUNCI=nilai, sedangkan bentuk peta menuliskannya sebagai pasangan kunci-nilai YAML:
db:
environment:
- POSTGRES_USER=katalog
- POSTGRES_PASSWORD=rahasia123
- POSTGRES_DB=katalog db:
environment:
POSTGRES_USER: katalog
POSTGRES_PASSWORD: rahasia123
POSTGRES_DB: katalog| Aspek | Bentuk daftar | Bentuk peta |
|---|---|---|
| Tata tulis | - KUNCI=nilai, satu baris satu variabel | KUNCI: nilai, pasangan YAML |
| Nilai yang tampak seperti angka | Selamat — seluruh baris adalah string | Perlu dikutip: PGPORT: "5432" |
| Urutan variabel | Terjaga persis seperti ditulis | Tidak berarti — peta tidak berurutan |
| Perilaku saat merge berkas override | Daftar lama diganti total | Digabung per kunci, nilai terbaru menang |
Menyambungkan web ke db lewat DATABASE_URL #
Banyak aplikasi Node.js membaca alamat koneksi databasenya dari satu variabel DATABASE_URL. Aplikasi katalog kita termasuk yang demikian — kodenya membentuk pool koneksi dari variabel itu:
const pool = new Pool({ connectionString: process.env.DATABASE_URL });Karena itu, menyambungkan web ke db cukup satu baris pada service web:
web:
build: .
ports:
- "8080:3000"
environment:
DATABASE_URL: postgres://katalog:rahasia123@db:5432/katalogMari kita telaah isi URL di atas segmen demi segmen:
postgres://— skema koneksi; library pg memakainya untuk memilih protokol dan bentuk parameter.katalog:rahasia123— user dan password, dipisah titik dua, sama dengan nilaiPOSTGRES_USERdanPOSTGRES_PASSWORDyang membangun cluster sejak awal.db— nama host; di dalam networkkatalog_default, nama servicedbdi-resolve menjadi alamat container database — mekanisme DNS yang kita bedah pada Chapter Port dan Network.5432— port internal PostgreSQL; komunikasi antar service berjalan di port container, bukan port host 8080 yang hanya milikweb./katalog— nama database yang dibuka setelah koneksi sah.
Terapkan perubahan, lalu validasi nilainya langsung dari dalam container:
$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Running
✔ Container katalog-db-1 Running
✔ Container katalog-web-1 Recreated
✔ Container katalog-web-1 Started$ docker compose exec web printenv DATABASE_URLOutput yang Dihasilkan:
postgres://katalog:rahasia123@db:5432/katalogPerhatikan bahwa hanya web yang dibuat ulang — service db tidak tersentuh karena konfigurasinya tidak berubah. Bukti akhirnya datang dari aplikasi itu sendiri, yang kini berhasil membaca tabel produk:
$ curl localhost:8080Output yang Dihasilkan:
<h1>Katalog Produk</h1>
<ul>
<li>Kopi Arabika Gayo — Rp85.000</li>
<li>Teh Melati — Rp45.000</li>
</ul>env_file: Daftar Environment dalam Berkas Terpisah #
Bayangkan daftar environment web membesar: mode logging, batas halaman, kunci API pembayaran, alamat layanan email. Menempel semuanya di compose.yaml membuat berkas konfigurasi sesak oleh nilai-nilai yang sifatnya per-lingkungan. Compose menyediakan pintu kedua berupa env_file — berkas teks berisi baris KUNCI=nilai yang dimuat ke dalam environment container:
POSTGRES_PASSWORD=rahasia123
LOG_LEVEL=infoBudi menyimpan berkas itu sebagai .env di root project, lalu mendaftarkannya pada service web:
web:
build: .
ports:
- "8080:3000"
env_file:
- .env
environment:
DATABASE_URL: postgres://katalog:rahasia123@db:5432/katalogCompose membuat ulang container web dengan pola output yang sudah kita kenal (Recreated lalu Started), dan variabel dari berkas itu pun hidup di dalam container:
$ docker compose exec web printenv LOG_LEVELOutput yang Dihasilkan:
infoMari kita telaah perilaku env_file di atas:
- Setiap baris
KUNCI=nilaimenjadi environment variable container — formatnya sama persis dengan--env-filepadadocker runyang kita pakai di Chapter *Environment Variable; baris berawalan#` diabaikan sebagai komentar. - Seluruh isi berkas ikut disuntikkan —
POSTGRES_PASSWORDikut masuk kewebpadahal service itu tidak membutuhkannya; bila tiap service hanya boleh menerima bagiannya, pisahkan berkasnya per service (web.env,worker.env) dan daftarkan masing-masing. - Nilai
environmenttetap menang untuk kunci yang sama —DATABASE_URLtidak muncul di.env, tetapi bila muncul, nilai di blokenvironmentyang akan dipakai container.
Dua Peran Berkas .env #
Di titik inilah kebingungan klasik menunggu siapa pun yang memakai Compose. Berkas bernama tepat .env di root project memperoleh perlakuan istimewa dari CLI Compose: dimuat otomatis sebagai sumber interpolasi — setiap ${VARIABEL} yang ditulis di compose.yaml digantikan nilainya dari berkas itu, sebelum konfigurasi dipakai untuk membuat container apa pun. Ini peran yang sama sekali berbeda dari env_file di atas: interpolasi mengerjakan teks compose.yaml, bukan environment container.
Budi memanfaatkan peran kedua itu untuk menghapus password yang tertulis polos di dua tempat:
services:
web:
build: .
ports:
- "8080:3000"
env_file:
- .env
environment:
DATABASE_URL: postgres://katalog:${POSTGRES_PASSWORD}@db:5432/katalog
db:
image: postgres:18-alpine
environment:
POSTGRES_USER: katalog
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: katalog
volumes:
- db-data:/var/lib/postgresql/data
volumes:
db-data:$ docker compose up -dOutput yang Dihasilkan:
[+] Running 3/3
✔ Network katalog_default Running
✔ Container katalog-db-1 Running
✔ Container katalog-web-1 RunningTidak ada container yang dibuat ulang — nilai hasil interpolasi sama persis dengan nilai yang sebelumnya tertulis polos, dan Compose menilai perubahan dari konfigurasi yang sudah diselesaikan, bukan dari teks mentah berkasnya. Verifikasi dari sisi container memastikan nilai tetap utuh:
$ docker compose exec db printenv POSTGRES_PASSWORDOutput yang Dihasilkan:
rahasia123Tiga pintu environment yang kita miliki kini bisa dirangkum dalam satu tabel:
| Aspek | environment | env_file | .env untuk interpolasi |
|---|---|---|---|
| Nilai mengalir ke | Environment container | Environment container | Teks compose.yaml itu sendiri |
| Kapan dibaca | Saat container diciptakan | Saat container diciptakan | Setiap kali perintah compose membaca berkas |
| Perlu didaftarkan di service | Ya — blok environment | Ya — daftar env_file | Tidak — nama .env di root project cukup |
| Bisa menimpa | Menimpa env_file dan ENV image | Menimpa ENV image | Nilai dari shell operator menimpa .env |
Berkas .env milik katalog saat ini memang memainkan dua peran sekaligus: dibaca env_file untuk container web, dan dibaca interpolasi untuk ${POSTGRES_PASSWORD}. Keduanya sah, asalkan kita sadar sedang memakai dua mekanisme berbeda — satu mengisi environment container, satu mengisi teks konfigurasi.
Prioritas Sumber Environment #
Untuk satu kunci yang sama, dari mana nilai container diambil? Urutannya tegap:
| Urutan | Sumber | Keterangan |
|---|---|---|
| 1 — menang | environment pada compose.yaml | Termasuk hasil merge berkas override yang dibelakangkan |
| 2 | env_file yang didaftarkan service | Nilai berkas menimpa ENV bawaan image |
| 3 — kalah | ENV pada Dockerfile image | Nilai dasar sejak build |
Ketentuan override berlapis juga berlaku pada penggabungan berkas konfigurasi: perintah docker compose -f compose.yaml -f compose.prod.yaml menggabungkan dua berkas, dan untuk blok environment bentuk peta, kunci yang ditulis ulang di berkas belakangan menang atas berkas depan — inilah lapisan prioritas paling atas yang kita sebut singkat tadi dan bedah penuh pada Chapter Compose Override dan Environment. Di sisi interpolasi, nilai yang sudah terpasang di shell operator menimpa nilai .env, karena Compose membaca shell lebih dulu — aturan lengkapnya menjadi materi Chapter Variable Interpolation.
Nilai Kosong versus Variabel Tak Diset #
Dua keadaan yang kelihatannya sama padahal berbeda nasib:
web:
environment:
TOKEN_VERIFIKASI: ""Mari kita telaah dua keadaan itu:
TOKEN_VERIFIKASI: ""— container menerima variabel yang ada tetapi bernilai string kosong;printenv TOKEN_VERIFIKASIberhasil keluar dengan baris kosong, dan di Node.jsprocess.env.TOKEN_VERIFIKASI === ""bernilaitrue.- Variabel yang tidak ditulis sama sekali — variabelnya tidak ada di environment;
process.env.LAINNYA === undefined, dan library validasi konfigurasi umumnya menolak dengan pesan seperti “required variable is missing”. - Konsekuensinya pada validasi startup — aplikasi yang hanya memeriksa “variabel ada” akan meloloskan string kosong tanpa sengaja; pemeriksaan yang benar menguji nilai terisi, bukan sekadar keberadaan kuncinya.
Melihat Hasil Resolusi lewat docker compose config #
Perintah docker compose config — yang kita kenal sebagai alat validasi sintaks pada Chapter Struktur compose.yaml — juga menjadi cermin hasil akhir seluruh mekanisme environment ini:
$ docker compose configOutput yang Dihasilkan (dipotong):
name: katalog
services:
db:
environment:
POSTGRES_DB: katalog
POSTGRES_PASSWORD: rahasia123
POSTGRES_USER: katalog
image: postgres:18-alpine
...
web:
build:
context: .
environment:
DATABASE_URL: postgres://katalog:rahasia123@db:5432/katalog
env_file:
- .env
ports:
- mode: ingress
target: 3000
published: "8080"
...
volumes:
db-data:
name: katalog_db-dataMari kita telaah apa yang diperlihatkan resolusi ini:
- Interpolasi sudah selesai —
${POSTGRES_PASSWORD}tampil sebagairahasia123; tidak ada jejak${...}yang tersisa pada keluarannya. environmenttercetak terurun — Compose mengurutkan kunci secara alfabetis pada keluaran; urutan penulisan di berkas sumber memang tidak bermakna bagi hasil akhir.env_filetetap tercantum apa adanya — isi berkasnya tidak digabungkan keenvironmentpada keluaran; keduanya tetap dua sumber yang berbeda hingga container diciptakan.- Volume tampil dengan nama penuhnya —
db-datadiresolusi menjadikatalog_db-data, pengingat bahwa prefix project berlaku pada seluruh objek. - Keluaran ini memuat rahasia —
rahasia123terbaca polos; jangan pernah menempelkan keluarandocker compose configke issue tracker atau pesan grup.
$ cat .gitignoreOutput yang Dihasilkan:
node_modules
.env$ cat .env.exampleOutput yang Dihasilkan:
POSTGRES_PASSWORD=ganti-dengan-password-anda
LOG_LEVEL=infoSetiap orang yang menyalin repositori cukup menyalin .env.example menjadi .env lalu mengisi nilainya sendiri. Sintaks interpolasi yang lebih jauh — nilai bawaan ${VAR:-default} sampai pesan galat ${VAR:?wajib diisi} — kita bedah tuntas pada Chapter Variable Interpolation.
Environment kini lengkap: web tahu alamat db, kredensial hidup di satu berkas, dan seluruh nilai bisa diaudit lewat docker compose config. Namun ada satu momen rawan yang belum kita sentuh: detik-detik pertama setelah docker compose up, ketika web menyala lebih cepat daripada db yang masih sibuk menjalankan initdb. Di mesin Budi kebetulan selalu mulus; di mesin baru yang volumenya masih segal, web justru mati dengan galat koneksi sebelum sempat melayani satu permintaan pun. Cara mendeklarasikan urutan start — dan batas dari padanya — adalah materi Chapter depends_on dan Startup Order.
Referensi #
- Environment files
— aturan berkas
.env, format barisKUNCI=nilai, dan pemakaiannya lewatenv_file. - Explore the .env file
— perilaku
.envdi root project sebagai sumber interpolasi otomatis. - Compose File Reference — Services
— definisi kunci
environmentdanenv_filebeserta prioritasnya. - docker compose config — CLI Reference — validasi dan tampilan konfigurasi hasil resolusi.