Separate dev and prod environments should mean separate AWS accounts, not a naming convention. If a mistake in dev can physically reach prod, they are not separate, however carefully the resources are tagged. The boundary that works is the account boundary, because it is enforced by AWS rather than by everyone remembering to be careful.
This post covers what “separate” should actually mean, three concrete ways shared environments go wrong, how deploys to each should differ, when to add the second account, and what it costs. The short version of the last one is: nothing.
What “separate” should mean
Separate means a different AWS account, with its own credentials, its own IAM, its own bill, and its own service quotas. Anything less is a label.
There are three common ways people try to separate environments:
| Approach | What separates them | What can cross the line |
|---|---|---|
Naming and tags (myapp-dev-db, env=prod) | A string | Everything. Any role with access to the account can touch both. |
| Separate VPCs or regions in one account | Network placement | IAM, billing, quotas, and any CLI call with the wrong flag. |
| Separate AWS accounts | The account itself | Only what you deliberately allow across accounts, which by default is nothing. |
The first two are better than nothing, but they share a weakness: the thing keeping dev out of prod is a person or a policy getting it right every time. The account boundary does not need anyone to get anything right. A credential for the dev account cannot act in the prod account because, as far as AWS is concerned, it does not exist there.
This is the same logic as giving each project its own account, applied one level down. If you have read why every project should have its own AWS account, the argument will feel familiar: isolation that is structural beats isolation that is procedural.
What goes wrong when they share
When dev and prod share an account, three things eventually happen: a dev operation hits a prod resource, a permission change meant for dev applies to prod, or a dev experiment runs up a bill that lands next to prod’s. Here is each one in enough detail to recognise.
The wrong table
Someone runs a cleanup script against dev. It lists DynamoDB tables with a prefix, finds myapp-users instead of myapp-dev-users because the prefix was wider than intended, and deletes it. The script worked exactly as written. The table it deleted was simply reachable.
In a separate account, the script lists tables in the dev account and the prod table is not in the list. There is nothing to get wrong.
The permission that applied everywhere
A developer needs broader access to debug something in dev, so an IAM policy gets widened. The policy is attached to a role that, because there is one account, also has access to prod resources. Weeks later, an AI coding agent or a tired human uses that role for something routine and the broad permission is what makes the mistake possible.
In a separate account, widening dev access widens dev access. Prod IAM is untouched because it is a different IAM.
The experiment on the same invoice
A load test, a forgotten EC2 instance, a Lambda in a retry loop. The bill arrives, and it is one bill, so there is no quick way to tell whether the number is prod traffic or a dev mistake. Working it out means tags, Cost Explorer filters, and an afternoon.
In a separate account, the dev bill is the dev bill. If it is wrong, you know which environment to look at, and a budget alert on the dev account fires without any filtering. There is more on that in how to avoid a surprise AWS bill.
None of these are rare, and none of them are caused by carelessness in the usual sense. They are caused by two things being reachable from the same place. The fix is to make them not reachable from the same place.
How deploys should differ
Dev should deploy from any branch, automatically, with no approval. Prod should deploy from main, or from a gated environment, with a different role in a different account. The two paths should not share credentials.
In practice that looks like:
- Branches deploy to dev. Push a branch, a workflow runs, the change lands on a dev URL. No review required, because nothing that matters can be hurt.
- Main deploys to prod. Merge to main after review, a workflow runs, the change lands on the real URL. If you want a human gate, a GitHub environment with required reviewers does it.
- Each path assumes its own role. The dev workflow assumes a role in the dev account whose trust policy accepts any branch. The prod workflow assumes a role in the prod account whose trust policy accepts only
refs/heads/mainor theproductionenvironment.
The roles are the enforcement. A dev workflow cannot deploy to prod, not because of a rule in a README, but because the prod account’s trust policy does not recognise it. The mechanics of those trust policies, and why they should use short-lived credentials rather than stored keys, are in keyless deploys with GitHub Actions and OIDC.
This split also matters for AI coding agents. An agent should be free to push to a branch and see its work on a dev URL, and it should hit the same gate a person would before anything reaches prod. Keeping prod behind an account boundary is what makes that gate real rather than advisory.
When to add the second account
Start with one isolated account per project. Add a prod account when there is something to protect: real users, real data, or a URL you have told people about. Before that point, the project account is dev, and there is nothing for it to be separate from.
The reason to delay is not cost, since the account is free. It is that early on, the only user is you, the data is test data, and a broken deploy is a broken deploy of nothing. A second account at that stage adds a step without removing a risk.
The reason to not delay too long is that the first real user tends to arrive quietly, and the moment you notice you have one is usually the moment after a dev mistake affected them.
The trick is to have the structure ready so that adding the account is a toggle, not a rebuild:
- The project already lives in its own account under an AWS Organization, so creating a sibling prod account is one operation, not a new signup.
- Deploys already run from GitHub with OIDC, so adding a prod path means adding a second role and a second workflow trigger, not a new deploy system.
- Infrastructure is already defined in code, so standing up prod is running the same definition against a different account.
If the project was set up this way from day one, “add prod” is an afternoon. If it was set up in one shared account with keys in secrets and resources created by hand, “add prod” is a migration. TigerSoup sets projects up the first way: each project gets its own account under an Organization, and adding separate dev and prod is one toggle later.
What it costs
A second AWS account costs nothing on its own, and the setup time is small if the first account was done properly.
- AWS accounts have no monthly fee. You pay for what runs, by the request or by the second. An empty prod account is a $0 line item.
- Always-free allowances are per account. More than 30 services include a monthly free allowance that never expires. AWS Lambda, for example, gives one million requests a month at no charge. Two accounts means two allowances. Details on what is and is not free are in the AWS Free Tier explained.
- One caveat on credits. New accounts get $100 in credits at sign-up and can earn up to $100 more, but AWS moves any account that uses Organizations onto the Paid plan, which ends credit earning. Since separate accounts under one Organization is exactly what this post recommends, treat the credits as a bonus, not a plan. The always-free allowances still apply, and the account never shuts down on you.
- Setup time. With Organizations, OIDC, and infrastructure as code already in place, the second account is a short task. Without them, it is the days of work described in the new project setup checklist, done twice.
Compare that to the cost of the three failure modes above. One deleted prod table pays for a lot of empty accounts.
Closing
Separate dev and prod environments by account, because the account boundary is the only one that does not depend on everyone being careful every time. Dev deploys from branches without ceremony, prod deploys from main through its own role in its own account, and a mistake in one cannot reach the other. Add the prod account when there is something to protect, and set things up so that adding it is a toggle.
TigerSoup does that setup for free on Mac: a project gets its own AWS account under an Organization, a private GitHub repository, and keyless deploys with production gated from the start. Adding separate dev and prod later is one toggle, and nothing it creates costs anything until you run something.