Konfigurasi Runtime Variables untuk Aplikasi Frontend Statis dalam Docker Image Promotion Workflow
Mon, March 2, 2026 — 6 min read
This article is available in other language(s): English
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:
| ENV | Staging | Production |
|---|---|---|
API_URL | https://staging.api.com | https://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:
- Membuat placeholder di berkas HTML pada bagian
<head> - Membuat sebuah shell script untuk meletakkan nilai yang dikirim melalui
--envke placeholder yang sudah dibuat
Sehingga, keseluruhan alur deployment akan menjadi seperti berikut 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.

Lalu, kita akan buat dan menjalankan kontainer berdasarkan image yang sudah kita buat tadi dengan perintah:
docker run -p 8181:80 \
mupinnn/inject-fe:latestPerintah 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.


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:latestAkses melalui http://localhost:8082 dan pastikan kembali nilai window.env sudah tersedia seperti langkah sebelumnya. Hasil akhirnya akan menjadi seperti ini:


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:latestAkses 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!


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!