Lanjutan Docker — banyak env, banyak environment, ada gambar tiap langkah
Deploy Docker dengan banyak environment variable
Sudah bisa men-deploy blog di Docker, tapi sekarang kamu punya banyak rahasia (database, secret login, API key) dan beberapa environment — laptop, staging, dan production? Panduan ini melanjutkan tutorial Docker dari nol dan menata semuanya pelan-pelan, dengan gambar di tiap langkah, sampai deploy benar-benar berhasil.
Multi-environment
Satu kode, beda nilai env untuk dev, staging, & production.
Build vs runtime
Paham mana env yang "dipanggang" saat build, mana yang disuntik saat jalan.
Aman
Rahasia tak ikut masuk image maupun ter-push ke GitHub.
Kenapa env di Docker terasa beda?
Tanpa Docker, semua env biasanya dibaca dari satu file .env di server. Dengan Docker, blog kamu hidup di dalam kotak tertutup (container) — ia tidak otomatis melihat file di luar kotak. Jadi kita harus sengaja menyuntikkan env ke dalam kotak.
Kabar baiknya: justru di sinilah Docker bersinar. Satu kotak yang sama bisa kamu jalankan dengan kumpulan env berbeda untuk tiap environment. Kotaknya sama, isinya (rahasianya) yang berganti.
Bedakan env build-time dan runtime
Ini bagian terpenting. Ada dua jenis env, dan memperlakukannya keliru adalah penyebab error nomor satu pemula:
Build-time (dipanggang)
Env NEXT_PUBLIC_*yang dipakai di kode browser. Nilainya ikut "dipanggang" ke dalam image saat build. Harus dimasukkan lewat ARG.
Runtime (disuntik)
Rahasia server seperti DATABASE_URL & AUTH_SECRET. Disuntik saat container jalan lewat env_file, bukan saat build.
NEXT_PUBLIC_* yang perlu di-build. Rahasia server jangan pernah dimasukkan lewat ARG — karena nilainya akan tertanam permanen di dalam image dan bisa terbaca siapa pun yang punya image itu.Daftar env yang dibutuhkan tiap environment
Sebelum menulis file apa pun, buat dulu "daftar belanja". Variabel yang sama, nilai yang berbeda per environment. Contohnya:
| Variabel | Development | Production |
|---|---|---|
| NEXT_PUBLIC_APP_URL | localhost:3000 | domainkamu.com |
| DATABASE_URL | db lokal | db production |
| AUTH_SECRET | asal (uji) | acak & rahasia |
| RESEND_API_KEY | key test | key live |
Tulis kerangkanya di .env.example tanpa nilai — file ini aman di-commit dan jadi panduan buat kamu (atau tim) nanti:
# .env.example → TEMPLATE, boleh di-commit (tanpa nilai rahasia)
NODE_ENV=
NEXT_PUBLIC_APP_URL=
DATABASE_URL=
AUTH_SECRET=
RESEND_API_KEY=Buat file .env terpisah per environment
Kuncinya: satu file untuk satu environment. Kita buat tiga file dengan nilai berbeda. Pertama, env untuk ngoprek di laptop:
# .env.development — dipakai saat ngoprek di laptop
NODE_ENV=development
NEXT_PUBLIC_APP_URL=http://localhost:3000
DATABASE_URL=postgres://blog:dev@localhost:5432/blog_dev
AUTH_SECRET=dev-secret-boleh-asal
RESEND_API_KEY=re_test_123Lalu env untuk server staging (uji coba):
# .env.staging — server uji coba sebelum live
NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://staging.domainkamu.com
DATABASE_URL=postgres://blog:R4hAsiA@db-staging:5432/blog_stg
AUTH_SECRET=Hxk9c2QvN0p7m3R8tY1aLz6kPq...VbQwE=
RESEND_API_KEY=re_live_staging_...Dan terakhir env untuk production yang diakses pengunjung asli:
# .env.production — server live yang diakses pengunjung
NODE_ENV=production
NEXT_PUBLIC_APP_URL=https://domainkamu.com
DATABASE_URL=postgres://blog:SaNg4tR4hasia@db-prod:5432/blog
AUTH_SECRET=Zq3mN8xR1vK7... (hasil openssl, BEDA dari staging)
RESEND_API_KEY=re_live_prod_...AUTH_SECRET staging dan production harus berbeda. Buat masing-masing dengan openssl rand -base64 32. Kalau secret production bocor, staging tetap aman — begitu pula sebaliknya.Pagari rahasia dengan .dockerignore & .gitignore
Sebelum lanjut, kita pastikan file rahasia tadi tidak ikut masuk ke dalam image maupun ke GitHub. Tambahkan ke .dockerignore:
# .dockerignore — JANGAN biarkan rahasia ikut masuk kotak
node_modules
.next
.git
# semua file env asli — nilai rahasia tak boleh ikut ter-build
.env
.env.*
# kecuali templatenya yang aman:
!.env.exampleLalu pastikan juga semuanya diabaikan Git. Cek dari laptop bahwa file env asli memang sudah aman:
kamu@laptop:~/blog$ git status --ignoredIgnored files: .env.development .env.staging .env.production # bagus — hanya .env.example yang boleh ter-commitDockerfile yang sadar build-time vs runtime
Sekarang sesuaikan Dockerfile. Env browser (NEXT_PUBLIC_*) masuk lewat ARG agar ikut ter-build, sementara rahasia server sengaja tidak ditulis di sini — itu urusan runtime.
1FROM node:20-alpine AS builder2WORKDIR /app3RUN corepack enable4 5# env build-time → lewat ARG lalu ENV6ARG NEXT_PUBLIC_APP_URL7ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL8 9COPY package.json pnpm-lock.yaml ./10RUN pnpm install --frozen-lockfile11COPY . .12RUN pnpm build13 14FROM node:20-alpine AS runner15WORKDIR /app16ENV NODE_ENV=production17COPY --from=builder /app/public ./public18COPY --from=builder /app/.next/standalone ./19COPY --from=builder /app/.next/static ./.next/static20EXPOSE 300021CMD ["node", "server.js"]Salin teks lengkapnya dari sini:
# Dockerfile — sadar mana env build-time, mana runtime
FROM node:20-alpine AS builder
WORKDIR /app
RUN corepack enable
# 1) env build-time HARUS masuk lewat ARG lalu jadi ENV
# (khusus yang berawalan NEXT_PUBLIC_ — ikut "dipanggang")
ARG NEXT_PUBLIC_APP_URL
ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
# env runtime (DATABASE_URL, AUTH_SECRET, dst) TIDAK ditulis di sini —
# nanti disuntik saat container jalan lewat env_file.
CMD ["node", "server.js"]docker-compose yang bisa ganti environment
Trik utamanya: buat docker-compose.yml yang membaca variabel pemilih. ENV_FILE menentukan file env runtime mana yang disuntik, dan NEXT_PUBLIC_APP_URL diteruskan sebagai build arg. Dengan begitu satu file Compose melayani semua environment.
# docker-compose.yml — satu file, beda environment lewat .env
services:
blog:
build:
context: .
args:
# env build-time diteruskan ke Dockerfile saat build
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
container_name: blog
restart: unless-stopped
ports:
- "${PORT:-3000}:3000"
# env runtime disuntik dari file yang kita pilih
env_file:
- ${ENV_FILE:-.env.production}${ENV_FILE:-.env.production}artinya: "pakai nilai ENV_FILE kalau diset, kalau tidak ya default ke .env.production". Jadi kalau lupa, ia aman jatuh ke production.Jalankan environment yang kamu mau
Untuk menjalankan production, cukup satu baris (memakai default tadi):
deploy@vps:~$ cd ~/blogdeploy@vps:~$ docker compose up -d --build[+] Building 41.7s ... FINISHED[+] Running 1/1 ✔ Container blog StartedUntuk menjalankan staging di server (atau port lain), set variabel pemilihnya dulu, baru jalankan:
# jalankan environment STAGING
export ENV_FILE=.env.staging
export NEXT_PUBLIC_APP_URL=https://staging.domainkamu.com
export PORT=3001
docker compose up -d --buildBlog production-mu kini hidup di dalam Docker:
Blog berbasis markdown
Tulis di Markdown. Push ke GitHub. Deploy di VPS milikmu sendiri.
Menyiapkan VPS pertama kamu
Sebuah catatan singkat tentang menjalankan blog kamu sendiri…
Menulis dengan Markdown
Sebuah catatan singkat tentang menjalankan blog kamu sendiri…
Pastikan env benar-benar terbaca di dalam container
Jangan cuma berharap — buktikan. Kita intip env yang benar-benar dilihat proses di dalam kotak. Ini cara tercepat memastikan kamu menjalankan environment yang benar (jangan sampai keliru pakai secret staging di production!).
deploy@vps:~$ docker compose exec blog env | grep -E "NODE_ENV|APP_URL"NODE_ENV=productionNEXT_PUBLIC_APP_URL=https://domainkamu.com # kalau salah environment, nilainya akan beda dari yang kamu harapkanNEXT_PUBLIC_APP_URLsudah "dipanggang" saat build. Kalau nilainya salah, mengganti env_file saja tidak cukup — kamu harus build ulang image dengan ARG yang benar.Ganti / rotate secret tanpa pusing
Perlu ganti API key atau mengganti secret yang bocor? Edit file env yang sesuai, lalu build & jalankan ulang. Rahasia runtime cukup up -d; tapi kalau yang berubah NEXT_PUBLIC_*, wajib pakai --build.
deploy@vps:~$ nano .env.production# ubah nilai yang perlu, simpandeploy@vps:~$ docker compose up -d --build ✔ Container blog StartedBiar konsisten tiap deploy, simpan langkah-langkahnya ke deploy.sh:
#!/usr/bin/env bash
set -e
cd ~/blog
echo "==> Ambil kode terbaru"
git pull
echo "==> Build & jalankan ulang environment production"
ENV_FILE=.env.production \
NEXT_PUBLIC_APP_URL=https://domainkamu.com \
docker compose up -d --build
docker image prune -f
echo "Deploy berhasil — env terbaru sudah disuntik ke container!"Kalau ada masalah
| Gejala | Solusi |
|---|---|
| NEXT_PUBLIC_* masih nilai lama di browser | Itu env build-time. Build ulang: docker compose up -d --build dengan ARG yang benar. |
| DATABASE_URL undefined di dalam container | env_file salah/tidak ditemukan. Cek nilai ENV_FILE dan nama file env-nya cocok. |
| Keliru jalan pakai env staging di production | Selalu cek dengan docker compose exec blog env sebelum percaya (langkah 9). |
| File .env ikut masuk ke image | Lengkapi .dockerignore dengan .env dan .env.* (langkah 5), lalu build ulang. |
| Secret tak sengaja ter-push ke GitHub | Anggap bocor: rotate semua key sekarang, dan pastikan .gitignore benar. |
Perintah penyelamat: docker compose exec blog env (lihat env asli di kotak), docker compose logs -f blog (lihat error), dan docker compose config (lihat hasil akhir Compose setelah variabel diisi).
Selamat, deploy berhasil!
Satu kotak Docker, banyak environment, puluhan env tertata rapi dan aman. Kamu sekarang bisa ganti dari development ke staging ke production hanya dengan menukar "amplop rahasia".