Skip to main content
ANVISoftware Solutions
Lesson 14 of 17Advanced16 min

Environments and Approvals

By the end of this lesson

Promote a build through environments with appropriate gates.

An environment is a complete running copy of your system with its own configuration and its own data. Three is the common arrangement: development, where changes land continuously; staging, where a build is exercised before anyone commits to it; and production, where real users and real data are.

The value is not in having three copies. It is in one artifact moving through them in order, gathering evidence at each step, with the checks between steps chosen deliberately. An environment that nobody uses to make a decision is infrastructure you are paying for and learning nothing from.

The terms, used consistently for the rest of this lesson:

Environment
A running copy of the system: its compute, its database, its storage, its configuration and its secrets. Production is one of them, and treating it as a different kind of thing is how the others stop resembling it.
Promotion
Deploying the artifact that already ran in one environment to the next, unchanged. If anything is rebuilt, it is not a promotion, and the evidence from the previous environment no longer applies.
Gate
A condition between two environments. Automated gates are checks — smoke tests, a health endpoint, an error budget. Manual gates are approvals. Both belong, in different places.
Smoke test
A small set of checks run against a deployed environment to confirm the obvious things work: the service answers, it can reach its database, one representative request succeeds end to end. Minutes, not hours.
Configuration drift
Environments diverging over time because a change was made in one and not the others. Usually discovered when a deployment that worked everywhere else fails in production.
Synthetic data
Generated records that resemble real ones in shape and volume without being real. This is how staging gets a realistic dataset without holding a copy of personal data.

The promotion path for the employees API, and what each step is actually for:

  1. Build once, on a merge to the shared branch

    One artifact, tagged with the commit, identified by digest. Every environment below deploys this exact artifact. Nothing downstream compiles anything.

  2. Deploy to development automatically

    No gate. This environment exists to catch the failures that only appear once something is deployed: a missing configuration value, a dependency injection registration that only fails at run time, a container that will not start. Those are worth finding within minutes of the merge.

  3. Promote to staging automatically, then run smoke tests

    Migrations run here first, against a dataset that resembles production. The smoke tests are the gate, and they must be able to fail the pipeline — a check whose result nobody acts on is decoration.

  4. Hold for approval before production

    A named group approves, and the approval request shows what is being promoted: the version, the commit, and what changed since the last production release. An approval without that information is a button, not a decision.

  5. Deploy to production, then verify

    The same artifact, the same deployment mechanism used in staging, followed by the same smoke tests. Using a different mechanism for production means the one you rehearsed is not the one that matters.

  6. Record what landed where

    Which version is in which environment, and when it arrived, somewhere a person can read during an incident. Reconstructing this from pipeline history takes longer than you will want to spend.

Staging earns its cost by catching problems before users meet them. It is worth being honest about which problems it can catch, because assuming it catches all of them is how confident teams get surprised.

 Staging can tell youOnly production will tell you
Start-up and configurationWhether the artifact starts, reads its configuration, and reaches its dependenciesLittle more, as long as staging is configured the same way
MigrationsWhether a migration applies cleanly, and roughly how long it takes on a similar datasetHow long it takes on the real one, and which rows break assumptions your generated data never had
PerformanceWhether a query is catastrophically slow, if the data volume is comparableBehaviour at real concurrency, with real cache states and real contention
Data edge casesWhat you thought to generateNames with apostrophes, records from 1998, the employee with no manager, and the duplicate nobody knew about
IntegrationsThat your code speaks the protocol correctly, usually against a sandboxHow the real third party behaves under load, and what it returns on a bad day
User behaviourNothing. Staging has no usersEverything: which paths are hot, what people do that you did not design for
What makes it usefulSame artifact, same deployment mechanism, comparable data volume, equivalent configurationMonitoring good enough to notice quickly, and a rollback you have rehearsed
One artifact, three environments, one approval
YAML
jobs:
  build:
    # Produces the artifact and writes its digest to digest.txt.
    uses: ./.github/workflows/build-and-test.yml

  deploy-dev:
    needs: build
    environment: development        # no protection rules: this one is automatic
    runs-on: ubuntu-latest
    steps:
      - name: Deploy the built digest
        run: ./scripts/deploy.sh employees-api-dev

  deploy-staging:
    needs: deploy-dev
    environment: staging
    runs-on: ubuntu-latest
    steps:
      - name: Apply migrations, then deploy
        run: ./scripts/migrate.sh staging && ./scripts/deploy.sh employees-api-staging
      - name: Smoke test
        run: ./scripts/smoke.sh https://employees-api-staging.internal.example

  deploy-production:
    needs: deploy-staging
    environment: production        # required reviewers are configured on the environment
    runs-on: ubuntu-latest
    steps:
      - name: Apply migrations, then deploy
        run: ./scripts/migrate.sh production && ./scripts/deploy.sh employees-api-prod
      - name: Smoke test
        run: ./scripts/smoke.sh https://employees-api.example.net
  • The needs chain is the promotion path. Production cannot run until staging has succeeded, so the order is enforced by the pipeline rather than by someone remembering it.
  • Every deploy job runs the same script against a different target, and each one uses the digest produced by the single build job. Nothing in these jobs builds anything, which is what makes the word promotion accurate.
  • The approval lives on the environment, not in this file. That separation matters: a reviewer requirement stored outside the repository cannot be removed by editing a pull request, so the gate is not bypassable by the change it is gating.
  • Environment-scoped credentials come from the same place. The staging job cannot obtain production credentials, because they are attached to an environment it never enters.
  • Migrations run as their own step before the deployment rather than from application start-up, so they execute once instead of racing across replicas. They run in staging first, which is the only rehearsal a migration gets.
  • The smoke test after production is not optional politeness. Deploying and then not checking means the pipeline's last word on a bad release is success.

