Running distinct development, staging, and production environments prevents changes from reaching real users untested. This guide covers structuring multiple environments practically, whether on separate VPS instances or a shared one.
Why Separate Environments Matter
- Test changes without risk to production data or users
- Catch environment-specific bugs before they reach production
- Give team members a safe space to experiment
Approach 1 — Separate VPS Per Environment (Recommended for Production Systems)
| Environment | Purpose | Typical Specs |
|---|---|---|
| Development | Active feature work, frequent changes | Small, can be shared/ephemeral |
| Staging | Pre-production testing, mirrors prod closely | Similar to production, possibly smaller |
| Production | Live, serving real users | Full production specs |
Approach 2 — Multiple Environments on One VPS (Budget-Conscious)
Using different ports/subdomains and Docker isolation:
staging.yourdomain.com → :3001
yourdomain.com → :3000 (production)
Acceptable for smaller projects, but be aware that a resource-heavy staging test could theoretically impact production performance if truly co-located — weigh this trade-off against cost savings.
Environment-Specific Configuration
Use separate .env files per environment (see How to Manage Environment Variables and Secrets on a VPS):
.env.development
.env.staging
.env.production
Environment-Specific Database Isolation
Never let staging and production share a database — always fully separate databases, even if they run on the same database server:
CREATE DATABASE myapp_staging;
CREATE DATABASE myapp_production;
Restricting Access to Non-Production Environments
Staging and development environments shouldn't be publicly indexed or easily discoverable:
location / {
auth_basic "Restricted";
auth_basic_user_file /etc/nginx/.htpasswd;
}
See VPS Hosting for Developers: Setting Up a Dev/Staging Environment for full detail on this pattern.
Keeping Staging Representative of Production
Periodically refresh staging's database from a sanitized production backup, ensuring bugs that only appear with realistic data volume/shape are actually caught before reaching production.
Deployment Pipeline Across Environments
on:
push:
branches:
- develop # deploys to staging
- main # deploys to production
Configure your CI/CD pipeline (see How to Build a Simple CI/CD Pipeline with GitHub Actions) to deploy different branches to different environments automatically.
Promoting Code Between Environments
A typical flow: feature branches merge to develop (auto-deploys to staging) → after verification, develop merges to main (auto-deploys to production) — ensuring nothing reaches production without first passing through staging.
Environment Naming and Labeling
Make it visually obvious which environment someone is looking at — a banner, different color scheme, or clear labeling in staging/dev environments prevents costly mistakes (like someone believing they're testing safely in staging when actually in production).
Common Errors
Accidentally testing against production database — almost always caused by insufficiently distinct environment configuration; ensure connection strings are environment-specific and clearly different, ideally with a visual safeguard in the application itself.
Staging environment publicly indexed by search engines — add a noindex meta tag and restrict access as shown above.
Best Practices
- Fully isolate databases between environments, always
- Restrict access to non-production environments
- Keep staging reasonably representative of production for meaningful testing
Continue Reading
- VPS Hosting for Developers: Setting Up a Dev/Staging Environment
- How to Manage Environment Variables and Secrets on a VPS
- How to Build a Simple CI/CD Pipeline with GitHub Actions
Browse more articles in DevOps & CI/CD.
