Non-production cloud costs become controllable when every staging, dev and preview environment has four things: a technical boundary its cost can be read from, an owner, an operating schedule and an expiry date. Without them, instances, databases, clusters and data copies keep running unnoticed. This analysis shows how to separate that spend, which provider features actually make idle time cheaper, and what a monthly review should check.

Separate non-production first

The strongest boundary is the billing unit itself. AWS's whitepaper "Organizing Your AWS Environment Using Multiple Accounts" describes a Workloads OU for production and non-production workloads, with Prod and Test child OUs as its example structure, and calls the account the default means of allocating cost. Microsoft's Cloud Adoption Framework describes subscriptions as a management boundary that separates development, test and production. Google Cloud's enterprise foundations blueprint uses separate production, nonproduction and development folders.

Where environments share an account, a tag carries the boundary. Define a key such as environment with fixed values, for example prod, staging, dev and preview:

  • AWS: activate the key as a cost allocation tag in the Billing console. It can take up to 24 hours to appear for activation and up to another 24 hours to activate. The management account can request a backfill for up to twelve months.
  • Azure: the built-in policies "Require a tag on resource groups" and "Inherit a tag from the resource group if missing" enforce the tag on resource groups and pass it down to resources.
  • Google Cloud: labels on projects and resources appear in the billing export.

What remains, such as CI runners, a shared dev cluster or a central NAT gateway, belongs in an explicit shared cost bucket, not silently in production. Report that bucket and the spend with no environment as separate lines every month.

What keeps costing after you stop it

A stop script is not proof of an empty bill. The table shows, per resource, what the provider says keeps running and which tool implements the schedule.

ResourceNot billed when stoppedStill billedScheduling tool
EC2 instanceinstance usageEBS volumes, Elastic IPsInstance Scheduler on AWS, Resource Scheduler in Systems Manager Quick Setup
RDS instanceinstance hoursstorage including Provisioned IOPS, backups, public IPv4Instance Scheduler on AWS; RDS restarts the instance itself after 7 days
Aurora Serverless v2instance capacity when auto-paused at 0 ACUstorageminimum capacity 0 ACU and SecondsUntilAutoPause
Azure VMcompute only in "Stopped (deallocated)"disks and networkingAuto-shutdown, Start/Stop VMs v2
Azure SQL Database serverlesscompute while pausedstorageauto-pause delay from 15 minutes (General Purpose)
AKS clustercompute for control plane and agent nodesanything that is not compute; state kept for up to 12 monthsaz aks stop / az aks start
Compute Engine VMCPU and memorydisks, external IP addressesinstance schedules
Cloud SQLinstance chargesstorage and IP addressesstop, or activation policy NEVER

The limits belong in the plan:

  • Instance Scheduler on AWS controls EC2, EC2 Auto Scaling groups and RDS through tags and Lambda, across accounts too. The solution has its own running cost: AWS's reference example for a small deployment (five scheduling targets, 20 resources, a five-minute interval) comes to about USD 9.15 a month, without naming a region.
  • RDS can be stopped for at most 7 consecutive days. RDS for SQL Server in a Multi-AZ deployment cannot be stopped.
  • Aurora typically takes about 15 seconds to resume after a pause, and 30 seconds or longer after more than 24 hours paused, as over a weekend; AWS recommends raising client timeouts accordingly.
  • Azure VMs in "Stopped" without deallocation are still billed, which happens for example after a shutdown from inside the guest OS. Check the state in the portal. Auto-shutdown sets a daily shutdown time with optional email or webhook notification; Start/Stop VMs v2 is designed for start and stop schedules across multiple subscriptions.
  • AKS may be unable to restart a stopped cluster in regions with capacity constraints.
  • Compute Engine can begin scheduled starts and stops up to 15 minutes after the scheduled time, does not support VMs with Local SSD, and allows one schedule per VM.

Size databases and data copies deliberately

A staging database has to test behaviour, not replicate production's resilience. Decide per environment on Multi-AZ or zone redundancy, read replicas, Provisioned IOPS and backup retention. For databases needed only during working hours, Aurora Serverless v2 with auto-pause or Azure SQL Database serverless are options, provided the application tolerates the resume delay.

