Command override memberi kita kuasa mengubah apa yang dijalankan container untuk satu kali jalan, tetapi kebutuhan yang lebih sering muncul justru lebih halus: aplikasi yang sama harus berperilaku berbeda di lingkungan yang berbeda. Budi ingin menguji myapp dalam mode production tanpa membangun ulang image, dan Siti perlu mengarahkan aplikasi yang sama ke basis data uji yang berganti-ganti dalam sepekan. Membangun satu image per lingkungan adalah jalan yang mahal — build ulang, tag baru, dan deretan image yang isinya hanya berbeda satu baris konfigurasi. Jawaban yang benar adalah menyuntikkan konfigurasi saat runtime lewat environment variable, dan docker run menyediakan tiga pintunya: flag -e yang boleh diulang, bentuk pass-through tanpa nilai, dan --env-file untuk daftar panjang.

Nilai Baru dengan Flag -e #

Flag -e menerima bentuk NAMA=nilai dan menyisipkan variabel tersebut ke environment container pada saat container diciptakan. Flag ini boleh — dan biasanya memang — diulang beberapa kali dalam satu perintah. Budi menjalankan myapp dengan dua variabel sekaligus:

bash
$ docker run -d --name myapp-prod -e NODE_ENV=production -e API_KEY=abc myapp:1.0

Output yang Dihasilkan:

text
b1e4f8a2c9d6073e5a8c1b4d6f2a9e3c7b5d8f1a3c6e9b2d4f7a1c5e8b3d6f0a

Nilai variabel paling mudah diverifikasi dari sudut pandang dalam container, memakai docker exec yang kita kenal dari Chapter Menjalankan Perintah dalam Container:

bash
$ docker exec myapp-prod printenv NODE_ENV

Output yang Dihasilkan:

text
production
bash
$ docker exec myapp-prod printenv API_KEY

Output yang Dihasilkan:

text
abc

Mari kita telaah tiga perintah di atas:

  1. -e NODE_ENV=production -e API_KEY=abc — dua flag terpisah, dua variabel berbeda; daemon menyisipkan keduanya ke environment container sebelum proses utama dinyalakan.
  2. printenv NAMA — mencetak nilai satu variabel dari dalam container; nilai production dan abc terbaca oleh proses tambahan hasil exec, bukti bahwa variabel benar-benar hidup di environment container.
  3. Environment berlaku untuk semua proses container — proses utama hasil CMD atau ENTRYPOINT maupun proses tambahan hasil docker exec melihat daftar variabel yang sama.

Daftar lengkap environment container juga bisa kita intip sekaligus:

bash
$ docker exec myapp-prod printenv

Output yang Dihasilkan:

text
HOSTNAME=b1e4f8a2c9d6
NODE_ENV=production
API_KEY=abc
HOME=/root
NODE_VERSION=22.20.0
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
...

Output di atas dipotong — beberapa baris bawaan image node lainnya tidak ditampilkan. Mari kita telaah asal-usul tiap barisnya:

  1. NODE_ENV dan API_KEY — dua suntikan flag -e milik Budi; keduanya tidak ada di dalam image myapp:1.0 sama sekali.
  2. HOSTNAME=b1e4f8a2c9d6 — diisi otomatis oleh daemon dengan ID pendek container; nilai ini berganti setiap kali container baru diciptakan.
  3. NODE_VERSION dan PATH — bawaan image node:22-alpine, ditulis lewat instruksi ENV pada Dockerfile-nya dan diwarisi setiap container yang berdiri di atasnya; materi ENV dan ARG di Dockerfile sudah kita bedah pada Chapter ENV dan ARG.
  4. HOME=/root — direktori rumah user root, identitas bawaan myapp:1.0 yang pembahasannya menyusul pada Chapter User dan Permissions.

Jejak Environment pada docker inspect #

Seluruh environment container tersimpan pada konfigurasinya dan terbaca lewat docker inspect — teknik template --format yang akrab sejak Chapter Image Inspect dan History:

bash
$ docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' myapp-prod

Output yang Dihasilkan:

text
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NODE_VERSION=22.20.0
...
NODE_ENV=production
API_KEY=abc

