Kembali ke Panduan Docker

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.

1
Pahami dulu

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.

Analogi: bayangkan kotak yang sama dititipi amplop rahasia yang berbeda. Amplop untuk staging berisi kunci uji coba; amplop untuk production berisi kunci asli. Kotaknya identik — amplopnya yang menentukan ia jadi staging atau production.
2
Paling sering bikin bingung

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.

Aturan praktis: hanya 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.
3
Rencanakan

Daftar env yang dibutuhkan tiap environment

Sebelum menulis file apa pun, buat dulu "daftar belanja". Variabel yang sama, nilai yang berbeda per environment. Contohnya:

VariabelDevelopmentProduction
NEXT_PUBLIC_APP_URLlocalhost:3000domainkamu.com
DATABASE_URLdb lokaldb production
AUTH_SECRETasal (uji)acak & rahasia
RESEND_API_KEYkey testkey 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=
4
Inti multi-env

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_123

Lalu 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_...
Wajib: 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.
5
Keamanan

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.example

Lalu pastikan juga semuanya diabaikan Git. Cek dari laptop bahwa file env asli memang sudah aman:

kamu@laptop: ~/blog
kamu@laptop:~/blog$ git status --ignoredIgnored files:        .env.development        .env.staging        .env.production # bagus — hanya .env.example yang boleh ter-commit
6

Dockerfile 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.

GNU nano 7.2~/blog/Dockerfile
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"]
^O Write Out^X Exit^W Where Is^K Cut

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"]
7
Sekali atur

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}
Bagian ${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.
8
Eksekusi

Jalankan environment yang kamu mau

Untuk menjalankan production, cukup satu baris (memakai default tadi):

deploy@vps: ~
deploy@vps:~$ cd ~/blogdeploy@vps:~$ docker compose up -d --build[+] Building 41.7s ... FINISHED[+] Running 1/1 ✔ Container blog  Started

Untuk 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 --build

Blog production-mu kini hidup di dalam Docker:

https://domainkamu.com
Koneksi aman — HTTPS aktif

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…

Deployment berhasil
9
Verifikasi

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: ~
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 harapkan
Ingat: NEXT_PUBLIC_APP_URLsudah "dipanggang" saat build. Kalau nilainya salah, mengganti env_file saja tidak cukup — kamu harus build ulang image dengan ARG yang benar.
10
Perawatan

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: ~
deploy@vps:~$ nano .env.production# ubah nilai yang perlu, simpandeploy@vps:~$ docker compose up -d --build ✔ Container blog  Started

Biar 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

GejalaSolusi
NEXT_PUBLIC_* masih nilai lama di browserItu env build-time. Build ulang: docker compose up -d --build dengan ARG yang benar.
DATABASE_URL undefined di dalam containerenv_file salah/tidak ditemukan. Cek nilai ENV_FILE dan nama file env-nya cocok.
Keliru jalan pakai env staging di productionSelalu cek dengan docker compose exec blog env sebelum percaya (langkah 9).
File .env ikut masuk ke imageLengkapi .dockerignore dengan .env dan .env.* (langkah 5), lalu build ulang.
Secret tak sengaja ter-push ke GitHubAnggap 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".