Skip to content

use case

Staging that looks like production

Staging earns its keep when it matches production closely enough to catch the bug. Same OS image, same proxy, same region, smaller box.

The point of staging

Staging exists to answer one question before customers do: does this build work on infrastructure shaped like ours? Local Docker setups miss TLS quirks, real DNS, proxy timeouts, file permissions and the cron job somebody added in 2023. A staging VM in the same London region, built from the same image as production, catches those.

Keep the differences deliberate and written down. Smaller plan, fake data, an auth wall, outbound email routed to a catch-all. Everything else should be identical, ideally built by the same scripts.

Option one: a single long-lived staging box

The simplest setup is one VM that tracks your main branch. CI deploys to it on every merge, QA tests there, and releases are promoted from the same artefact. Build it with the exact image production uses; if production runs Ubuntu 22.04, staging does too, even if a newer release is available.

Once it is configured and seeded, take a snapshot from the portal. Snapshots turn "staging is broken again" from an afternoon of archaeology into a restore and a coffee.

Option two: preview environments per branch

For teams that review UI changes, a URL per pull request is worth the setup. One VM can host many previews if each is a set of containers behind a router. Point a wildcard DNS record at the VPS:

*.preview.example.co.uk.  300  IN  A  203.0.113.10

Then let Caddy issue certificates on demand and route by subdomain to the right container:

{
    on_demand_tls {
        ask http://127.0.0.1:9000/allowed
    }
}
*.preview.example.co.uk {
    tls { on_demand }
    basicauth {
        team $2a$14$REPLACE_WITH_CADDY_HASH
    }
    reverse_proxy {http.request.host.labels.4}-web:8080
}

The ask endpoint is a tiny service that returns 200 only for branches that exist, so nobody can make you request certificates for random names. Each preview runs as a Compose project named after the branch, attached to a shared Docker network the proxy can reach.

BRANCH=pr-418
docker compose -p "$BRANCH" -f compose.preview.yml up -d --build
# tear down when the PR closes
docker compose -p "$BRANCH" down -v

Data that behaves like real data

Empty databases hide bugs. Production copies create risk. The middle path is a seed script that generates realistic volumes: thousands of orders, long names, odd Unicode, accounts in every state your code handles. Keep it in the repository and run it on every fresh environment.

If you must start from production, anonymise in a pipeline before the data lands on staging. Replace emails, names, addresses and phone numbers, and truncate tables you do not need. Treat a raw production dump on a staging server as a data protection incident waiting to happen.

Keep it private

  • Restrict ports 80 and 443 in the portal firewall to your office and VPN addresses, or put basic auth on every host.
  • Send an X-Robots-Tag: noindex header as a second line of defence.
  • Route outbound mail to a capture tool such as Mailpit. Port 25 is blocked by default on new FastNode VMs, which is exactly what you want here.
  • Use separate API keys, payment sandbox credentials and webhook secrets from production. Never share them.

Sizing and cost

SetupPlan
One staging copy of a small web appSpark or Boost
Full stack staging with database and workersTurbo
Five to ten concurrent branch previewsNitro or Warp

Staging rarely needs production's CPU, but previews multiply RAM use fast. Budget the memory of one full stack per open pull request, then add a cleanup job that removes previews for branches closed more than a day ago. On a monthly plan the bill stays flat no matter how many previews you spin up and tear down; our invoices are in US dollars rather than pounds for now.

Promote, don't rebuild

Build the artefact once in CI, deploy it to staging, test it, then deploy the same image digest to production. Rebuilding for production reintroduces the gap staging was meant to close. Pin digests, not tags, in your deploy step and the thing you tested is the thing you ship.

Questions

Should staging be the same plan as production?

Not necessarily. Match the OS image, software versions and configuration exactly, but a smaller plan is fine for functional testing. Use a production-sized VM temporarily when you need to load-test.

How do I stop search engines indexing staging?

Put the whole environment behind authentication. A robots.txt disallow or noindex header helps, but basic auth or an IP allow list in the portal firewall is the only thing that reliably keeps crawlers and the public out.

Can I reset staging to a clean state quickly?

Yes. Take a portal snapshot once staging is configured and seeded, then restore it whenever tests have left it in a mess. It takes far less time than rebuilding by hand.

Is real customer data allowed in staging?

Avoid it. Use generated or anonymised data. Staging is usually less locked down than production, and copying personal data into it widens your exposure under UK GDPR.