Baris-baris bawaan image di tengah dipotong agar ringkas; dua baris terakhir adalah suntikan runtime. Nilai yang tersimpan di .Config.Env inilah yang diwarisi setiap proses di dalam container — dan format penyimpanannya polos, tanpa enkripsi apa pun.

Pass-Through dengan -e Tanpa Nilai #

Bentuk kedua flag -e hanya menyebut nama variabel tanpa tanda sama dengan: -e NAMA. Daemon kemudian menyalin nilai variabel dari shell host yang memanggil docker run ke dalam container:

bash
$ export TOKEN_LATIHAN=tokendev123
$ docker run --rm -e TOKEN_LATIHAN myapp:1.0 printenv TOKEN_LATIHAN

Output yang Dihasilkan:

text
tokendev123

Mari kita telaah perilaku bentuk pass-through ini:

  1. -e TOKEN_LATIHAN tanpa = — daemon membaca nilai TOKEN_LATIHAN dari lingkungan shell yang mengetik perintah; container menerima variabel dengan nilai yang sama.
  2. Variabel yang tidak ada dilewati — bila TOKEN_LATIHAN tidak diset di host, variabel itu sama sekali tidak disisipkan ke container; tidak ada galat, tidak ada nilai kosong.
  3. Kegunaannya di skrip — skrip CI dapat menyalurkan token dari secrets manajer-nya tanpa pernah menuliskan nilai secara eksplisit di dalam berkas skrip; nilai mengalir dari lingkungan ke container.

Daftar Panjang dengan –env-file #

Ketika daftar variabel memanjang, mengetik deretan -e menjadi rawan salah ketik. Flag --env-file membacanya dari sebuah berkas teks. Budi membuat berkas .env di folder myapp — folder yang sama dengan Dockerfile-nya — dengan isi sebagai berikut:

bash
$ cat .env

Output yang Dihasilkan:

text
NODE_ENV=production
API_KEY=abc
# level log untuk lingkungan uji
LOG_LEVEL=info

Lalu container dijalankan dengan membaca daftar itu:

bash
$ docker run --rm --env-file .env myapp:1.0 printenv NODE_ENV LOG_LEVEL

Output yang Dihasilkan:

text
production
info

Mari kita telaah format berkas dan hasilnya:

  1. Satu KEY=VALUE per baris — setiap baris yang bukan komentar dan bukan baris kosong menjadi satu entri environment; printenv dengan dua argumen mencetak nilai keduanya, dan API_KEY tidak tampil karena memang tidak diminta.
  2. Baris berawalan # adalah komentar — baris keterangan level log diabaikan begitu juga baris kosong; format ini sengaja sederhana, bukan parser shell.
  3. Nilai dibaca mentah — tanda kutip yang ditulis menjadi bagian dari nilai, berbeda dengan kebiasaan beberapa tool lain yang mengupasnya; biasakan menulis nilai tanpa kutip.
  4. Berkas dibaca dari host, bukan dari image — --env-file menyasar berkas di direktori kerja shell yang menjalankan docker run; berkas .env tidak pernah ikut terbakar ke dalam image, dan bila kunci yang sama disebut oleh -e sekaligus --env-file, nilai dari -e yang menang.

Runtime Menimpa ENV Image #

Hubungan -e dengan ENV bawaan image mengikuti satu aturan pendek: nilai runtime selalu menang. Image node:22-alpine membawa NODE_VERSION dari Dockerfile-nya, dan Budi menimpanya sekadar untuk membuktikan urutannya:

bash
$ docker run --rm -e NODE_VERSION=ditimpa myapp:1.0 printenv NODE_VERSION

Output yang Dihasilkan:

text
ditimpa

Timpaan hanya berlaku untuk container itu — docker run myapp:1.0 berikutnya tanpa -e kembali melihat nilai bawaan image. Tabel kecil berikut merangkum hirarki sumber environment di dalam container:

SumberKapan disetBisa ditimpa runtime?
ENV pada Dockerfilesaat build; menempel permanen di imageya — -e menang untuk container itu
-e dan --env-filesaat docker runsumber tertinggi sebelum container start
HOSTNAMEotomatis oleh daemon saat createtidak — nilainya ditetapkan Docker

Aplikasi yang Membaca Environment #

Docker hanya menyediakan variabel; aplikasilah yang memutuskan apa yang dibaca. Node.js mengekspos seluruh environment melalui process.env, dan kita bisa membuktikannya tanpa memodifikasi server.js sama sekali:

