One build, many environments

Reading · 6 min · Module 4, lesson 3 of 517 min left in this module

Module 4 · ConfigurationLesson 3 of 5

Goal: Explain why the same build runs in every environment, and spot configuration that has been baked into a build.

3:50 · captions and chapters · narrated with an AI-generated voice
Transcript

Narration uses an AI-generated voice.

[00:00] Where we're going

By the end of this video, you'll be able to explain why one build should run in every environment. And you'll know how to spot configuration that's been baked into a build.

[00:10] Build, then run

Getting code live happens in two steps. First, build. That turns your code into something runnable. Dependencies get installed, code gets compiled, front-end files get bundled. The result is often a container image. Second, run. That starts the build, with this environment's configuration. And configuration belongs in that second step.

Then one build works anywhere. Development gives it the development database. Staging gives it the staging database. Production gives it the production one. Only the configuration changes.

[00:51] Why it matters

Why does this matter? Three reasons. First, what you tested is what you ship. If production gets its own build, it runs something nobody tested. A dependency may have moved on, or a build step behaved differently that day.

Second, moving a release is fast. Promoting to production means starting the same build with production values. And rolling back means starting the previous build again.

[01:19] Secrets stay out

Third, secrets stay out of the build. A build can copy your dot env file into the finished app. And a key that gets in stays there. Anyone who can download the build can find it, even after you change the key. So keep the dot env file out of the build.

[01:38] When configuration gets baked in

Now, some values are read while building, not while running. The value at that moment is copied into the output, and changing it later does nothing until you build again. The most common case is front-end code. A browser can't read your server's environment variables. So frameworks like Next.js and Vite copy chosen ones into the JavaScript at build time.

Here's the lesson's example. This front end was built with the staging API address. The built file now contains the staging URL itself. So when that build reaches production, production calls staging.

[02:19] Two honest options

It's a real trade-off, not a mistake, and you have two honest options. One, build once per environment, with each environment's values. It's simple, but each build is only tested where it's built. Two, load the settings at run time. The server hands the browser a settings file, or the front end calls a relative path like slash API. Either way, never put a secret in these variables. Every visitor downloads them.

[02:50] How to spot it

How do you spot a baked-in value? Look for three signs. A variable change had no effect until a rebuild. Searching the built files finds the value itself. Or the build step is given a value that differs by environment. Any of these means it's read at build time. Move it to run time, or build per environment on purpose.

[03:14] Recap

So, build once. Give each environment its own configuration when the build runs. And promote the build you tested, rather than building again.

Check yourself. Staging passed its tests. For production, the pipeline builds again from the same commit. What's the risk? Production runs a build nobody tested. Promote the build that passed in staging, with production configuration. Next, you'll see where these values live on ComputeSphere.

Key idea

Build your app once, then run that same build in development, staging and production, giving it different configuration in each. What you tested in staging is then exactly what reaches production.

Build, then run

Getting code live happens in two steps:

  1. Build: turn the code into something runnable. That means installing dependencies, compiling, and bundling front-end files. The result is often a container image, which Path 3 covers.
  2. Run: start the build with this environment's configuration.

Configuration belongs in step 2. Then one build works anywhere: staging gives it the staging database, production gives it the production one.

Why it matters

What you tested is what you ship. If production gets its own build, it runs something nobody tested. A dependency may have moved on, or a build step may have behaved differently that day.

Moving a release is fast. Promoting to production means starting the same build with production values, not waiting for a new one. Rolling back means starting the previous build again.

Secrets stay out of the build. A build can copy your .env into the finished app, and a key that gets in stays there. Anyone who can download the build can find it, even after you change the key in your settings. Keep .env out of the build; Path 3 shows how in lesson 3.4.4.

When configuration gets baked in

Some values are read while building, not while running. The value in effect at that moment is copied into the output, and changing it later does nothing until you build again.

The most common case is front-end code. A browser can't read your server's environment variables, so frameworks copy chosen ones into the JavaScript files at build time. In Next.js these are variables named NEXT_PUBLIC_…; in Vite, VITE_….

// Built with NEXT_PUBLIC_API_URL=https://staging-api.example.com
fetch(`${process.env.NEXT_PUBLIC_API_URL}/orders`);
// The built file contains the staging URL itself. Production would call staging.

That breaks "build once" for these values, and it's a real trade-off, not a mistake. You have two honest options:

  • Build once per environment for the front end, with each environment's values. Simple, but each build is only tested where it's built.
  • Load the settings at run time. The server hands the browser a small settings file, or the front end calls a relative path like /api so the address never changes.

Either way, never put a secret in one of these variables. Everything in them ends up in files every visitor downloads.

How to spot a baked-in value
  • A variable change had no effect until a rebuild.
  • Searching the built files (.next/, dist/) finds the value itself.
  • The build step is given a value that differs by environment.

Any of these means the value is read at build time. Either move it to run time or build per environment on purpose.

Check yourself

Staging passed its tests. For production, the pipeline builds again from the same commit. What's the risk?
You change VITE_API_URL in production settings and restart. The front end still calls the old address. Why?