Guide
Dev, UAT and prod: why your software needs three separate environments
Dev, UAT and prod are three separate copies of the same application: dev is where developers build and break things, UAT (user acceptance testing) is where you check a finished change with realistic data before customers see it, and prod (production) is the live version your customers use. Keeping them apart, with their own data, keys and addresses, is the simplest way to stop a test from reaching a real customer or a half-finished feature from breaking your business.
The three environments in one table
Some teams add a preview per branch: a temporary address showing one change in isolation, so it can be reviewed before it joins the others in dev.
| Dev | UAT | Prod | |
|---|---|---|---|
| Who uses it | Developers | You, your team, a few test users | Your customers |
| What it is for | Building and trying changes | Checking a finished change before release | Running the business |
| Data | Fake or anonymised | Realistic but not live customers' data, or a controlled copy | Real data |
| Payments | Test mode | Test mode | Live mode |
| Emails and messages | Sent to the team only, or not at all | Sent to test addresses only | Sent to real customers |
| Who can release to it | Developers | Developers, after review | Named people, after UAT sign-off |
Why a small business needs this too
Separate environments sound like big-company process, but the risks are the same at any size. A developer testing a reminder email on the live database writes to real customers. A change that works on a laptop fails with real data at 9 a.m. on Monday. A test payment goes through with a real card. Each of these is avoided by one rule: nothing is tried for the first time in prod.
Payment providers build this separation in. Stripe, for example, provides sandboxes and test API keys: test transactions don't move funds, live payments require live keys, and Stripe asks you not to store keys in source code and not to test in live mode with real card details. The same idea applies to every external service your app uses.
What must differ between environments
If your app runs with Docker Compose, Docker's own guide to production suggests keeping the shared setup in one file and the production-only changes (different ports, environment variables, a restart policy, no code mounted from outside the container) in a separate file such as compose.production.yaml, applied on top with docker compose -f. The same pattern works for a UAT file.
- Addresses: each environment has its own web address, so nobody confuses them.
- Data: separate databases. Prod data is copied to UAT only when needed, and anonymised where it contains personal data.
- Keys and secrets: separate API keys per environment (test keys outside prod), stored outside the code.
- Outgoing messages: emails, SMS and WhatsApp from dev and UAT go to test recipients only.
- Settings: logging level, error reporting, external service addresses.
- Access: fewer people can release to prod than to dev.
How a change moves from dev to prod
Tooling supports these gates. GitHub Actions environments, for instance, can require approval by named reviewers before a job deploys, restrict which branches may deploy to an environment, and hold secrets that are only available to jobs using that environment once its protection rules have passed.
- A developer builds the change on a branch and checks it in a preview or in dev.
- Automated checks run: build, tests, and a health check of the new version.
- The change is released to UAT. You or your team test it with realistic scenarios and sign it off.
- The same build is released to prod by a named person, ideally at a quiet time.
- Health checks confirm prod is fine; if not, the previous version is restored.
Common mistakes
- One database for everything, so a test in dev changes real orders.
- Live payment keys in UAT, or test keys accidentally left in prod.
- Building on the production server, so a failed build takes the site down.
- Fixing directly in prod “just this once”, then losing the fix at the next release.
- Real customer data copied to dev without anonymisation.
- No way back: a release without a previous version ready to restore.
How we ship at Takat
Every project we build has dev, UAT and prod environments, with a preview address per branch. Builds run on GitHub Actions, never on the production server; apps run in Docker Compose with health checks; servers take daily snapshots; and a release that fails its health checks is rolled back automatically. A safari quote assistant we built for a travel company in Southern Africa, for example, runs with separate dev, UAT and prod environments. See Custom software and Hosting & DevOps.
Questions we get
What does UAT stand for?
User acceptance testing: the environment where the business, not the developer, checks that a change does what it should before it reaches customers.
Can UAT use a copy of my real data?
Sometimes, when realistic data is needed. Personal data should then be anonymised or limited, and outgoing messages from UAT must go to test recipients only.
Do I need three servers?
Not necessarily. Environments can share a server if they are properly separated: their own addresses, databases, keys and settings. What matters is separation, not the number of machines.
Why not just test carefully in production?
Because some mistakes cannot be undone there: an email sent to customers, a real payment, a corrupted order. Payment providers such as Stripe provide test modes precisely so that tests don't move real funds.
Sources
- GitHub Docs: Managing environments for deployment (checked 2026-10-06)
- Stripe Docs: Testing (sandboxes and test keys) (checked 2026-10-06)
- Docker Docs: Use Compose in production (checked 2026-10-06)