Dev vs Staging vs Production: Environment Strategy
Dev, staging, and production are not just labels on identical infrastructure. Each environment serves a different purpose in the delivery pipeline, carries different risk tolerance, handles different data, and warrants different access controls. Getting these distinctions right prevents a staging experiment from corrupting production data and a production incident from being caused by a change that skipped validation.
What each environment is actually for
Development (dev) is where individual developers test their changes before creating a pull request. It is frequently broken, frequently reset, and never used for customer-facing validation. Speed matters more than stability in dev — a developer should be able to deploy their branch, test it, and destroy it within minutes. Dev data is synthetic or a small anonymized subset. Dev exists for fast feedback, not quality assurance.
Staging is a stable environment that mirrors production in architecture and configuration. Integration tests, QA, user acceptance testing (UAT), and performance testing happen here. Staging must not be broken during business hours — if a deployment breaks staging, it is treated like a production incident in terms of urgency to fix. Staging data is production-like in shape but not real customer data (anonymized or generated). Staging catches the class of bugs that only appear when all services run together: integration failures, configuration errors, and environment-specific regressions.
Production is live, customer-facing infrastructure. No experimental changes, no direct developer write access, and all changes deployed through a controlled pipeline with approval gates. Production is the environment you are protecting. Every other environment exists to give you confidence before you touch production.
What staging must match in production
The value of staging comes from parity with production. If staging is different in important ways, bugs that staging would have caught slip through. These things must be identical between staging and production:
| Property | Must match? | Notes |
|---|---|---|
| Application version / artifact | Yes, exactly | Same build artifact, not rebuilt for staging |
| Infrastructure architecture | Yes | Same services, same networking topology |
| Configuration structure | Yes (values differ) | Same keys, different values (staging endpoints vs prod) |
| Network security rules | Yes | Different NSG rules mean different attack surface |
| Operating system / runtime version | Yes | A Node.js version mismatch between environments is a bug factory |
| VM or App Service SKU size | No | Staging can be smaller for cost savings |
| Real customer data | No | Use anonymized or synthetic data in staging |
| Number of replicas / scale | No | Staging can run with fewer instances |
Infrastructure as code per environment
When you manage infrastructure with Terraform or Bicep, you need a strategy for handling multiple environments. Two common approaches:
Terraform workspaces. A single set of Terraform files, multiple workspaces (one per environment). Each workspace has its own state file. Variable values differ per workspace through .tfvars files. Simple to implement, but all environments share the same code structure. A change to the Terraform code affects all environments on the next apply.
# Create and use the staging workspace
terraform workspace new staging
terraform workspace select staging
terraform apply -var-file=staging.tfvars
# Switch to production
terraform workspace select production
terraform apply -var-file=production.tfvarsSeparate state files with separate backends. The Terraform code is the same, but each environment uses a different backend configuration (different storage account container) for its state. This gives more isolation — a corrupted staging state does not affect production state. Many teams store state in separate storage accounts in separate subscriptions entirely.
# Initialize for staging (different backend)
terraform init \
-backend-config="resource_group_name=rg-tfstate-staging" \
-backend-config="storage_account_name=satfstatestaging001" \
-backend-config="container_name=tfstate" \
-backend-config="key=myapp.tfstate"Regardless of approach, keep the infrastructure code for all environments in the same repository. Separate repos for staging and prod infrastructure lead to drift — changes get applied to one and forgotten for the other. One repo, multiple environments, enforced by CI/CD.
Environment promotion workflow
Promotion is the process of moving a validated artifact from a lower environment to a higher one. The workflow:
1. Developer creates a pull request. The CI pipeline builds the artifact and runs unit tests. If tests pass, the artifact is published.
2. On merge to main, the CD pipeline deploys the artifact to dev automatically. Automated smoke tests run. If they pass, the pipeline continues.
3. The pipeline deploys to staging. Integration tests and automated end-to-end tests run. The pipeline pauses for human approval.
4. After QA signs off in staging, a designated approver approves the production deployment in Azure DevOps.
5. The same artifact deploys to production. Post-deployment smoke tests verify the deployment. Monitoring dashboards are checked for error rate increases.
This is a linear promotion: dev → staging → production. The artifact never skips an environment. The only exception is hotfixes, which follow an expedited process with post-deployment review.
Feature flags as an alternative to environment promotion
Environment promotion gates features behind environment boundaries. Feature flags gate features behind runtime configuration switches, allowing the same code to be deployed everywhere while controlling which users see which features.
Azure App Configuration supports feature flags natively. Flags are stored in App Configuration and queried by the application at runtime. Flags can be toggled without redeployment, targeted to specific users or percentages of users, and scheduled to activate or deactivate at specific times.
Feature flags complement environment promotion — they do not replace it. Use environment promotion for infrastructure changes, configuration changes, and breaking changes. Use feature flags for gradual rollout of new application features after the code has already been deployed and validated in staging.
Data strategy across environments
Data is the hardest part of environment parity. Production has real customer data that cannot go into staging due to privacy regulations (GDPR, CCPA) and security requirements. Staging needs data that resembles production in shape and volume for realistic testing.
Approaches: synthetic data generation (use tools to create realistic-looking but fake datasets), data masking (copy production data and anonymize PII fields before importing to staging), and subset sampling (take a statistically representative 1% sample of production data, anonymize it, use in staging).
Database schema migrations need special attention across environments. Apply schema changes to staging first, validate application compatibility, then apply to production. Never apply a migration to production that has not first been validated in staging with a data set that resembles production schema and volume.
Common mistakes
- Staging using a different network architecture than production. If staging uses a single VNet with no NSGs while production uses hub-and-spoke with Azure Firewall, a change that works in staging will fail in production because of firewall rules blocking traffic. Network architecture must match — you can reduce scale (fewer subnets, smaller CIDRs) but not skip architectural components.
- Treating dev as staging. Developers use dev for active development — it gets broken, reset, and modified. If QA tests in the dev environment, they are testing against an unstable target. Staging must be a dedicated, controlled environment that only receives deployments through the pipeline. No developer should be making manual changes to staging infrastructure.
- Not having a rollback plan before every production deployment. Every production deployment should have a defined rollback path before it begins. What is the previous version? How quickly can you revert? Does the database schema change allow reverting the application? Answering these questions after something goes wrong is too late. Document the rollback plan in the deployment runbook.
Summary
- Dev is for fast feedback. Staging is for production-parity validation. Production is for live customers. Each has different access controls and risk tolerance.
- Staging must match production in architecture, OS/runtime versions, configuration structure, and network security rules — not necessarily in capacity or data.
- Use Terraform workspaces or separate backend configs to manage infrastructure per environment from a single codebase.
- Promote the same artifact from dev → staging → production. Never rebuild the artifact per environment.
- Feature flags enable gradual feature rollout to production users without bypassing staging promotion for code validation.
Frequently asked questions
Does staging need to be exactly the same size as production?
No. Staging should be architecturally identical (same services, same networking, same configuration structure) but not capacity-identical. Running staging at half the VM SKU of production is fine. What matters is that staging does not have architectural differences — different services, missing network rules, or different secret management — that would allow bugs to hide in staging and surface only in production.
Should developers have direct access to the production Azure portal?
Generally no. Developers should have read-only access or no direct portal access to production. Changes to production should go through a pipeline with approval gates. This prevents manual configuration drift and creates an audit trail. Use Azure Privileged Identity Management (PIM) for temporary elevated access during incident response — time-bounded, audited, and requiring justification.
What are feature flags and how do they relate to environment promotion?
Feature flags are runtime configuration switches that enable or disable features without deploying new code. They are an alternative to environment promotion for some scenarios: instead of gating a feature behind staging, you deploy it to production with the flag off, then turn it on for a subset of users. Services like Azure App Configuration support feature flags natively. They add operational complexity but enable continuous deployment without environment gates blocking every change.