What may differ between environments, and what must not:

  • Must be identical: the artifact. Same digest in development, staging and production. This is the rule the whole arrangement rests on
  • Must be identical in shape: the deployment mechanism, the migration process, the platform service types, and the runtime version. If production uses a different mechanism, staging rehearsed the wrong thing
  • May differ: scale. Two replicas in staging and eight in production is reasonable and honest, as long as you know that scale-related faults will not show up before production
  • Must differ: secrets. Each environment gets its own credentials, its own store, and access granted separately. A shared secret means a development machine holds production access
  • Must differ: data. Production data does not belong in staging. Use generated records that match production in volume and in awkwardness, and keep real personal data in one place
  • May differ: third-party integrations, where a sandbox exists. Note what the sandbox does not simulate — rate limits and slow responses are the usual omissions
  • Should not differ quietly: anything changed directly in one environment's portal. Make the change in code and let it apply everywhere, or the environments diverge and the divergence is invisible until it breaks a deployment

Summary

  • An environment is a full running copy with its own configuration, secrets and data, and production is one of them
  • One artifact moves through development, staging and production unchanged; if anything is rebuilt, the earlier evidence no longer applies
  • Gates belong between environments: automated checks where a machine can decide, approvals where a person genuinely might decline
  • Staging only catches what it resembles, so data volume and configuration parity decide how much it is worth
  • Secrets and data must differ per environment; the artifact, the deployment mechanism and the migration process must not

Practice

Attempt each one before opening the solution. Getting it wrong first is how the idea sticks.

Think about it

What did staging fail to catch?

A release passes every check in staging and causes timeouts in production within ten minutes. Staging runs the same artifact and the same migrations, with 5,000 employee records. Production has 1.8 million.

List the candidate causes that staging could not have found, and say which single change to staging would have found the most of them.

Show solution

Candidates: a query with no usable index, which is instant on 5,000 rows; a different query plan chosen by the database once the table is large; a migration that locks a big table for minutes; pagination that loads far more rows than intended; a cache that is effective at small volume and thrashes at large; and connection pool exhaustion at production concurrency.

Most of that list is data volume, not concurrency. Loading staging with generated data at production scale would surface the index, the plan change, the migration duration and the pagination fault — four of the six — without needing production traffic.

Concurrency effects remain. Pool exhaustion and cache contention need load, so either a load test against staging or a careful canary in production is the honest answer for those.

It is worth naming the decision this exercise exposes. A production-sized staging database costs real money every month, and it is the difference between a staging environment that can catch this class of fault and one that cannot. Choosing the cheaper option is legitimate; believing it protects you is not.

Challenge

Design the gates for one system

For a system you know, write the promotion path: which environments, what gate sits between each pair, and who or what decides. For every manual approval, state what the approver is shown and what would make them decline.

Show solution

A defensible answer for a small team: automatic to development with no gate; automatic to staging with migrations and smoke tests as the gate; manual approval to production, shown the commit range, the migrations included, and the result of the staging smoke run.

The question about declining is the one that does the work. If you cannot describe a realistic reason to decline, the approval is not a gate — it is a delay, and removing it would lose nothing. Common genuine reasons: a migration that needs a quiet window, a release that has to follow a customer communication, or a change that a specific person needs to see first.

Automated gates are usually undersold. A smoke test that fails the pipeline catches more real problems than an approval does, because it looks at the deployed system rather than at a description of it. Prefer adding an automated check over adding another human step.

There is a defensible opposite answer too. A team with strong automated checks, fast rollback and good monitoring can remove the production approval and deploy continuously. That is a higher bar than it looks and it is a reasonable destination, as long as the checks that replace the approval exist first.

Saved in this browser only.