bash
$ docker run --rm -e NODE_ENV=production myapp:1.0 node -e "console.log('NODE_ENV:', process.env.NODE_ENV)"

Output yang Dihasilkan:

text
NODE_ENV: production

Mari kita telaah posisi masing-masing pihak:

  1. node -e menjalankan potongan kode satu baris — pola command override dari Chapter Command Override yang sekaligus membuktikan variabel terbaca oleh proses Node di dalam container.
  2. NODE_ENV adalah konvensi ekosistem Node — framework seperti Express membacanya untuk memilih mode production atau development; server.js milik myapp belum membacanya, sehingga perilaku aplikasi memang belum berubah — variabel tersedia, tinggal kode yang memutuskan memakainya.
  3. Berbeda dengan image resmi — image seperti postgres justru membaca env-nya dari entrypoint-nya sendiri; mengubah variabel benar-benar mengubah apa yang dikerjakan container saat menyala.

Pola Variabel Image Resmi #

Setiap image resmi mendefinisikan nama variabelnya sendiri — konsisten di dalam satu image, tetapi tidak universal antar image. Tabel berikut merangkum nama yang paling sering kita jumpai:

ImageVariabel yang sering dipakaiEfeknya
postgresPOSTGRES_PASSWORD, POSTGRES_DB, POSTGRES_USERdibaca entrypoint saat inisialisasi pertama basis data
mysqlMYSQL_ROOT_PASSWORD, MYSQL_DATABASEkata sandi root dan basis data awal saat inisialisasi
wordpressWORDPRESS_DB_HOST, WORDPRESS_DB_USER, WORDPRESS_DB_PASSWORD, WORDPRESS_DB_NAMEdipetakan menjadi konstanta wp-config
aplikasi NodeNODE_ENVkonvensi ekosistem Node; dibaca aplikasi, bukan image
redistanpa variabel konfigurasi khususdikonfigurasi lewat argumen seperti redis-server --requirepass — kembali ke pola command override

Demo: postgres dengan Dua Variabel #

Kita tutup chapter dengan demo lengkap memakai image postgres yang sudah berulang kali muncul dalam course ini. Dua variabel dibaca entrypoint-nya saat direktori data masih kosong:

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

Output yang Dihasilkan:

text
c4d8e1f3a7b2950d6e8c1a4b7d3f5e2c9a6b8d1f3e5c7a9b2d4f6e8c0a3b5d7f

Bila image postgres:18-alpine belum ada di lokal, daemon menariknya otomatis — kebiasaan yang melekat sejak Chapter Menjalankan Container Pertama. Setelah beberapa detik inisialisasi, kedua variabel bisa diverifikasi dari dalam:

bash
$ docker exec db-katalog printenv POSTGRES_DB

Output yang Dihasilkan:

text
katalog
bash
$ docker exec db-katalog pg_isready

Output yang Dihasilkan:

text
/var/run/postgresql:5432 - accepting connections

Mari kita telaah rangkaian demo penutup ini:

  1. POSTGRES_DB=katalog benar-benar dipakai — entrypoint postgres membaca variabel itu saat menyiapkan direktori data yang masih kosong, lalu membuat basis data bernama katalog; variabel bukan sekadar catatan, melainkan input kerja entrypoint.
  2. POSTGRES_PASSWORD=rahasia123 — kata sandi superuser untuk inisialisasi pertama; tanpa variabel ini container postgres menolak jalan, persis peringatan yang kita saksikan pada Chapter Use Case Docker.
  3. pg_isready — alat bawaan image yang melaporkan server sudah menerima koneksi; accepting connections menandakan inisialisasi selesai dan variabel-variabel tadi sudah diproses habis.

Environment variable mengatur bagaimana aplikasi berperilaku, tetapi masih menyisakan satu pertanyaan identitas: sebagai user siapa seluruh proses itu bekerja? Jawaban bawaan Docker mengejutkan banyak orang — hampir semuanya berjalan sebagai root, user nomor 0 dengan hak administratif penuh. Chapter User dan Permissions membedah identitas proses container, flag --user untuk menimpanya, dan konsekuensi izin berkas yang mengikuti angka UID itu.

Referensi #