TL;DR

Our DevOps team was told we needed to rebuild the frontend for each customer because API URLs were hardcoded at build time. We refused to accept that limitation. By switching from process.env (build-time) to window.__ENV__ (runtime configuration), we achieved a “build once, deploy many” model. The lesson: keep questioning the obvious and challenge the status quo.


The Problem

All was fine until our DevOps team was asked to deploy the frontend and backend images of a Node.js application for another customer.

For the backend, things were straightforward: we could mount updated config files at runtime and the service would pick up the new values. But the frontend was different.

The developers told us bluntly:

“You’ll need to rebuild the frontend for each customer.”

That was unacceptable.

When we looked closer, we saw the problem: the API URL was hardcoded into the frontend build artifacts at multiple places.

The Immediate (But Imperfect) Fix

Our quick workaround was to create an entrypoint script.

This script would run every time the container started, scanning files and replacing old values with the new environment variables.

It worked, but at a cost:

  • Containers took longer to start
  • We added unnecessary complexity
  • We were treating symptoms, not the root cause

We knew we needed something better.

The Turning Point

Then one day, one of our DevOps engineers stumbled upon an idea: use window.__ENV__ for runtime configuration in React.

When we investigated with the development team, we found the real root of the issue: the code was using process.env, which is resolved at build time. That is why the API URL kept getting baked directly into the bundle.

With window.__ENV__, we could inject values at runtime instead. This was the breakthrough we needed.

The Technical Solution

The change involved:

  1. Creating a runtime config file that gets loaded before the React app initializes
  2. Replacing process.env.REACT_APP_* references with window.__ENV__.*
  3. Injecting the config via a script tag or mounted file at container startup
1
2
3
4
5
// Before: Build-time configuration
const apiUrl = process.env.REACT_APP_API_URL;

// After: Runtime configuration
const apiUrl = window.__ENV__.API_URL;

The config file could now be mounted or generated at runtime:

1
2
3
4
5
// env-config.js (mounted at runtime)
window.__ENV__ = {
  API_URL: "https://customer-specific-api.example.com",
  // other runtime configs
};

The Dare to Developers

We went back to the developers and proposed the switch.

At first, there was hesitation. Changing from process.env to window.__ENV__ meant refactoring parts of the codebase. But after discussions and a few experiments, the developers agreed.

We dared them to try—and they rose to the challenge.

The Breakthrough

Once implemented, the difference was dramatic:

  • One frontend image for all customers – no rebuilds required
  • Unified deployment model – both frontend and backend could now read configs at runtime
  • Faster container startup – no more heavy entrypoint hacks
  • Operational agility – deployments became leaner and more flexible

What had once felt like a roadblock became a smooth, repeatable flow.

The Lesson

The real lesson was not just about process.env vs window.__ENV__.

It was about mindset.

As DevOps engineers, we could have accepted the limitation and kept rebuilding. But we did not. We kept searching, questioned the obvious, and challenged the status quo.

That is the DevOps mindset:

  • Keep your mind open to possibilities. The solution often exists—you just have not found it yet.
  • Challenge “that’s how it’s always done.” Legacy decisions are not always the right decisions.
  • Align development and operations to find the simplest, most scalable solution.

This mindset turned a frustrating rebuild cycle into a “build once, deploy many” model—and that is the kind of thinking that drives true innovation.

When to Use This Pattern

This approach works well when:

  • You have multi-tenant deployments with customer-specific configurations
  • You want to use the same container image across environments (dev, staging, production)
  • Your configuration values are not sensitive secrets (for secrets, use proper secret management)
  • You need to change configuration without rebuilding

It may not be suitable when:

  • Configuration values are highly sensitive and should not be in browser-accessible files
  • You have complex build-time optimizations that depend on environment variables
  • Your framework has better built-in solutions for runtime configuration

Have you faced similar challenges with frontend configuration in containerized environments? What solutions worked for your team?


Editorial Note

This article was originally published on LinkedIn and has been revised for uk4.in.

Original publication date: 2025-10-18

Original link: https://www.linkedin.com/pulse/when-devops-dared-developers-uttam-jaiswal-eojmc/