Konfigurasi Runtime Variables untuk Aplikasi Frontend Statis dalam Docker Image Promotion Workflow

Mon, March 2, 2026 — 6 min read

Men-deploy Docker Image yang sama dari staging hingga production adalah hal yang umum ketika menggunakan deployment berbasis kontainer. Docker Image Promotion adalah sebuah pendekatan untuk menggunakan Docker Image yang sudah di-build, diuji, dan diverifikasi untuk digunakan pada lingkungan yang berbeda seperti staging, test, dan production tanpa perlu build ulang pada setiap langkahnya.

Docker Image Promotion menjamin konsistensi perilaku antar lingkungan, sehingga image yang sudah di-build dan diuji sebelumnya adalah image yang di-deploy ke production. Selain itu, image promotion juga akan memotong waktu deployment menjadi lebih singkat dengan tidak melakukan build ulang; misal, jika build dilakukan pada tahap deployment ke staging, maka ketika ingin melakukan deployment ke production, kita hanya perlu melakukan “promosi” ke Docker Image tersebut, dan tidak melakukan build ulang.

Akar Masalah

Perlu kita ketahui, ARG/--build-arg hanya tersedia ketika build time pada image dan ENV/--env tersedia ketika build time dan runtime pada image. Ini artinya, jika kita memiliki skema environment variables sebagai berikut:

ENVStagingProduction
API_URLhttps://staging.api.comhttps://api.com

Maka, ketika image di-build dan di dalamnya ada proses build untuk aplikasi statis kita, umumnya build tools seperti Vite akan mengganti referensi import.meta.env.API_URL ke nilainya langsung (static replace). Ini akan merusak proses “promosi” image kita dari staging ke production, karena production akan mendapatkan nilai API_URL yang salah.

Bagaimana dengan menggunakan --env ketika menjalankan kontainernya? Perlu diketahui, pemahaman konteks aplikasi itu sangat penting. Karena aplikasi kita adalah aplikasi statis, maka akses ke nilai environment variables dengan process.env atau sejenisnya dari server tidak memungkinkan; aplikasi kita berjalan di browser dan tidak diproses terlebih dahulu di server (SSR).

Untuk menanggulangi masalah tersebut, kita bisa memanfaatkan objek window untuk meletakkan nilai environment variables yang dikirim melalui --env pada saat kontainer dijalankan. Tentu, ada satu hal yang perlu dipastikan sebelum menggunakan cara ini, yaitu pastikan nilai tersebut adalah hal yang aman untuk dilihat oleh publik seperti API URL; sangat tidak disarankan untuk meletakkan secrets apa pun ke objek window.

Menguraikan Solusi

Karena pada konteks ini aplikasi kita berjalan di lingkungan browser, kita bisa memanfaatkan objek window untuk menampung segala nilai yang diperlukan dari --env ketika kontainer dijalankan. Hanya ada dua langkah inti yang akan kita lakukan:

Sehingga, keseluruhan alur deployment akan menjadi seperti berikut ini:

Gambaran besar keseluruhan workflow pada artikel ini

Kita akan menggunakan repositori ini untuk praktek, silakan clone dan ikuti langkah-langkahnya!

Menyiapkan placeholder

Pada berkas index.html, kita bisa menambahkan sebuah placeholder di dalam tag <head> seperti berikut:

<script>
  // ENV_PLACEHOLDERS
</script>

Membuat shell script untuk mengganti placeholder

Shell script ini berguna untuk mengganti ENV_PLACEHOLDERS dengan nilai window.env yang valid. Ekspektasinya, objek window.env tersebut akan menjadi seperti berikut:

window.env = {
  API_URL: "https://staging.api.com",
};

Kita bisa memanfaatkan sed untuk melakukan text replacement terhadap placeholder yang sudah disiapkan.

#!/bin/sh
ENV_STRING='window.env = { \
	"API_URL":"'"${API_URL}"'" \
}'
 
sed -i "s@// ENV_PLACEHOLDERS@${ENV_STRING}@" /usr/share/nginx/html/index.html
exec "$@"
 
nginx -g 'daemon off;'

Lalu, kita perlu menyalin berkas init.sh di atas ke dalam kontainer kita dan menjalankannya. Perbarui berkas Dockerfile menjadi seperti berikut:

FROM oven/bun:1.3.3 AS base
 
WORKDIR /app
 
COPY . /app/
COPY package.json /app/package.json
COPY bun.lock /app/bun.lock
RUN bun install --frozen-lockfile
 
FROM base AS builder
RUN bun run build
 
FROM nginx:alpine-slim AS runner
COPY ./nginx /etc/nginx/conf.d
COPY ./init.sh /app/init.sh
COPY --from=builder /app/dist /usr/share/nginx/html
 
EXPOSE 80
CMD ["sh", "/app/init.sh"]

