Config Berbeda per Environment, Tapi Kode Sama
Aplikasi perlu tahu: URL database production, API key Stripe, secret JWT. Nilai-nilai ini berbeda per environment (dev, staging, prod) tapi kode harus tetap sama. Jawabannya: environment variables.
Env Var Dasar
# Set sementara (hanya untuk satu command)
DATABASE_URL=postgres://localhost/mydb node server.js
# Set untuk session shell
export DATABASE_URL=postgres://localhost/mydb
echo $DATABASE_URL
# Akses di aplikasi
# Node.js
process.env.DATABASE_URL
# PHP
getenv('DATABASE_URL');
$_ENV['DATABASE_URL'];
# Python
os.environ.get('DATABASE_URL')
.env Files untuk Development
# .env (di root project)
DATABASE_URL=postgres://localhost/mydb
STRIPE_SECRET_KEY=sk_test_abc123
JWT_SECRET=dev-secret-do-not-use-in-prod
LOG_LEVEL=debug
# Library loader (dotenv di Node, phpdotenv di PHP)
# otomatis baca .env saat aplikasi start
⚠️ WAJIB: tambahkan .env ke .gitignore. Commit .env.example (tanpa nilai sensitif) sebagai template bagi tim.
12-Factor App: Config
Prinsip III dari 12-factor app: store config in the environment. Artinya:
- Kode sama di semua environment (dev/staging/prod)
- Config disimpan di env var, bukan file yang berbeda per env
- Tidak ada
if (env === "prod")tersebar di kode - Mudah deploy image yang sama ke berbagai env dengan env var berbeda
Config vs Secrets: Berbeda!
# CONFIG — aman di commit, tidak rahasia
LOG_LEVEL=info
DEFAULT_PAGE_SIZE=20
FEATURE_DARK_MODE=true
# SECRETS — WAJIB dirahasiakan
DATABASE_PASSWORD=prod-secret-xyz
STRIPE_SECRET_KEY=sk_live_abc
JWT_SIGNING_KEY=very-long-random-string
AWS_SECRET_ACCESS_KEY=...
Kedua jenis sama-sama lewat env var, tapi penanganannya berbeda. Secrets butuh rotasi, access control ketat, dan audit log.
Secret Managers
Menyimpan secret di env var plain text di server production berisiko. Gunakan secret manager:
- HashiCorp Vault — open source, fitur lengkap, bisa self-host
- AWS Secrets Manager — managed, integrasi IAM, auto-rotation
- AWS Parameter Store — lebih murah, lebih sederhana
- Google Secret Manager — managed di GCP
- Doppler — SaaS, friendly untuk tim kecil
- 1Password Connect / Infisical — alternatif modern
# Flow umum di production:
# 1. App start
# 2. Auth ke secret manager pakai IAM role (bukan credential)
# 3. Fetch secrets ke memory
# 4. Tidak pernah menulis ke disk
// Contoh AWS SDK (Node)
const client = new SecretsManagerClient({ region: "ap-southeast-1" });
const data = await client.send(
new GetSecretValueCommand({ SecretId: "prod/db" })
);
const { username, password } = JSON.parse(data.SecretString);
KMS & Encryption-at-Rest
KMS (Key Management Service) mengelola master encryption key. Secret manager menyimpan secret dalam bentuk terenkripsi di disk, KMS yang memegang key-nya. Akses secret → app butuh permission baca secret dan permission kms:Decrypt.
Secret Rotation
Secret yang sama selama 2 tahun = risiko besar jika bocor. Best practice: rotate secara berkala.
- Database password: rotate setiap 30-90 hari
- API key service: saat ada developer keluar tim
- JWT signing key: rotate dengan grace period agar token lama tetap valid sementara
- AWS Secrets Manager bisa auto-rotate database credential (Lambda + dual-user pattern)
Secrets di CI/CD
# GitHub Actions — encrypted di repository settings
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.PRODUCTION_API_KEY }}
run: ./deploy.sh
OIDC: Pengganti Long-Lived Credentials
Daripada menyimpan AWS_ACCESS_KEY_ID tetap di secrets (berisiko bocor via log), gunakan OpenID Connect. GitHub bertindak sebagai identity provider, cloud provider meng-issue credential sementara per workflow run.
permissions:
id-token: write
jobs:
deploy:
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/gha-deploy
aws-region: ap-southeast-1
# Credential temporary — tidak ada yang disimpan long-term
Build-Time vs Runtime Secrets
- Build-time — secret dibutuhkan saat build (misal: NPM private registry token). ⚠️ Jangan bake secret ke image Docker — akan tersimpan di layer!
- Runtime — secret di-inject saat container start (environment variable). Aman, tidak terkunci di image
# BURUK: ENV di Dockerfile — secret ada di image layer selamanya
ENV DATABASE_PASSWORD=supersecret
# BAIK: inject saat run
docker run -e DATABASE_PASSWORD=$DB_PASS myapp
# atau pakai --env-file .env.production
Common Leaks: Apa yang Harus Dihindari
- Commit .env ke Git — jika sudah, rotate semua secret + pakai
git-filter-repountuk purge - Log secret — jangan pernah
console.log(process.env)di production - Client-side bundling — variabel
PUBLIC_*atauNEXT_PUBLIC_*masuk JS bundle, bisa dilihat user! - Screenshot terminal dengan env var terpampang
- Error message yang expose config — sanitize error response
- Hardcoded fallback:
process.env.KEY || "default-secret"→ "default-secret" ke repo publik
Tools seperti git-secrets, gitleaks, atau GitHub secret scanning bisa mendeteksi leak sebelum push.