Data copies are the second quiet line item: restored production snapshots, exports in test buckets and database dumps for local development. Every copy costs storage, and every refresh costs transfer. If it contains personal data, it is also a data protection question; agree masking, access and retention with whoever owns data protection, under the GDPR or UK GDPR as applicable.

Preview environments with an expiry date

A preview environment per pull request is useful as long as it disappears with the pull request. In GitHub Actions, the pull_request event with type closed also fires for merged pull requests. A teardown workflow can look like this:

on:
  pull_request:
    types: [closed]
jobs:
  teardown:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Delete preview environment
        run: ./scripts/preview-destroy.sh "pr-${{ github.event.pull_request.number }}"

Because a failed workflow can leave an environment behind, add an expiry date as well: a tag or label such as expires-on and a daily job that deletes expired environments. If previews run as namespaces in a shared cluster, a ResourceQuota caps what a preview can request. The values are placeholders:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: preview-quota
  namespace: pr-1234
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 4Gi
    requests.storage: 20Gi
    services.loadbalancers: "0"

services.loadbalancers: "0" stops every preview from creating its own cloud load balancer.

Shared dev clusters

A shared cluster saves fixed costs such as the control plane and minimum nodes, but it moves the problem: base capacity runs without jobs, and without quotas every namespace grows freely. Check minimum node counts at night and at weekends, namespaces without an owner, and volumes without a pod. How to read cluster cost without per-pod guesses is covered in Understand Kubernetes cluster cost without per-pod guesses.

Checklist for the monthly non-production review

The review fits as a standing block in the monthly cloud cost review:

  1. Cost per environment over closed months, with shared cost and cost without an environment as separate lines.
  2. Every non-production unit has an owner, a purpose and, where time-limited, an expiry date.
  3. Compute outside working hours: is a schedule running, and does the state really show "deallocated" or "stopped"?
  4. What stays after stopping: volumes, snapshots, public IPs, NAT gateways, load balancers.
  5. RDS instances that restarted after 7 days.
  6. Previews older than their expiry date and environments for closed pull requests.
  7. Production data copies: age, size, masking, retention.
  8. Non-production databases with high availability or replicas: justified or not?
  9. One decision per finding, with owner and date: keep and budget, schedule, shrink or delete.

Budget the remaining permanent environments as their own line, as described in Cloud budget planning for 2027. Whether staging cost belongs in gross margin is a separate boundary question, covered in Cloud costs and SaaS gross margin.

Non-production in Costfluent

Costfluent makes non-production visible once accounts, subscriptions, projects or tags identify the environment. In cost reports you group by account or tag; allocation rules assign cost to segments such as "Staging", and whatever no rule matches stays visibly unallocated. Cost insights show when that spend grows significantly over the comparison period. Budgets compare actual spend with the planned amount but never stop resources; spend forecasts and budget alert notifications are Planned. Schedules and deletions stay in your own automation.

Sources and method

Checked on 10 October 2026: AWS whitepaper: Benefits of using multiple AWS accounts, AWS whitepaper: Application OUs, Activating user-defined cost allocation tags, Backfill cost allocation tags, EC2 instance state and billing, Instance Scheduler on AWS, Instance Scheduler on AWS: solution page, Instance Scheduler on AWS: cost, Resource Scheduler in Systems Manager Quick Setup, Stopping an Amazon RDS DB instance, Aurora Serverless v2 auto-pause, Azure landing zone: subscriptions, Azure Policy built-in definitions, Azure VM states and billing, Azure VM auto-shutdown, Start/Stop VMs v2, Azure SQL Database serverless, Stop and start an AKS cluster, Google Cloud enterprise foundations: organization structure, Compute Engine instance schedules, Compute Engine instance lifecycle, Cloud SQL: start, stop and restart instances, Kubernetes Resource Quotas, GitHub Actions: events that trigger workflows. The tables and checklist are Costfluent recommendations, not legal or data protection advice.