How deployment pipelines actually work
When you push code to Upsun, the build process compiles your binaries, runsnpm install if necessary, and generates a folder of built artifacts. We make a squashfs archive of that folder. Then that archive gets deployed in a fully read-only manner to a runtime environment behind a live URL.
The build container is isolated from the environments resources. No database access, no external APIs, no live traffic. This isolation prevents you from accidentally messing with your production or preview environments during the build phase.
The well-intentioned mistake
We implemented build-time environment variables to address a common need: frontend applications required different settings between staging and production. Different API URLs, different feature flags, that sort of thing. We used to recommend runtime configuration fetching (generate a JSON file in a mount during the deploy hook, expose it at/config/conf.json, let your frontend grab it during bootstrap). But JavaScript frameworks didn’t support this pattern well at the time. Build-time environment variables filled that gap.
Why this is a problem
Build-time environment variables create differences between environments, which undermines reliable deployments. At Upsun, we reuse build outputs whenever possible. When you merge staging to production, you get the exact same code. Not “equivalent” code. The same bytes. This approach gives you three things:- Confidence: What worked in staging will work in production
- Speed: No waiting for redundant rebuilds
- Security: You’re not re-downloading packages that might have changed (and the npm ecosystem has shown us repeatedly why this matters)