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 — file docker-compose.yml biasanya di-commit ke Git. Secret yang ada di sini akan terekspos di repository history. Selalu gunakan env_file atau secrets untuk 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:

  1. Environment variable di shell (override yang di file)
  2. File .env di current directory
  3. File .env di parent directory
  4. Variable env di 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:

  1. Apakah variable ada di environment atau env_file?
  2. Apakah file env_file ada di path yang benar?
  3. Apakah nama variable benar (case-sensitive)?
  4. Apakah ada typo di nama?

Variable Berubah Saat Runtime #

Gejala: variable di-container tidak match dengan yang di-set. Cek:

  1. Apakah Dockerfile punya ENV yang override?
  2. Apakah ada entrypoint script yang modify env?
  3. Apakah command override env yang di-set?

Variable Punya Nilai Berbeda per Environment #

Gejala: konfigurasi benar di development tapi salah di production. Cek:

  1. Apakah kamu menjalankan file yang benar? docker compose -f ... menentukan file mana yang dipakai.
  2. Apakah override file merge dengan benar? version: "3.8" di top-level vs di override.
  3. Apakah .env di-load dari direktori yang benar? Compose membaca .env di CWD, bukan di path docker-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) → .env di-.gitignore atau secrets:.

Ringkasan #

  • Environment variable di Compose punya 4 mekanisme: hardcoded di YAML, env_file, .env otomatis, dan shell environment.
  • Hardcoded cocok untuk konfigurasi non-sensitif yang tetap. Jangan hardcode secret.
  • env_file untuk inject file ke container. required: false untuk file opsional.
  • .env otomatis untuk substitusi di YAML (image tag, port, variable versi). Bukan untuk inject ke container.
  • secrets: untuk data sensitif yang tidak boleh terlihat di docker inspect. Lebih aman dari environment variable.
  • build args untuk variable saat build, ENV di 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 .env saja.
  • Jangan simpan secret di image — jangan ENV SECRET=... di Dockerfile.

← Sebelumnya: Multi-Container   Berikutnya: Volume & Network →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact