ScreenshotNeo

BlogGuides

How to Manage Multiple Testing Environments in DevOps

Design DevOps environments around clear testing purposes, repeatable infrastructure, safe deployments, and reliable cleanup. Includes GitHub Actions and GitLab CI/CD patterns.

By the ScreenshotNeo team4 October 20269 min read

Manage multiple testing environments by giving each one a clear purpose, provisioning it from reusable infrastructure as code, protecting its credentials, and controlling deployments and cleanup. A practical baseline is development, test, and production; add staging, review apps, or individual developer environments only when they solve a specific validation or parallel-work need. There is no universal number of environments that fits every team.

For each environment, decide what it validates, who can deploy to it, how closely it should match production, how long it should exist, and who owns cleanup. This keeps test results useful without paying for idle infrastructure or exposing production credentials to untrusted jobs.

1. Choose environments by purpose

AWS DevOps Guidance recommends at least deployment, test, and production environments for each system. Separate system-level environments to isolate systems, tailor resources, and distinguish lifecycle concerns. This is a starting point, not a requirement to create a fixed stack of environments for every repository. AWS DevOps Guidance: AG.DEP.3

Environment What it is for Typical lifetime Useful controls
Developer or sandbox Local integration, experimentation, and early validation Persistent per developer or scheduled Limited permissions and resources; easy reset or shutdown
Deployment or integration Verify a deployable build and interactions among services Persistent shared target or isolated per pipeline Serialize deployments if shared; use representative dependencies
Test Run automated functional, integration, or acceptance checks Persistent or created per run Repeatable data setup and teardown; isolate concurrent runs
Staging Validate a release candidate with production-like controls and configuration Usually persistent or release-scoped Controlled promotion, restricted secrets, explicit ownership
Production Serve users Persistent Strong deployment permissions, approvals where appropriate, audited credentials
Review app Let reviewers inspect a branch or merge request independently Temporary Unique URL, expiration, and a teardown job that removes external resources

Choose the number and boundaries based on system architecture, test types, risk, team parallelism, quotas, and operating cost. A shared staging environment can be enough for a small team; parallel reviews may justify temporary environments. For experimentation with a large blast radius, stronger account or organization separation may be appropriate. Account separation alone is not sufficient for every organization-level experiment, according to AWS guidance.

2. Set the right degree of production similarity

Reproduce the controls and dependencies that matter to the test: authentication, network boundaries, deployment configuration, service versions, and relevant data shapes. Keep non-production sizing appropriate to its purpose. An environment does not need to be an exact production clone for every test.

For load tests whose results need to represent production behavior, AWS recommends production-equivalent environments. A smaller or differently configured target may still be useful for functional checks, but do not interpret its capacity results as production capacity evidence. AWS Well-Architected Framework: multiple environments

3. Make environments repeatable with infrastructure as code

  1. Define infrastructure and environment configuration in version-controlled code.
  2. Keep shared defaults in reusable modules, then parameterize differences such as region, size, hostname, and dependency endpoints.
  3. Provision through a consistent CI job or self-service workflow rather than undocumented manual changes.
  4. Record the source revision and configuration version deployed to each target.
  5. Detect drift and either reconcile it through code or document why a deliberate difference exists.
  6. Make test data setup and cleanup repeatable, especially when jobs can run concurrently.

AWS recommends infrastructure as code and configuration management to keep environments consistent with controls present in production. It also recommends turning off unused environments to avoid idle resource costs. AWS guidance

4. Scope credentials and deployment access

  • Give each environment only the credentials and permissions it needs.
  • Keep production secrets unavailable to untrusted branches and pull requests.
  • Restrict which branches, users, or deployment jobs can target production.
  • Use approval gates when the risk of an unreviewed deployment warrants them.
  • Rotate credentials and avoid copying production secrets into lower environments.
  • Separate deployment configuration from general build jobs if that gives production access a smaller boundary.

GitHub Actions environments can apply protection rules before a job starts; environment secrets are available to the job only after those rules pass. GitLab documents protected variables, environment-scoped variables, deployment permissions, approvals, and separate deployment projects as ways to limit access. Check the current platform documentation and plan availability before relying on a particular control.

5. Serialize deployments to shared targets

CI jobs can run concurrently, and two pipelines may try to replace the same shared environment at once. Choose one of two approaches:

  • Shared target: serialize deployments and decide what happens to older queued runs.
  • Isolated target per pipeline: allow parallel work, then remove each target reliably.

GitHub Actions supports concurrency groups, while GitLab CI/CD supports resource_group for serializing deployment jobs. Review both parallel runs and stale pipeline behavior: an older run completing later should not silently replace a newer build.

6. Create temporary review environments and clean them up

Dynamic environments are useful when reviewers need an independent deployment for each merge request or branch. GitLab supports names and URLs derived from pipeline variables, review apps, stop actions, expiration settings, and stale-environment cleanup. Treat teardown as part of the environment definition:

  1. Generate a unique environment name and URL from a branch or pipeline identifier.
  2. Deploy resources with ownership labels or another reliable link to the pipeline.
  3. Configure a stop action for merge, close, or expiration events.
  4. Have the stop job delete cloud resources, DNS entries, temporary data, and other external dependencies.
  5. Run a periodic cleanup for expired resources whose stop job failed or never ran.
  6. Alert or report on teardown failures so orphaned resources do not accumulate.

Changing an environment’s status in the CI interface does not necessarily delete the external infrastructure. GitLab specifically cautions that forced stopping can skip cleanup actions; make sure the teardown job actually executes. GitLab CI/CD environments

7. Example: GitHub Actions environments

Create named environments such as test and production in the repository settings. Configure production protection rules and secrets there, then reference the environment from the deployment job. The following illustrates a serialized deployment workflow; replace the placeholder commands with the project’s build and deployment steps.

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy-test:
    runs-on: ubuntu-latest
    environment: test
    concurrency:
      group: deploy-test
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v4
      - name: Build and deploy to test
        run: ./scripts/deploy.sh test

  deploy-production:
    needs: deploy-test
    runs-on: ubuntu-latest
    environment: production
    concurrency:
      group: deploy-production
      cancel-in-progress: false
    steps:
      - uses: actions/checkout@v4
      - name: Build and deploy to production
        run: ./scripts/deploy.sh production

Configure approval and branch restrictions on the production environment itself. Keep secrets in the appropriate environment configuration, and do not print them in logs. The example uses a checkout action version for illustration; review action versions and your repository’s current security policy before adopting it.

For parallel review deployments, create a distinct environment name per pull request and add a cleanup workflow triggered when the pull request closes. Ensure that the cleanup script deletes the provisioned resources, not just the CI environment record.

References: GitHub Actions environments and GitHub Actions concurrency.

8. Example: GitLab CI/CD review app and shared deployment

This minimal pattern names a review environment from the merge request and defines a stop action. Adapt the deployment and deletion scripts to your infrastructure. The cleanup job must be runnable with the permissions and context required to remove the actual resources.

stages:
  - deploy
  - cleanup

review:
  stage: deploy
  script:
    - ./scripts/deploy-review.sh "$CI_ENVIRONMENT_SLUG"
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_ENVIRONMENT_SLUG.review.example.com
    on_stop: stop_review
    auto_stop_in: 1 week
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'

stop_review:
  stage: cleanup
  script:
    - ./scripts/delete-review.sh "$CI_ENVIRONMENT_SLUG"
  environment:
    name: review/$CI_COMMIT_REF_SLUG
    action: stop
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: manual
      allow_failure: true

Confirm the variable expansion and review-app behavior for your GitLab version and project configuration. For a shared target, add a resource_group to the deployment job so simultaneous pipelines do not deploy at the same time:

deploy_staging:
  stage: deploy
  resource_group: staging
  script:
    - ./scripts/deploy.sh staging

