🔒 Hello, this is Valentina with a premium issue of the Optivem Journal. I help Engineering Leaders & Senior Software Developers apply TDD in Legacy Code.
“Should we build once, or build per environment?”
“Should production have its own deploy workflow?”
These are the same question.
The first is about running a fresh docker build per environment — one build for QA, another for production, “because production needs different config baked in.”
The second is about writing a separate deploy workflow per environment — a deploy-qa.yml, a deploy-production.yml, each with its own steps and copy-pasted-then-edited logic, drifting apart commit by commit.
Both come from the same place: this environment is special, so it needs its own thing.
A pipeline’s entire job is to make “what we tested” and “what we shipped” the same. Every per-environment fork is a place where they quietly stop being the same.
Build once: no second build
The build is created in the Commit Stage. Every stage after that uses the same build — it gets tagged and deployed, but never rebuilt.
The temptation to rebuild happens because of configuration: “production needs the production API URL,” “QA needs the QA feature flags.”
So a second docker build happens with different build args, and now production is running a build that no test ever ran against. The green checkmark from QA is for a different build than the one customers actually get.
The build has no environment. Configuration is injected at deploy time — environment variables, mounted config, secrets from the environment — into the same build. That’s what allows the same build to move from Acceptance to QA to Production unchanged.
⚡Want to stop stressful releases, late-night fixes, and broken deployments?
Limited spots. Register now - 100 EUR off with code DISCOUNT_100
Deploy once: same script, different environment
Here’s the same deploy step running in three different environments.
Acceptance:
- name: Deploy
uses: acme/actions/deploy@v1
with:
environment: acceptance
version: ${{ inputs.version }} # the system version, e.g. v2.5.0-rc.3
image-urls: |
ghcr.io/acme/shop/frontend
ghcr.io/acme/shop/backend
