Three environments are not three names: each answers a different question and is best separated into its own AWS account, because the account is the isolation boundary for identity and access.
1. What each environment is#
An environment is a copy of your application with its own configuration, data and access. Almost every team uses three: development (dev), staging and production. The names matter less than the question each one answers.
| Environment | Question it answers | Data | Who has access | How it is deployed |
|---|---|---|---|---|
| Development | Does my change work? | Synthetic or test | Whoever develops | From your machine or automatic on every branch |
| Staging | Does it work like production? | Reduced and anonymized copy | The team and QA | With the same process as production, after each merge |
| Production | Do customers use it? | Real | Users; minimal human access | With approval and a rollback plan |
Development is where you try your change; staging is the dress rehearsal with a configuration as close as possible to production; production is what customers use. If two environments answer the same question, one is redundant.
2. How they really differ#
Beyond the name, four things set one environment apart from another.
- Data: dev uses synthetic data; staging, a reduced and anonymized copy; production, real data. Never copy real personal data into staging without anonymizing it.
- Access: the more real users, the less human access. In production you go in through the deployment process, not the console.
- Size and cost: staging is usually a smaller version of production with the same infrastructure code and different variables.
- Alerts: a failure in dev wakes nobody; one in production does. Configure alerts per environment.
The useful rule is that staging should fail for the same reasons as production: if its configuration differs, what you see there does not predict what will happen.
3. How to separate them on AWS#
According to AWS's official guide on organizing with multiple accounts, an AWS account acts as an isolation boundary for identity and access management, and by default there is no access between accounts: if you use separate accounts for production and non-production, they stay isolated unless you enable communication. AWS charges for resource usage, not for the number of accounts, so separating adds no fee.
| Option | Isolation | When it makes sense |
|---|---|---|
| One AWS account per environment | The strongest: no access between accounts unless you allow it | Production with real data or compliance requirements |
| Separate VPCs in one account | Network isolation, but shared permissions and limits | Small teams starting out; move to accounts as risk grows |
| Only tags and names | None really: a permissions mistake reaches everything | Only for disposable tests; avoid with real data |
That makes one account per environment the strongest option. The table compares the usual alternatives. If you already have a single account, Codifly's guide on separating environments on AWS explains the move to accounts with organizational units.
Go deeperSeparating environments (dev, staging, production) on AWS
Documentation: AWS: organizing your environment using multiple accounts ↗ · AWS Organizations: introduction ↗ · AWS: Well-Architected Framework ↗
4. A rule to avoid touching production by mistake#
The costliest mistakes happen when you run the right command in the wrong environment. A small check before each action prevents it: verify that the current account is the environment's and require confirmation for destructive actions in production.
ACCOUNTS = {"dev": "111111111111", "staging": "222222222222", "production": "333333333333"} # example IDs
def validate(environment: str, current_account: str, destructive: bool, confirmed: bool) -> str:
expected = ACCOUNTS.get(environment)
if expected is None:
return f"BLOCKED: unknown environment ({environment})"
if current_account != expected:
return f"BLOCKED: account {current_account} is not the one for {environment}"
if environment == "production" and destructive and not confirmed:
return "BLOCKED: in production, destructive actions require confirmation"
return "OK"
print("1:", validate("staging", "222222222222", True, False))
print("2:", validate("production", "222222222222", False, False))
print("3:", validate("production", "333333333333", True, False))
print("4:", validate("production", "333333333333", True, True))Call a function like this from your deployment scripts and your pipeline. It does not replace IAM permissions, which are the real defense, but it catches the human error before it reaches the API.
5. How to promote changes between environments#
A change should travel from dev to staging to production without being rebuilt.
- Build the artifact once and promote it: what you tested in staging is the same thing that reaches production.
- Use the same infrastructure code in all environments and change only the variables.
- Test database migrations in staging with a volume similar to the real one.
- Require approval before production and have a rollback plan ready.
- Store secrets per environment in a secrets manager, never in the repository.
Go deeperEnvironment variables and DATABASE_URL in production: a full guide
6. Checklist#
Before treating your environments as separate, check the following.
- Each environment has its own account or, at minimum, its own VPC and its own roles.
- Staging uses anonymized data and production is the only one with real data.
- Deployment to production requires approval and leaves a record.
- Alerts and budgets are configured per environment.
- Nobody uses production credentials from their machine for daily work.
Go deeperWhat is the best organizational structure for your AWS account?
Sources and scope
Documentation checked on October 17, 2026. Examples and decision criteria are editorial proposals; adapt them to your application's contract and validate them in an authorized test environment.