Scaling Backstage to serve thousands of engineers: horizontal scaling with multi-instance frontend and backend, load balancing, a shared PostgreSQL database, a task scheduler for pipelines, plus bundling size optimization, memory management, and cache strategy.

Episode 18 equipped you with the backend foundation: plugins, modules, service APIs, and integrations. Episode 19 answers the question that arises once Backstage is really used: what if the users aren't dozens, but thousands of engineers in one organization? Here you'll learn scaling strategies — adding frontend and backend instances, balancing load, using a shared database, scheduling task pipelines correctly — then close with bundle, memory, and cache optimizations, plus reliability notes from the Backstage 2026 backlog.
Backstage is designed so each instance stays as stateless as possible. The frontend is just static files served to the browser, while the backend stores most state in an external database. These two properties are what make horizontal scaling fairly easy.
| Component | Property | How to scale |
|---|---|---|
| Frontend | Static, stateless | Serve the bundle via CDN or behind a load balancer |
| Backend | Stateless per instance | Add replicas, state lives in PostgreSQL |
| Database | Centralized state | One shared PostgreSQL, with a connection pool |
| Task scheduler | Has shared state | Run tasks through a database-backed scheduler |
The Backstage frontend is a static build output. For large scale, serve that bundle through a CDN or object storage, so requests don't always hammer a single server. The backend can run as many replicas behind a load balancer — each replica is self-contained because its state lives in PostgreSQL.
What to avoid is storing state in an instance's memory: login sessions, cached query results, or task locks that only live on one pod will vanish when that pod restarts. All cross-instance state must move to shared storage.
A load balancer (an Ingress in Kubernetes, nginx, or a cloud load balancer) distributes requests to each backend replica. To stay healthy, also configure a health check on a recognized endpoint, for example the health route of the plugin you built in episode 18:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backstage-ingress
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 50m
spec:
rules:
- host: backstage.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backstage-backend
port:
number: 7007With this pattern, a downed instance is removed from rotation, and new requests are routed to healthy instances.
Backstage uses a database to store the catalog, auth sessions, task state, and plugin data. In a multi-instance setup, the database must be single and accessible to all instances: PostgreSQL. Connections are managed through configuration:
backend:
database:
client: pg
connection:
host: postgres.internal
port: 5432
user: backstage
password: ${POSTGRES_PASSWORD}
database: backstageA few important notes:
Backstage runs various scheduled tasks: catalog refresh, TechDocs analysis, or scaffolder tasks. If every replica runs the tasks, the work gets duplicated. The solution is a task scheduler that uses the database as a coordination source — only one instance executes each task at a time.
In the New Backend System, plugins use the coreServices.scheduler service to register tasks:
import { coreServices } from '@backstage/backend-plugin-api';
register(env) {
env.registerInit({
deps: { scheduler: coreServices.scheduler, logger: coreServices.logger },
async init({ scheduler, logger }) {
await scheduler.scheduleTask({
id: 'refresh-reports',
frequency: { hours: 6 },
timeout: { minutes: 10 },
fn: async () => {
logger.info('memperbarui laporan agregat');
},
});
},
});
}With a database-backed scheduler, a task runs once no matter how many replicas there are — an important requirement when pipelines run at the scale of thousands of engineers.
A large frontend bundle slows page loads. Some practices that help:
webpack-bundle-analyzer.The backend deserves attention too: build in production mode and make sure development dependencies aren't packed in.
Node.js backend processes can grow over time. Things to manage:
NODE_OPTIONS so a pod doesn't suddenly run out of memory.The backend defaults to an in-memory cache per instance. The problem: such a cache isn't shared between instances — one instance can serve stale data while another is fresh. For large scale, move the cache to shared storage like Redis:
backend:
cache:
store: redis
connection:
host: redis.internal
port: 6379A shared cache speeds up responses and keeps all replicas consistent — important for catalog entities read constantly by thousands of engineers.
Important
The rule of thumb for scaling Backstage: move all state out of the instance before adding replicas. Sessions, cache, and task locks must live in shared storage, not in process memory. Adding replicas to an instance that stores state in memory only creates more stale data.
Scaling to thousands of engineers isn't only about the number of instances. Backstage performance at scale gets special attention in the Backstage 2026 reliability backlog — covering improvements across catalog load, scaffolder, and backend runtime in general. A few steps you can take in your own organization:
Scaling isn't a one-time project; it's an iterative process that follows real usage patterns.
In this episode 19, you understood Backstage scaling principles: horizontal scaling with multi-instance frontend and backend, load balancing with health checks, a shared PostgreSQL database, a coordinated task scheduler for pipelines, plus bundling size optimization, memory management, a Redis cache strategy, and reliability notes for the scale of thousands of engineers.
The key takeaways:
In episode 20, once the system is big and heavily used, you need a way to see inside it: observability & Tech Insights. We'll use structured logging, OpenTelemetry, Prometheus, and analytics events, then run Tech Insights with scoring cards to monitor entity health.