Test environments are the infrastructure where your tests run. When environments are flaky, inconsistent, or shared between teams, your tests become unreliable regardless of how well they’re written. Environment management is the foundation that everything else sits on.
Environment Types
| Environment | Purpose | Lifetime | Data | Access |
|---|
| Local dev | Developer workstation testing | Permanent | Synthetic | Individual |
| CI ephemeral | Automated test runs | Minutes | Generated | CI system |
| Feature branch | PR-specific preview + testing | Days | Seeded | Team |
| Staging | Pre-production validation | Permanent | Production-like (masked) | All engineers |
| Performance | Load and stress testing | On-demand | Production-like (masked) | SRE team |
| Production canary | Real traffic validation | Permanent | Real (1% traffic) | Deployment system |
Ephemeral Environments
Ephemeral environments spin up on demand and are destroyed after use. They eliminate the “works on staging” problem by giving every PR its own isolated environment.
| Benefit | How |
|---|
| Zero contention | Each PR gets its own environment |
| Clean state | Fresh database, no leftover data from other tests |
| Cost efficient | Only exists during PR lifecycle |
| Production-like | Same infrastructure-as-code as production |
| Parallel testing | Multiple PRs test simultaneously |
Provisioning Flow
PR opened:
→ Terraform/Pulumi provisions infrastructure
→ Database migrations run
→ Application deployed
→ Seed data loaded
→ Tests execute
→ Preview URL posted to PR
→ PR merged or closed: environment destroyed
Environment-as-Code
| Tool | Approach | Best For |
|---|
| Docker Compose | Multi-container local environments | Local dev + CI |
| Terraform | Cloud infrastructure provisioning | Full cloud environments |
| Pulumi | Infrastructure in real programming languages | Developer-friendly IaC |
| Helm | Kubernetes application packaging | K8s-native environments |
| Nix/devenv | Deterministic development environments | Reproducible local dev |
| Tilt | Live-reload Kubernetes dev | K8s development workflows |
Data Isolation Strategies
| Strategy | Isolation Level | Performance | Complexity |
|---|
| Database per environment | ✅ Complete | ⚠️ Slow to provision | Medium |
| Schema per environment | ✅ Complete | Good | Medium |
| Tenant isolation | ✅ Complete | Good | High |
| Transaction rollback | ✅ Complete | ✅ Fast | Low |
| Shared with row-level filtering | ⚠️ Partial | ✅ Fast | Medium |
Environment Configuration Management
| Configuration | Source | Example |
|---|
| Database URL | Environment variable | DATABASE_URL=postgres://... |
| API keys | Secret manager (Vault, AWS Secrets) | STRIPE_KEY=sk_test_... |
| Feature flags | Feature flag service | FEATURE_NEW_CHECKOUT=true |
| Service URLs | DNS or service discovery | USER_SERVICE_URL=http://... |
| Log level | Environment variable | LOG_LEVEL=debug |
Configuration Hierarchy
Production defaults (safe, restrictive)
← Staging overrides (relaxed for testing)
← CI overrides (deterministic, mocked externals)
← Local overrides (developer preferences)
← Test overrides (per-test configuration)
Cost Management
| Strategy | Savings | Implementation |
|---|
| Ephemeral environments | Pay only during PR lifecycle | Auto-destroy on PR close |
| Scheduled teardown | No forgotten environments | Cron job checks for stale environments |
| Right-sized instances | Don’t use production-grade infra for tests | Smaller instances, fewer replicas |
| Shared staging | One environment for manual testing | Reserve ephemeral for automated tests |
| Spot/preemptible instances | 60-80% cost reduction | Acceptable for test workloads |
Troubleshooting Environment Issues
| Symptom | Likely Cause | Fix |
|---|
| Tests pass locally, fail in CI | Environment differences | Pin all versions (OS, runtime, dependencies) |
| Tests pass individually, fail together | Shared state between tests | Isolate data per test |
| Staging is “always broken” | Multiple teams deploying simultaneously | Use ephemeral environments per PR |
| Environment provisioning is slow | Cold starts, no caching | Cache Docker layers, use prebuilt images |
| Can’t reproduce CI failure locally | Different seed data or configuration | Export CI environment config for local use |
Anti-Patterns
| Anti-Pattern | Problem | Fix |
|---|
| Shared staging for everything | Teams block each other, data corrupted | Ephemeral per PR + single staging for manual QA |
| Manual environment setup | Unreproducible, developer-dependent | Environment-as-code (Docker Compose, Terraform) |
| Production data in test environments | Security and compliance risk | Mask PII, use synthetic data |
| No environment cleanup | Costs grow unbounded | Auto-destroy with TTL |
| Different infra in test vs prod | Tests pass on wrong infrastructure | Use same IaC modules with different parameters |
Checklist
:::note[Source]
This guide is derived from operational intelligence at Garnet Grid Consulting. For DevOps and infrastructure consulting, visit garnetgrid.com.
:::
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting
Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.
View Full Profile →