This runbook covers when and how to move between the three reference tiers as your
organisation grows, what to apply at each step, and how to verify the outcome.
Move from Small → Medium when any of these are true:
- Backstage catalog entity count > 500
- Backstage pod CPU > 70% sustained for > 10 minutes
- ArgoCD sync queue depth > 20 apps waiting
- Team count approaches 20 (plan ahead — provisioning takes 30–60 min)
Move from Medium → Large when any of these are true:
- Team count approaches 60
- ArgoCD application count > 250
- EKS node group desired_size > 15 consistently
- RDS CPU > 60% sustained (Backstage catalog queries)
cdterraform
# Dry-run first — inspect what changes
terraformplan-var-file=profiles/medium.tfvars
# Apply (EKS node group resize is rolling, ~10 min; RDS Multi-AZ failover takes ~5 min)
terraformapply-var-file=profiles/medium.tfvars
Key changes per tier:
- Small → Medium: EKS nodes t3.large→m5.xlarge, RDS db.t3.medium→db.m5.large,
rds_multi_az=true (triggers RDS modification, brief failover), Karpenter enabled.
- Medium → Large: EKS nodes m5.xlarge→m5.2xlarge, RDS db.m5.large→db.r5.xlarge,
VPC CIDR expands (requires VPC replacement — plan a maintenance window).
Large tier VPC note: Expanding vpc_cidr from 10.0.0.0/16 to 10.0.0.0/8 requires
destroying and recreating the VPC and all dependent resources. Do this with a blue/green
cluster approach — provision the new VPC in a separate Terraform workspace, migrate teams,
then decommission the old one.
Step 2 — Apply Helm tier values (platform service resize)¶
# Replace <tier> with small, medium, or large
helmupgradebackstagebackstage/backstage\-nbackstage\-fhelm/values-tiers/backstage-<tier>.yaml\--reuse-values
At Medium+, Elasticsearch must be running before applying the medium/large values:
123456789
# Deploy Elasticsearch (one-time)
helmupgrade--installelasticsearchelastic/elasticsearch\-nsearch--create-namespace\--setreplicas=1\--setresources.requests.memory=2Gi
# Then install the ES search backend plugin in Backstage# See backstage/app/packages/backend/src/index.ts — add:# backend.add(import('@backstage/plugin-search-backend-module-elasticsearch'));
At Large, controller sharding is enabled (3 replicas with round-robin algorithm).
Verify sharding is active:
12
argocdadmincontroller-info
# Should show shard assignments across 3 controller pods
Step 3 — Onboard teams at scale (Team Namespace template)¶
Each new team should go through the Provision Team Namespace Backstage template.
It creates in one scaffold run:
- team-<slug> Namespace with tier label and cost tags
- ResourceQuota (Small/Medium) or LimitRange defaults (Large)
- ArgoCD AppProject scoped to the namespace
- idp-developer RoleBinding for team members
- deployer ServiceAccount for CI/CD
- Backstage Group entity (auto-registered in catalog)
Navigate to: Backstage → Create → Provision Team Namespace
For bulk onboarding (migration from existing namespaces), use the scaffold API directly:
1 2 3 4 5 6 7 8 9101112131415
# Trigger scaffold via Backstage API for each team
curl-XPOSThttps://<backstage-url>/api/scaffolder/v2/tasks\-H"Authorization: Bearer $BACKSTAGE_TOKEN"\-H"Content-Type: application/json"\-d'{ "templateRef": "template:default/team-namespace", "values": { "teamName": "payments", "ownerGroup": "engineering", "costCenter": "CC-1234", "tier": "medium", "githubOrg": "your-org", "platformRepo": "backstage-platform-template" } }'
# EKS nodes are the right instance type and count
kubectlgetnodes-owide
# RDS Multi-AZ is active (Medium+)
awsrdsdescribe-db-instances\--query'DBInstances[].{id:DBInstanceIdentifier,multiAZ:MultiAZ,class:DBInstanceClass}'
# Pods are healthy and replicas match tier
kubectlgetpods-nbackstage
# Search backend is reachable (Medium+)
kubectllogs-nbackstagedeploy/backstage|grep-ielasticsearch
# Kyverno generated the quota for a test namespace
kubectlgetresourcequota-nteam-<slug>
# Attempt a quota-busting pod (should be denied at Medium/Small)
kubectlrunquota-test--image=nginx-nteam-<slug>\--overrides='{"spec":{"containers":[{"name":"c","image":"nginx","resources":{"requests":{"cpu":"100"}}}]}}'
Steps:
1. Run terraform apply -var-file=profiles/large.tfvars with cluster_name=idp-workloads
in a separate Terraform workspace.
2. Register the workload cluster in ArgoCD on the platform cluster:
1
argocdclusteradd<workload-kubeconfig-context>
3. Update ArgoCD ApplicationSets to target https://<workload-cluster-api> for team apps.
4. Update Kyverno ClusterPolicy to target the workload cluster (install Kyverno on workload cluster).
Note: RDS instance class downgrades require a brief DB restart. EKS node group downgrades
are rolling and non-disruptive if pods fit on the smaller nodes.