References: GitLab environments and GitLab resource groups.

9. Validate a screenshot or PDF across environments

Browser output can be one of the artifacts you compare between test, staging, and production. Capture the same route at the same viewport and state, and keep the capture inputs consistent: authentication, headers, cookies, locale, and any required wait condition. Store the environment URL and build revision with the artifact so reviewers know what they are inspecting. Protect screenshots that contain private data as you would any other test artifact.

A screenshot check can reveal a missing asset, layout shift, consent dialog, or incorrect deployment configuration. It complements functional checks; a picture alone does not prove that the application behaves correctly.

10. Troubleshooting environment management

Symptom Likely cause Fix
Test passes in one environment but fails in another Configuration drift, dependency version mismatch, or different test data Provision both from the same IaC baseline, record versions, compare effective configuration, and make intentional differences explicit.
Two deployments overwrite each other Parallel jobs target a shared environment Serialize with GitHub Actions concurrency or GitLab resource_group, or provision separate targets per pipeline.
An old pipeline replaces a newer build Queue or cancellation behavior allows an outdated run to finish last Define stale-run behavior, cancel or reject outdated revisions where appropriate, and verify ordering under concurrent runs.
Review app URL exists but the app is unavailable Provisioning failed, hostname construction is wrong, or a dependency is unreachable Inspect deployment logs and generated environment variables, verify DNS and routing, then check service health and dependency configuration.
Cloud resources remain after a review app closes Stop action did not run, failed, or only changed CI status Make teardown idempotent, inspect job permissions and logs, and run a scheduled orphan-resource cleanup.
Production secrets appear unavailable Protection rules have not passed, branch is not permitted, or the job references the wrong environment Check the environment name, allowed branches, approval state, and secret scope. Do not move secrets into a broadly accessible scope to bypass the gate.
Staging results do not predict load behavior Staging capacity or topology differs materially from production Use a production-equivalent target for representative load tests, or clearly limit conclusions from the smaller environment.
Environment costs keep growing Persistent resources are idle or temporary cleanup is unreliable Schedule shutdown for idle development resources, set expiration for ephemeral targets, track cleanup failures, and assign an owner.

11. Performance, reliability, and cost

  • Performance: parallel isolated environments reduce queueing but consume more capacity and may add provisioning time. Shared serialized targets use fewer resources but can become a bottleneck.
  • Reliability: IaC, repeatable data setup, health checks, and idempotent deploy and teardown jobs make failures easier to reproduce and recover from. Track drift and cleanup failures.
  • Cost: right-size resources for each test purpose, shut down unused persistent environments, and expire temporary environments. Include external resources such as databases, DNS, storage, and observability services in cleanup.
  • Signals to review: failed deployments, drift findings, cleanup failures, idle spend, and how often a shared environment blocks parallel work. Use these signals to decide whether to split, share, resize, or make an environment ephemeral.

Or skip the browser setup

If one environment check requires a screenshot or PDF, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request captures a URL; see the ScreenshotNeo API documentation for options such as full-page capture, CSS selectors, viewport and device presets, wait conditions, custom headers and cookies, PDF output, and asynchronous jobs.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets AI agents use screenshot, page information, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Learn about ScreenshotNeo, then sign up for 1,000 free screenshots a month with no card.

FAQ

How many environments should a small team have?

Start with the targets required to validate and release your system, commonly development, test, and production. Add a shared staging or temporary review target when it addresses a concrete release or parallel-review need.

Should every environment use a separate cloud account?

No universal rule applies. Choose the boundary based on blast radius, access control, quotas, and operational overhead. Some organization-level experimentation may need stronger separation than accounts alone provide.

Should developers test against production data?

Use data that is safe for the environment and appropriate to the test. Avoid copying production secrets into lower environments; make data shape and privacy constraints part of the environment design.

Should staging be identical to production?

Match the parts that affect the result you need. AWS specifically recommends production-equivalent environments for representative load testing; other test types may need a lighter target.