Environment Variable #
Environment variable adalah cara paling umum untuk mengkonfigurasi aplikasi di environment container. Docker Compose menyediakan beberapa mekanisme untuk mendefinisikan dan meng-inject environment variable ke service: hardcoded di YAML, env_file, .env file, dan variable substitution.
Artikel ini membahas setiap mekanisme, kapan harus dipakai, dan best practice management konfigurasi untuk development, staging, dan production.
Cara Mendefinisikan Environment Variable #
Ada empat cara utama, masing-masing dengan use case yang berbeda.
1. Hardcoded di docker-compose.yml #
Cara paling langsung: tulis env var di field environment service.
services:
api:
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://user:pass@db:5432/myapp
- DEBUG=false
Atau dengan map syntax:
services:
api:
environment:
NODE_ENV: production
DATABASE_URL: postgres://user:pass@db:5432/myapp
DEBUG: "false"
Cocok untuk: konfigurasi non-sensitif yang tidak berubah antar environment (mis. nama service, port internal, default value).
Tidak cocok untuk: secret (password, API key), konfigurasi yang berbeda per environment, data yang sering berubah.
Jangan hardcode secret di YAML — filedocker-compose.ymlbiasanya di-commit ke Git. Secret yang ada di sini akan terekspos di repository history. Selalu gunakanenv_fileatausecretsuntuk data sensitif.
2. env_file #
Field env_file membaca file dan inject variabelnya ke service.
services:
api:
env_file:
- .env
- .env.production
File .env:
DATABASE_URL=postgres://user:pass@db:5432/myapp
REDIS_URL=redis://cache:6379
API_KEY=sk-1234567890
Setiap baris KEY=VALUE di file menjadi env var di container.
Opsi tambahan:
services:
api:
env_file:
- path: ./config/.env
required: false # default true; jika false dan file tidak ada, error diabaikan
format: json # format alternatif
required: false berguna untuk environment variable opsional. format: json memungkinkan JSON untuk struktur yang lebih kompleks.
Cocok untuk: konfigurasi per environment (dev/staging/prod), secret yang tidak ingin di Git, konfigurasi yang dibagi antar beberapa service.
File .env biasanya di-.gitignore untuk mencegah commit secret.
3. File .env Otomatis #
Docker Compose otomatis membaca file .env di direktori yang sama dengan docker-compose.yml. Variabel di file ini bisa dipakai untuk substitusi di YAML.
# docker-compose.yml
services:
api:
image: myapp:${VERSION:-latest}
ports:
- "${PORT:-8080}:8080"
File .env:
VERSION=1.2.3
PORT=9000
Saat docker compose up, Compose membaca .env, lalu substitusi ${VERSION} dengan 1.2.3 dan ${PORT} dengan 9000. ${VAR:-default} memberikan default jika var tidak di-set.
Penting: file .env otomatis hanya dipakai untuk substitusi di YAML, bukan untuk inject ke container. Untuk inject ke container, tetap gunakan env_file atau environment.
Urutan loading .env:
- Environment variable di shell (override yang di file)
- File
.envdi current directory - File
.envdi parent directory - Variable
envdi shell
Cocok untuk: konfigurasi yang berbeda per developer (path lokal, port), variable untuk substitusi YAML.
4. Variable Substitution di Environment #
Kamu juga bisa referensikan environment variable shell di environment service.
services:
api:
environment:
- DATABASE_URL=${DATABASE_URL}
- API_KEY=${API_KEY}
Compose membaca DATABASE_URL dan API_KEY dari shell saat ini dan inject ke container. Ini berguna untuk passing secret dari environment CI/CD atau local shell tanpa menyimpannya di file.
Cocok untuk: secret dari environment CI/CD, konfigurasi yang sudah di-set di shell user, dynamic value per-deployment.
Passing Build Args #
Environment variable juga bisa di-pass ke Docker build via args.
services:
api:
build:
context: ./api
args:
- NODE_ENV=production
- VERSION=1.2.3
Di Dockerfile:
ARG NODE_ENV=development
ARG VERSION=latest
ENV NODE_ENV=$NODE_ENV
ENV VERSION=$VERSION
RUN npm ci --only=production
build args hanya tersedia saat build, tidak di runtime. ENV di Dockerfile menjadikannya runtime environment variable.
Secrets vs Environment Variables #
Docker Compose punya field secrets: yang lebih aman dari environment: untuk data sensitif.
services:
api:
image: myapp
secrets:
- db_password
- api_key
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
file: ./secrets/api_key.txt
File db_password.txt berisi password (plain text), di-mount ke container di /run/secrets/db_password. Aplikasi membaca file tersebut.
Di Compose V2 (non-Swarm): secrets disimpan di filesystem host dan di-mount ke container. Tidak terenkripsi, tapi tidak terekspos di docker inspect seperti environment variable.
Di Docker Swarm: secrets dienkripsi dan didistribusikan ke semua node. Hanya container yang punya permission yang bisa membaca.
Kapan pakai secrets vs env:
- Secrets: password, API key, sertifikat TLS, data yang tidak boleh terlihat di
docker inspect. - Env vars: konfigurasi non-sensitif, flag (production/development), nama service, port.
File .env vs env_file #
Penting untuk membedakan keduanya.
| Aspek | .env |
env_file |
|---|---|---|
| Dipakai untuk | Substitusi di YAML | Inject ke container |
| Format | Sederhana, KEY=VALUE |
Lebih fleksibel, bisa JSON |
| Loading | Otomatis | Eksplisit di service |
| Multiple file | Satu per project | Banyak per service |
| Cocok untuk | Variable versi, port host | Konfigurasi runtime service |
Contoh kombinasi:
# docker-compose.yml
services:
api:
image: myapp:${VERSION} # dari .env (substitusi)
ports:
- "${HOST_PORT}:8080" # dari .env (substitusi)
env_file:
- .env.runtime # inject ke container
# .env (untuk substitusi)
# VERSION=1.2.3
# HOST_PORT=8080
# .env.runtime (untuk inject)
# DATABASE_URL=postgres://...
# REDIS_URL=redis://...
Best Practice #
Pisahkan Konfigurasi per Environment #
# docker-compose.yml (base)
services:
api:
build: .
environment:
- NODE_ENV=production
# docker-compose.dev.yml (override untuk development)
services:
api:
environment:
- NODE_ENV=development
volumes:
- ./src:/app/src
# docker-compose.prod.yml (override untuk production)
services:
api:
environment:
- DATABASE_URL=${PROD_DATABASE_URL}
deploy:
replicas: 3
# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up
Secret di .env, Bukan di YAML #
File .env di-.gitignore, tidak ter-commit. Saat deploy, file .env di-inject via mekanisme lain (CI/CD variable, secret manager, file yang di-mount dari host).
# .gitignore
.env
.env.*
!.env.example
File .env.example adalah template yang di-commit untuk referensi developer baru.
Validasi Variable #
Untuk aplikasi yang butuh variable tertentu, validasi di startup.
// Node.js
const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
const missing = required.filter(v => !process.env[v]);
if (missing.length) {
console.error(`Missing env vars: ${missing.join(', ')}`);
process.exit(1);
}
Jangan Simpan Secret di Image #
Pastikan Dockerfile tidak menyimpan secret di layer image.
# BURUK - secret di-bake ke image
ENV API_KEY=sk-1234567890
# BAGUS - secret di-inject saat runtime
# (tidak ada ENV untuk secret)
Gunakan .dockerignore untuk exclude file secret dari build context.
Gunakan Secret Manager di Production #
Untuk production, jangan pakai .env atau secrets: Compose saja. Gunakan secret manager proper:
- AWS Secrets Manager
- HashiCorp Vault
- Google Secret Manager
- Azure Key Vault
Integrasikan dengan aplikasi via SDK atau init container.
Contoh Lengkap #
File docker-compose.yml dengan konfigurasi environment yang lengkap:
services:
api:
build: ./api
image: myregistry.com/api:${VERSION:-latest}
ports:
- "${HOST_PORT:-8080}:8080"
environment:
- NODE_ENV=${NODE_ENV:-production}
- LOG_LEVEL=${LOG_LEVEL:-info}
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
env_file:
- .env.runtime
secrets:
- api_key
- jwt_secret
depends_on:
db:
condition: service_healthy
cache:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_DB=${DB_NAME}
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
cache:
image: redis:7-alpine
command: redis-server --requirepass ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
secrets:
api_key:
file: ./secrets/api_key.txt
jwt_secret:
file: ./secrets/jwt_secret.txt
volumes:
db-data:
File .env (untuk substitusi):
VERSION=1.2.3
HOST_PORT=8080
NODE_ENV=production
LOG_LEVEL=info
DB_USER=app
DB_PASSWORD=secret
DB_NAME=myapp
REDIS_PASSWORD=redissecret
DATABASE_URL=postgres://app:secret@db:5432/myapp
REDIS_URL=redis://:redissecret@cache:6379
File .env.runtime (untuk inject):
APP_VERSION=1.2.3
SENTRY_DSN=https://...
FEATURE_FLAGS=newUI,betaSearch
Konfigurasi ini memisahkan:
- Image tag & port: dari
.env(untuk versioning) - Konfigurasi koneksi: dari
.env(untuk substitusi) - Konfigurasi runtime: dari
.env.runtime(untuk inject) - Secret: dari file
./secrets/
Tooling Pendukung #
dotenv (Berbagai Bahasa) #
Kebanyakan bahasa punya library untuk membaca .env file. Ini membuat aplikasi portable — bisa jalan dengan atau tanpa Docker.
# Python (python-dotenv)
from dotenv import load_dotenv
import os
load_dotenv() # baca .env file
database_url = os.getenv("DATABASE_URL")
// Node.js (dotenv)
require('dotenv').config();
const databaseUrl = process.env.DATABASE_URL;
// Go (godotenv atau built-in)
import "github.com/joho/godotenv"
// Di main:
godotenv.Load()
docker-compose CLI untuk Debugging #
# Tampilkan env var yang akan di-inject ke service
docker compose run --rm api env
# Inspect service yang sedang jalan
docker compose exec api env
# Lihat resolved value
docker compose config | grep -A 5 "DATABASE_URL"
Tool docker compose config sangat berguna untuk debug — ia menunjukkan file final setelah semua substitution dan merge.
Validasi dengan docker compose config #
# Strict validation
docker compose config --quiet
Jika ada error syntax atau referensi yang tidak valid, exit code non-zero. Cocok untuk CI/CD pipeline.
# .github/workflows/ci.yml
- name: Validate docker-compose
run: docker compose config --quiet
Troubleshooting #
Variable Tidak Di-Inject #
Gejala: aplikasi tidak bisa baca variable. Cek:
- Apakah variable ada di
environmentatauenv_file? - Apakah file env_file ada di path yang benar?
- Apakah nama variable benar (case-sensitive)?
- Apakah ada typo di nama?
Variable Berubah Saat Runtime #
Gejala: variable di-container tidak match dengan yang di-set. Cek:
- Apakah Dockerfile punya
ENVyang override? - Apakah ada
entrypointscript yang modify env? - Apakah
commandoverride env yang di-set?
Variable Punya Nilai Berbeda per Environment #
Gejala: konfigurasi benar di development tapi salah di production. Cek:
- Apakah kamu menjalankan file yang benar?
docker compose -f ...menentukan file mana yang dipakai. - Apakah override file merge dengan benar?
version: "3.8"di top-level vs di override. - Apakah
.envdi-load dari direktori yang benar? Compose membaca.envdi CWD, bukan di pathdocker-compose.yml.
Variable Berisi Karakter Spesial #
Karakter seperti $, \, newline butuh escaping.
# Karakter $
KEY=value_with_$$dollar
# Pakai single quote
KEY='value with $dollar'
# Newline
KEY="line1\nline2" # literal \n, bukan newline
Untuk password yang punya karakter khusus, lebih aman pakai secrets: daripada env var.
Recap Pattern #
Pilih pattern berdasarkan use case:
| Use case | Pattern |
|---|---|
| Konfigurasi tetap per service | Hardcoded di YAML |
| Konfigurasi per environment | env_file dengan multiple file |
| Substitusi image tag / port | .env otomatis |
| Secret lokal (dev) | .env di-.gitignore |
| Secret production | secrets: + secret manager |
| Variable dari CI/CD | Shell env var |
| Variable dari local override | .env di working directory |
Golden rule: Konfigurasi yang sama untuk semua orang (port default, nama service, image tag stable) → hardcoded atau.env. Konfigurasi yang berbeda per orang/per environment (path lokal, secret, kredensial database) →.envdi-.gitignoreatausecrets:.
Ringkasan #
- Environment variable di Compose punya 4 mekanisme: hardcoded di YAML,
env_file,.envotomatis, dan shell environment.- Hardcoded cocok untuk konfigurasi non-sensitif yang tetap. Jangan hardcode secret.
env_fileuntuk inject file ke container.required: falseuntuk file opsional..envotomatis untuk substitusi di YAML (image tag, port, variable versi). Bukan untuk inject ke container.secrets:untuk data sensitif yang tidak boleh terlihat didocker inspect. Lebih aman dari environment variable.build argsuntuk variable saat build,ENVdi Dockerfile untuk runtime.- Pisahkan konfigurasi per environment dengan override file:
-f docker-compose.yml -f docker-compose.dev.yml.- Validasi variable wajib di startup aplikasi agar gagal cepat jika konfigurasi missing.
- Production: gunakan secret manager (AWS Secrets Manager, Vault) bukan
.envsaja.- Jangan simpan secret di image — jangan
ENV SECRET=...di Dockerfile.
← Sebelumnya: Multi-Container Berikutnya: Volume & Network →