Dev vs Staging vs Production in AWS: Differences, Setup, and Best Practices
Dev, staging, and production are three environments that each serve a different role in your release process. Dev is where developers build and test individual changes. Staging is where those changes run together against production-like conditions. Production is where real users are. The safest AWS setup puts each environment in a separate account under AWS Organizations, giving you clean per-environment billing and isolation that VPC separation alone cannot provide.
What dev, staging, and production are#
Most teams run three environments. Here is what each one is for:
Dev is for individual developers testing their work. A developer building a new feature deploys it to dev to verify it works end-to-end before opening a pull request. Dev breaks often, and that is fine. The cost of a broken dev environment is one developer’s afternoon.
Staging is the final gate before production. It runs the same code that is about to go live against production-like data volumes and infrastructure. Staging catches the bugs that dev misses because dev is too small or too simplified to surface them.
Production serves real users. Failures here mean lost revenue, data incidents, or damaged user trust. Every change to production should have already passed through staging.
Think of it like a restaurant. Dev is the prep kitchen where chefs experiment freely: things spill, recipes get scrapped, and that is the point. Staging is the chef’s table the night before opening, where a full meal is served to trusted guests so problems can still be caught without cost. Production is the dining room with paying customers. You do not experiment there.
| Dimension | Dev | Staging | Production |
|---|---|---|---|
| Purpose | Individual feature testing | Pre-release validation | Serving real users |
| Who uses it | Developers | Developers, QA, PMs | Real users |
| Data | Synthetic test data | Masked, production-like data | Real user data |
| Traffic / load | Low, intermittent | Moderate, realistic patterns | Full production load |
| Access level | Developer full access | Developer read-only | Pipeline only (no direct writes) |
| Cost expectation | Low (can be minimal) | Medium (reduced size) | Full cost |
| Failure impact | Developer productivity | Release confidence | Real users, revenue, trust |
How the environments work together#
Code moves in one direction: dev to staging to production. This is called the promotion flow.
A developer finishes a feature, tests it in dev, and opens a pull request. Automated tests run. When the PR merges, the CI/CD pipeline builds the artifact and deploys it to staging automatically. See Managing Environments in CI/CD Pipelines for how to structure CodePipeline promotion stages with cross-account deployment roles.
In staging, the full application runs together against realistic conditions. QA runs final checks. For significant releases, a human approval gate in CodePipeline requires an authorized person to approve before the deployment proceeds to production.
Production deployments typically use blue-green or canary strategies to reduce risk. The pipeline handles the rollout. No developer logs into the production console to run commands manually.
Recommended AWS setup#
The recommended pattern is one AWS account per environment, grouped under AWS Organizations.
AWS Organization
├── Management Account (billing, Organizations, IAM Identity Center)
├── Dev Account (123456789001)
├── Staging Account (123456789002)
├── Production Account (123456789003)
└── Tools Account (123456789004) ← CI/CD pipelines live here
Account-level separation provides guarantees that VPC separation cannot. A dev IAM role literally cannot call production AWS APIs because it exists in a completely different account.
Access control: Set up AWS IAM Identity Center (formerly SSO) so developers sign in once and get environment-appropriate permissions automatically:
- Dev account: full developer access
- Staging account: read-only (writes through the pipeline only)
- Production account: read-only or no access (all changes through the pipeline)
Apply the principle of least privilege: give each person the minimum access they need in each environment.
With IAM Identity Center, create a DeveloperAccess permission set for dev accounts and a ReadOnlyAccess permission set for staging and production. Developers get the right permissions automatically at sign-in. No manual IAM user management per environment.
Guardrails: Apply Service Control Policies at the OU level to enforce environment-specific limits. A production SCP can require MFA for all API calls, prevent CloudTrail deletion, and block disabling of GuardDuty. These guardrails apply to everyone in the account, including administrators, and cannot be bypassed by IAM policies.
Define all of this in Infrastructure as Code from the start. Manual account configuration drifts quickly as teams grow.
How staging should match production#
“Staging mirrors production” means the same services in the same architectural pattern, not the same instance sizes. If production uses an ALB, ECS Fargate with auto-scaling, RDS Multi-AZ, and ElastiCache, staging needs all of those same services. Just smaller.
Architecture parity means matching which services you use and how they connect. Size parity means matching instance types and capacity. Staging needs architecture parity. It does not need size parity. Skipping services to save cost is the mistake; using smaller instance types is perfectly fine.
| Resource | Dev | Staging | Production |
|---|---|---|---|
| ECS Fargate | 0.5 vCPU / 1 GB, count=1 | 1 vCPU / 2 GB, count=2 | 2 vCPU / 4 GB, count=4–20 (auto-scaling) |
| RDS PostgreSQL | db.t3.micro, single-AZ | db.t3.medium, single-AZ | db.r6g.xlarge, Multi-AZ |
| ElastiCache | cache.t3.micro, 1 node | cache.t3.small, 1 node | cache.r6g.large, 2 nodes |
| ALB | Yes (same config) | Yes (same config) | Yes (same config) |
| Auto Scaling | Fixed count | Yes (limited range) | Yes (production range) |
If production uses PostgreSQL, every environment must use PostgreSQL. Not SQLite. If production auto-scales, staging needs auto-scaling configured even if it rarely triggers. These architectural gaps are where “works in staging, broken in prod” bugs hide.
Use separate Terraform state files per environment so dev, staging, and production infrastructure changes stay independent.
Managing data safely across environments#
Dev should always use synthetic data: records generated for testing that contain no real user information. Integration tests seed the database with known records and clean up after themselves.
Staging requires production-like data to be useful, but copying raw production data into staging carries compliance risk under GDPR, CCPA, HIPAA, and similar regulations.
Restoring a production database snapshot to staging without masking means every developer with staging access can query real user emails, phone numbers, and payment data. Always mask before restoring, and do it before granting any access to the restored instance.
The recommended approach: restore a production snapshot to staging, then run a masking script.
# 1. Snapshot production
aws rds create-db-snapshot \
--db-instance-identifier prod-postgres \
--db-snapshot-identifier staging-refresh-$(date +%Y%m%d)
aws rds wait db-snapshot-completed \
--db-snapshot-identifier staging-refresh-$(date +%Y%m%d)
# 2. Restore to staging
aws rds restore-db-instance-from-db-snapshot \
--db-instance-identifier staging-postgres-temp \
--db-snapshot-identifier staging-refresh-$(date +%Y%m%d)
# 3. Mask PII before granting access
psql $STAGING_DB_URL -f mask-pii.sql
The masking script replaces real emails with user_123@example.com, real names with generic values, phone numbers with 555-0100, and payment data with test card numbers. The data structure and volume stay realistic; the actual values are not real user data.
Verify your specific legal and compliance requirements before restoring production snapshots to any non-production environment. In regulated industries such as healthcare, synthetic data generation may be the only compliant approach.
Store environment credentials separately. Use Secrets Manager or Parameter Store per environment, injected through the pipeline. Never hardcode credentials or share secrets across environments.
Managing dev cost#
Dev environments run 24/7 but are only used during business hours. Automated shutdown schedules typically cut dev costs by 60–70%.
Use Amazon EventBridge Scheduler to stop dev resources outside business hours:
# Stop dev RDS at 8pm UTC on weekdays
aws scheduler create-schedule \
--name stop-dev-rds \
--schedule-expression "cron(0 20 ? * MON-FRI *)" \
--flexible-time-window '{"Mode": "OFF"}' \
--target '{
"Arn": "arn:aws:scheduler:::aws-sdk:rds:stopDBInstance",
"RoleArn": "arn:aws:iam::ACCOUNT:role/SchedulerRole",
"Input": "{\"DbInstanceIdentifier\": \"dev-postgres\"}"
}'
For ECS Fargate services, scale the desired count to 0. For EC2 workloads, stop the instances. Define these schedules in Terraform alongside the resources they manage, not manually in the console.
Tag every resource with an Environment tag and use Cost Explorer reports to see how much dev, staging, and production each cost per month:
tags = {
Environment = var.environment # "dev", "staging", or "production"
Application = "my-app"
ManagedBy = "terraform"
Team = "platform"
}
Tag-based IAM conditions can also prevent a dev pipeline role from accidentally deploying to a resource tagged as production.
When to use this setup#
Starting out: A small team prototyping or running a low-traffic product can begin with two environments in the same account, separated by naming conventions and IAM policies. This is simpler to manage and costs less. It works until it doesn’t.
When separate accounts become essential:
- You hire a second or third developer and access control becomes important
- A security audit or compliance review requires account-level isolation (SOC 2, ISO 27001, HIPAA)
- A staging incident nearly touched production data
- Your cloud bill is large enough that per-environment cost visibility matters
- Your team is large enough that someone could accidentally misconfigure production
For most teams building real products, the one-time cost of setting up separate accounts is worth it. Account setup takes a few hours. Cleaning up an environment bleed incident takes much longer.
See Securing Production Systems for production hardening patterns that complement the account-separation model.
Staging vs test vs UAT#
These terms are often used interchangeably, but they refer to distinct phases:
| Environment | When it runs | Who uses it | What it validates |
|---|---|---|---|
| Test / QA | After each commit or PR | Automated tests, QA engineers | Individual features, regression tests |
| Staging | Before each production release | Developers, QA, product managers | Full system in production-like conditions |
| UAT | After staging sign-off | Business stakeholders, end users | Business requirements and acceptance criteria |
| Production | Always | Real users | n/a |
In practice, many teams run UAT on staging. That is fine as long as staging is locked and stable during UAT, not actively receiving deployments from other developers at the same time.
Common mistakes#
- Different database engine in dev than production. SQLite in dev and PostgreSQL in production hides bugs around query syntax, transaction behavior, and constraint enforcement. Use the same engine in every environment.
- Skipping services in staging that production uses. If production depends on ElastiCache for sessions and staging skips it, cache-related bugs won’t surface until production. Architecture parity is the whole point of staging.
- Dev resources running 24/7. An always-on dev RDS instance costs nearly as much as production for zero benefit outside business hours. Automate shutdown from the start.
- No environment tagging. Without a consistent Environment tag, cost allocation is a guessing game and any automation that targets resources by environment becomes fragile.
- Raw production data in non-production environments. Even in staging, real user data is subject to data protection regulations. Mask it before restoring.
- Developers making direct changes to production. Direct console or CLI access to production bypasses the audit trail, approval gates, and rollback mechanisms the pipeline provides.
- Shared Terraform state across environments. A single state file for all environments means a mistake in a dev change can corrupt the production state. Keep state per environment.
Frequently asked questions
Do I need a separate AWS account for each environment?
It is the recommended pattern for teams beyond early exploration. Account-level separation means a dev IAM role literally cannot reach production APIs because it exists in a different account. VPC-level separation can achieve similar isolation, but account separation is enforced structurally by AWS rather than by policies you have to maintain.
Should staging be identical to production?
Staging should use the same architecture: same services, same deployment patterns, same security configuration. Instance sizes can be smaller to reduce cost. What staging must not do is skip services entirely. If production uses Auto Scaling, ElastiCache, and a Multi-AZ database, staging needs all three in some form.
Can dev be cheaper than production?
Yes, significantly. Dev uses small instance types, runs at low traffic volumes, and can shut down overnight and on weekends using EventBridge Scheduler. Teams routinely cut dev costs by 60-70% with automated shutdown schedules.
Should developers have direct access to staging or production?
Developers typically get read-only access to staging for debugging, and no direct write access to production. Production changes should flow through the CI/CD pipeline with approval gates. Emergency break-glass procedures can grant temporary elevated access with a full audit trail.
What is the difference between staging, test, and UAT?
Test environments run automated test suites against feature branches before merge. Staging runs the full application in production-like conditions before release. UAT is usually staging reviewed by business stakeholders to confirm the release meets requirements before it goes live.