Environment Variable Tidak Terbaca di Production: Penyebab dan Cara Fixnya
Environment Variable Tidak Terbaca di Production: Penyebab dan Cara Fixnya
"Di lokal jalan, di production error." Kalimat ini hampir selalu berkaitan dengan environment variable yang tidak terbaca dengan benar. Ini adalah salah satu bug paling umum sekaligus paling mudah dicegah — kalau kamu tahu pola-pola masalahnya.
1. Penyebab Paling Umum
Penyebab 1: File .env Tidak Di-deploy
# .gitignore yang benar memang mengecualikan .env
# Tapi ini berarti .env tidak ikut ke server production!
# ❌ Kesalahan umum: developer lupa set env var di server
# Aplikasi jalan tapi DATABASE_URL = undefined
# Cara cek di server:
echo $DATABASE_URL
# atau
printenv | grep DATABASE
# Solusi: set env var di server secara eksplisit
# Jangan copy .env ke server — set satu per satu atau pakai secret manager
Penyebab 2: Nama Variable Typo atau Case Salah
# Environment variable bersifat case-sensitive!
# Di .env lokal:
database_url=postgresql://... # huruf kecil
# Di kode:
process.env.DATABASE_URL # huruf besar → undefined!
# Konvensi standar: SELALU gunakan UPPER_SNAKE_CASE
DATABASE_URL=postgresql://...
JWT_SECRET=mysecret
API_KEY=abc123
Penyebab 3: dotenv Tidak Di-load Sebelum Dipakai
// ❌ Salah — import config sebelum dotenv di-load
import { dbConfig } from './config/database'; // Ini baca process.env saat import
import dotenv from 'dotenv';
dotenv.config(); // Terlambat! dbConfig sudah di-evaluate dengan env yang kosong
// ✅ Benar — dotenv harus di-load PERTAMA KALI, sebelum import lain
import dotenv from 'dotenv';
dotenv.config(); // Load dulu
import { dbConfig } from './config/database'; // Baru import yang butuh env
import app from './app';
Penyebab 4: Variable Tidak Tersedia di Build Time (Frontend)
// Untuk Vite: variable HARUS diawali VITE_
// Untuk Create React App: variable HARUS diawali REACT_APP_
// ❌ Tidak akan terbaca di frontend
API_URL=https://api.example.com
// ✅ Akan terbaca di frontend Vite
VITE_API_URL=https://api.example.com
// ✅ Akan terbaca di frontend CRA
REACT_APP_API_URL=https://api.example.com
// Cara akses di kode:
const apiUrl = import.meta.env.VITE_API_URL; // Vite
const apiUrl = process.env.REACT_APP_API_URL; // CRA
// PENTING: Variable frontend ini akan ter-embed di bundle JavaScript
// Jangan masukkan secret key di sini!
Penyebab 5: Docker Tidak Meneruskan Env Var
# ❌ Env var di host tidak otomatis masuk ke container
export DATABASE_URL=postgresql://...
docker run myapp # DATABASE_URL tidak tersedia di dalam container!
# ✅ Cara 1: Pass eksplisit dengan -e
docker run -e DATABASE_URL=$DATABASE_URL myapp
# ✅ Cara 2: Pass semua dari file .env
docker run --env-file .env myapp
# ✅ Cara 3: Di docker-compose.yml
services:
app:
environment:
- DATABASE_URL=${DATABASE_URL} # Ambil dari host
- NODE_ENV=production # Set langsung
env_file:
- .env.production # Atau dari file
2. Cara Debug Environment Variable
// Tambahkan logging sementara untuk debug (hapus setelah selesai!)
console.log('ENV CHECK:', {
NODE_ENV: process.env.NODE_ENV,
DATABASE_URL: process.env.DATABASE_URL ? '✓ SET' : '✗ MISSING',
JWT_SECRET: process.env.JWT_SECRET ? '✓ SET' : '✗ MISSING',
// Jangan log nilai aslinya! Hanya cek apakah ada atau tidak
});
// Atau buat endpoint debug (HANYA di development!)
if (process.env.NODE_ENV === 'development') {
app.get('/debug/env', (req, res) => {
const safeEnv = Object.keys(process.env).reduce((acc, key) => {
acc[key] = process.env[key] ? '✓ SET' : '✗ MISSING';
return acc;
}, {});
res.json(safeEnv);
});
}
3. Best Practice: Validasi Env Var saat Startup
// config/env.ts — validasi semua env var saat aplikasi start
// Jika ada yang missing, aplikasi langsung crash dengan pesan jelas
// Lebih baik crash saat start daripada error misterius saat runtime
import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.string().default('3000'),
DATABASE_URL: z.string().url('DATABASE_URL harus berupa URL yang valid'),
JWT_SECRET: z.string().min(32, 'JWT_SECRET minimal 32 karakter'),
REDIS_URL: z.string().url().optional(),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('❌ Environment variable tidak valid:');
console.error(parsed.error.flatten().fieldErrors);
process.exit(1); // Crash dengan pesan yang jelas
}
export const env = parsed.data;
// Cara pakai di seluruh aplikasi:
// import { env } from './config/env';
// const db = new Database(env.DATABASE_URL);
4. Setup Env Var di Platform Populer
Vercel
# Via CLI
vercel env add DATABASE_URL production
# Via dashboard: Settings → Environment Variables
# Pilih environment: Production / Preview / Development
Railway / Render
# Dashboard → Service → Variables
# Tambahkan key-value satu per satu
# Atau import dari file .env
GitHub Actions
# Settings → Secrets and variables → Actions → New repository secret
# Cara pakai di workflow:
jobs:
deploy:
steps:
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
JWT_SECRET: ${{ secrets.JWT_SECRET }}
run: npm run deploy
VPS / Server Langsung
# Tambahkan ke /etc/environment (berlaku untuk semua user)
echo 'DATABASE_URL=postgresql://...' | sudo tee -a /etc/environment
# Atau ke ~/.bashrc / ~/.zshrc (hanya untuk user tertentu)
echo 'export DATABASE_URL=postgresql://...' >> ~/.bashrc
source ~/.bashrc
# Untuk systemd service, tambahkan di unit file:
[Service]
Environment="DATABASE_URL=postgresql://..."
EnvironmentFile=/etc/myapp/production.env
Kesimpulan
Environment variable yang tidak terbaca hampir selalu disebabkan oleh salah satu dari lima penyebab di atas. Solusi jangka panjang terbaik adalah validasi env var saat startup menggunakan schema validation — dengan begitu, aplikasi tidak akan pernah berjalan dalam kondisi konfigurasi yang tidak lengkap, dan error message-nya akan selalu jelas dan actionable.