Ketika kontainer dibuat dan dijalankan, init.sh akan dieksekusi dan akan mengganti placeholder dengan environment variables yang cocok, lalu menjalankan NGINX agar aplikasi kita dapat diakses.

Mencoba dengan beberapa skenario pengujian

Untuk percobaan pertama, kita hanya akan menjalankan kontainernya tanpa mengirimkan environment variables apa pun. Sebelum itu, kita perlu melakukan build image dengan Dockerfile yang sudah tersedia. Silakan jalankan perintah berikut:

docker build -t mupinnn/inject-fe .

Jika proses build telah selesai, kamu bisa memastikan image-nya siap dengan menjalankan perintah docker image ls dan lihat image dengan nama mupinnn/inject-fe terdapat di daftar.

Hasil perintah docker image ls

Lalu, kita akan buat dan menjalankan kontainer berdasarkan image yang sudah kita buat tadi dengan perintah:

docker run -p 8181:80 \
mupinnn/inject-fe:latest

Perintah di atas akan menjalankan kontainernya dan bisa diakses melalui http://localhost:8181. Kamu bisa memastikan bahwa window.env sudah tersedia dengan membuka page source (CTRL + u) atau buka console dan ketik window.env.

Page source dengan window.env kosong
Console dengan window.env kosong

Okey, sampai sini kita berhasil mengganti placeholder dengan objek window yang kita inginkan. Selanjutnya kita harus melakukan uji coba dengan nilai environment variables yang berbeda.


Berikutnya, kita akan menjalankannya dengan nilai API_URL untuk staging seperti berikut:

docker run -p 8082:80 \
--env API_URL="https://staging.api.com" \
--name mupinnn-inject-fe-staging \
mupinnn/inject-fe:latest

Akses melalui http://localhost:8082 dan pastikan kembali nilai window.env sudah tersedia seperti langkah sebelumnya. Hasil akhirnya akan menjadi seperti ini:

Page source dengan window.env staging
Console dengan window.env staging

Kemudian, kita akan menjalankan kontainer untuk production dari image yang sama dengan perintah:

docker run -p 8083:80 \
--env API_URL="https://api.com" \
--name mupinnn-inject-fe-prd \
mupinnn/inject-fe:latest

Akses melalui http://localhost:8083 dan pastikan kembali nilai window.env dengan cara yang sama sepert di atas. Kamu akan melihat bahwa nilai API_URL selalu berubah tergantung apa yang dikirim melalui --env dan kita melakukannya tanpa rebuild!

Page source dengan window.env production
Console dengan window.env production


Untuk mengaksesnya di runtime aplikasi, kamu bisa gunakan window.env.API_URL. Mungkin, pada saat development kamu tidak menggunakan Docker sama sekali, berarti window.env tidak akan tersedia. Kamu bisa mengakses nilainya dengan menggunakan fallback seperti import.meta.env.API_URL ?? window.env.API_URL. Untuk memudahkan penggunaan, kamu bisa membuat sebuah helper function untuk mengakses environment variables.

function getEnv(key) {
  const envMap = {
    API_URL: import.meta.env.API_URL,
  };
 
  return window?.env?.[key] || envMap[key];
}

Jika kamu menggunakan TypeScript dan Vite, kamu bisa meningkatkan developer experience dengan membuat fungsi getEnv menjadi lebih type-safe.

export function getEnv<K extends keyof ImportMetaEnv>(key: K) {
  const envMap = {
    API_URL: import.meta.env.API_URL,
  };
 
  return window?.env?.[key] || envMap[key];
}

Lalu, kamu bisa me-extend type pada objek window dengan menambahkan kode di bawah ke dalam berkas vite-env.d.ts

declare global {
  interface Window {
    env?: ImportMetaEnv;
  }
}

Workflow yang sesungguhnya

Proses di atas merupakan proof-of-concept singkat dan bisa diimplementasikan pada skenario deployment sederhana. Pada organisasi yang lebih kompleks dan terstruktur, biasanya mereka akan melakukan labeling dan tagging pada image di tiap tahapnya. Labeling dan tagging ini berguna untuk mempermudah pelacakan dan rollback jika suatu saat terjadi error pada aplikasi atau pada saat menjalankan kontainernya.

Tentu, labeling dan tagging ini tidak memerlukan proses build lagi sehingga proses di atas masih relevan. Build once, multiple environments, deploy anywhere.

Kesimpulan

Dengan shell script yang cukup sederhana, memahami proses deployment, memahami perbedaan antara runtime dan build time, dan memahami lingkungan di mana aplikasi dijalankan—kamu akan bisa memaksimalkan segala potensi dan kemungkinan yang ada untuk melakukan optimisasi dari pengetahuan tersebut.

Terima kasih!

Referensi