docs: deployment golden path guide

Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com>
This commit is contained in:
aramissennyeydd
2026-03-24 16:33:57 +01:00
parent 3d67aebd35
commit f9c57344d7
8 changed files with 798 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
---
id: docker
sidebar_label: 001 - Building the Docker image
title: Building the Docker image
description: How to build your Backstage app into a deployable Docker image
---
Audience: Developers and Admins
## Summary
Every production deployment of Backstage starts with a Docker image. The image
bundles both the frontend and backend into a single artifact that you can deploy
anywhere containers run.
By the end of this page, you will have a Docker image ready to push to a
container registry.
## What is in the Docker image?
When you created your app with `@backstage/create-app`, a `Dockerfile` was
generated at `packages/backend/Dockerfile`. The build process layers both the
frontend and backend into a single image:
1. The **backend** is compiled and bundled into `packages/backend/dist/`.
2. The **frontend** is built and served by the
`@backstage/plugin-app-backend` plugin, which is included in the backend
by default.
3. Production dependencies are installed, and the result is packaged into a
slim Node.js image.
The image runs as a non-root `node` user and sets `NODE_ENV=production`.
## Building the image
The recommended approach is a **host build**, where compilation happens on
your machine (or CI runner) and Docker only packages the result. This is faster
and produces better caching behavior.
From the root of your repository:
```shell
yarn install --immutable
yarn tsc
yarn build:backend
```
Then build the Docker image:
```shell
docker image build . -f packages/backend/Dockerfile --tag backstage
```
To verify it works locally:
```shell
docker run -it -p 7007:7007 backstage
```
You should see logs in your terminal and be able to open `http://localhost:7007`
in your browser.
## Running the build in CI
While you can run this build locally, you will typically run it as part of your
CI pipeline. A GitHub Actions workflow might look like this:
```yaml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: yarn install --immutable
- run: yarn tsc
- run: yarn build:backend
- run: docker image build . -f packages/backend/Dockerfile --tag backstage
```
After building, push the image to your container registry (ECR, GCR, Docker
Hub, etc.) so that your deployment platform can pull it.
:::tip Troubleshooting
If you run into build issues, two Docker flags can help:
- `--progress=plain` shows verbose output instead of folded log sections.
- `--no-cache` rebuilds all layers from scratch.
```shell
docker image build . -f packages/backend/Dockerfile --tag backstage --progress=plain --no-cache
```
:::
## Further reading
For a full multi-stage Docker build (where everything happens inside Docker)
or for deploying the frontend separately, see the
[Building a Docker image](../../deployment/docker.md) reference documentation.
## Next steps
Before deploying, you need to set up two production dependencies: a database and
an authentication provider.
- [Setting up a production database](./002-database.md)
+103
View File
@@ -0,0 +1,103 @@
---
id: database
sidebar_label: 002 - Database
title: Setting up a production database
description: How to configure PostgreSQL for your Backstage production deployment
---
Audience: Admins
## Summary
During local development, Backstage uses SQLite as a fast in-memory database.
SQLite does not persist data across restarts and is not designed for
multi-instance deployments, so you need a dedicated database for production.
By the end of this page, you will have PostgreSQL configured as your Backstage
database.
## Why PostgreSQL?
PostgreSQL is the recommended production database for Backstage. It handles
concurrent connections well, supports the query patterns that Backstage plugins
use, and is available as a managed service from every major cloud provider.
Some options for running PostgreSQL:
- **Managed services**: Amazon RDS, Google Cloud SQL, Azure Database for
PostgreSQL, or similar offerings.
- **Self-hosted**: Running PostgreSQL in a container or on a dedicated server.
- **In Kubernetes**: Deploying PostgreSQL alongside Backstage (useful for
getting started, but managed services are preferred for production).
## Configuring Backstage to use PostgreSQL
Open your `app-config.production.yaml` and add the database configuration:
```yaml title="app-config.production.yaml"
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
```
The `${...}` syntax references environment variables, which keeps secrets out
of your configuration files. Set these variables in your deployment environment.
:::caution
Avoid hardcoding database credentials in configuration files. Use environment
variables or a secrets manager provided by your deployment platform.
:::
### Optional: SSL connections
If your database provider requires SSL (most managed services do), add the
SSL configuration:
```yaml title="app-config.production.yaml"
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
ssl:
require: true
rejectUnauthorized: true
```
If your provider uses a custom certificate authority, you can reference the
CA certificate file:
```yaml
ssl:
ca:
$file: /path/to/ca/server.crt
```
## How Backstage uses the database
Each plugin gets its own isolated database schema. Migrations run automatically
when the backend starts, so you do not need to run any manual migration steps.
The database coordinates state and work distribution between instances, which
is what makes horizontal scaling possible later on.
## Further reading
For step-by-step PostgreSQL installation instructions and cloud-specific
setup guides, see the [Database configuration guide](../../getting-started/config/database.md).
## Next steps
With the database in place, the next step is to replace the guest login with
a real authentication provider.
- [Configuring authentication](./003-authentication.md)
@@ -0,0 +1,90 @@
---
id: authentication
sidebar_label: 003 - Authentication
title: Configuring authentication
description: How to set up a production authentication provider for Backstage
---
Audience: Developers and Admins
## Summary
By default, Backstage uses a guest authentication provider that lets anyone
log in without credentials. This is convenient for local development but
creates a security risk when deployed to production. Before deploying, you
should configure a real authentication provider.
By the end of this page, you will understand what needs to change and where
to find the setup instructions for your chosen provider.
## Why replace guest authentication?
The guest provider is explicitly not intended for containerized or production
environments. With guest auth enabled, any user who can reach your Backstage
instance shares the same identity and has the same level of access. Replacing
it is one of the most important steps before going to production.
:::danger
Deploying Backstage with the guest authentication provider exposes your
instance to unauthorized access. Configure a real provider before deploying.
:::
## Choosing an authentication provider
Backstage supports a wide range of authentication providers. GitHub is a
common choice since most developers already have accounts, but you should
pick whatever your organization already uses for single sign-on.
Some popular options:
| Provider | Good fit when... |
| :------------------- | :------------------------------------------------ |
| GitHub | Your team uses GitHub and you want a quick setup. |
| Microsoft / Azure AD | Your company uses Microsoft Entra ID (Azure AD). |
| Google | Your company uses Google Workspace. |
| Okta | You use Okta as your identity provider. |
| OIDC | You have a generic OpenID Connect provider. |
| OAuth2 Proxy | You already use an authenticating reverse proxy. |
The full list of providers and their configuration is available in the
[Authentication documentation](../../auth/index.md).
## What the setup involves
Regardless of which provider you choose, the setup follows the same pattern:
1. **Create an OAuth application** (or equivalent) with your identity
provider. You will get a client ID and client secret.
2. **Add the provider configuration** to `app-config.yaml` with the
credentials, typically using environment variables for secrets.
3. **Install the backend auth module** for your chosen provider in
`packages/backend`.
4. **Configure a sign-in resolver** that maps the authenticated user identity
to a Backstage catalog user entity.
5. **Update the frontend** to show the correct sign-in page.
The [Authentication getting started guide](../../getting-started/config/authentication.md)
walks through this process step by step using GitHub as the example provider.
## Production configuration
Once authentication is configured, make sure that the guest provider is
disabled in your production config:
```yaml title="app-config.production.yaml"
auth:
providers:
guest: null
```
This ensures that even if the guest provider is configured in your base
`app-config.yaml` for local development, it is explicitly disabled in
production.
## Next steps
With both a database and authentication configured, you are ready to deploy.
- [Deploying to production](./004-deploying.md)
@@ -0,0 +1,186 @@
---
id: deploying
sidebar_label: 004 - Deploying
title: Deploying to production
description: How to deploy your Backstage instance to Kubernetes and other platforms
---
Audience: Admins
## Summary
You have a Docker image, a database, and authentication configured. Now it is
time to deploy. The best way to deploy Backstage is _the same way_ you deploy
other software at your organization. This page covers deploying to Kubernetes
as a concrete example and points to resources for other platforms.
## Deploying to Kubernetes
Kubernetes is the most common deployment target for Backstage. The deployment
involves a few Kubernetes objects: a namespace, secrets, a deployment, and a
service.
### 1. Create a namespace
```yaml
# kubernetes/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: backstage
```
```shell
kubectl apply -f kubernetes/namespace.yaml
```
### 2. Create secrets
Store your database credentials and any other secrets (API tokens, auth
client secrets) as Kubernetes Secrets:
```yaml
# kubernetes/backstage-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
name: backstage-secrets
namespace: backstage
type: Opaque
data:
POSTGRES_USER: <base64-encoded-value>
POSTGRES_PASSWORD: <base64-encoded-value>
```
:::note
Kubernetes secrets are base64-encoded but not encrypted. Enable
[Encryption at Rest](https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/)
for your cluster and consider tools like
[SealedSecrets](https://github.com/bitnami-labs/sealed-secrets) for storing
secrets in Git.
:::
```shell
kubectl apply -f kubernetes/backstage-secrets.yaml
```
### 3. Create the deployment
```yaml
# kubernetes/backstage.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backstage
namespace: backstage
spec:
replicas: 1
selector:
matchLabels:
app: backstage
template:
metadata:
labels:
app: backstage
spec:
containers:
- name: backstage
image: <your-registry>/backstage:<tag>
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 7007
envFrom:
- secretRef:
name: backstage-secrets
readinessProbe:
httpGet:
port: 7007
path: /.backstage/health/v1/readiness
livenessProbe:
httpGet:
port: 7007
path: /.backstage/health/v1/liveness
```
Replace `<your-registry>/backstage:<tag>` with the full image URL from your
container registry.
```shell
kubectl apply -f kubernetes/backstage.yaml
```
### 4. Create a service
```yaml
# kubernetes/backstage-service.yaml
apiVersion: v1
kind: Service
metadata:
name: backstage
namespace: backstage
spec:
selector:
app: backstage
ports:
- name: http
port: 80
targetPort: http
```
```shell
kubectl apply -f kubernetes/backstage-service.yaml
```
### 5. Expose the service
The Service is not accessible outside the cluster by default. Use a Kubernetes
[Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
or an
[external load balancer](https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/)
to expose Backstage to your users.
Make sure the `app.baseUrl` and `backend.baseUrl` values in your
`app-config.production.yaml` match the URL where users will access Backstage:
```yaml title="app-config.production.yaml"
app:
baseUrl: https://backstage.example.com
backend:
baseUrl: https://backstage.example.com
listen:
port: 7007
```
## Other deployment platforms
Backstage is a standard Node.js application running in a Docker container, so
it works on any platform that supports containers:
- **Amazon ECS**: Create a task definition referencing your Docker image, set
up an ECS service, and place it behind an Application Load Balancer.
- **Google Cloud Run**: Deploy the image as a Cloud Run service with the
appropriate environment variables.
- **Azure Container Apps**: Deploy the image and configure ingress through
the Azure portal or CLI.
- **Docker Compose**: Suitable for smaller installations. Run Backstage and
PostgreSQL together using a `docker-compose.yaml`.
Community-contributed deployment guides are available in the
[contrib/docs/tutorials](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/)
folder of the Backstage repository.
## Further reading
For a detailed guide including setting up PostgreSQL in Kubernetes, see
the [Deploying with Kubernetes](../../deployment/k8s.md) reference
documentation.
## Next steps
Your Backstage instance is deployed. Next, let's look at how to manage
configuration effectively.
- [Config-first development](./005-config-first.md)
@@ -0,0 +1,92 @@
---
id: config-first
sidebar_label: 005 - Configuration management
title: Config-first development
description: Managing Backstage configuration across environments
---
Audience: Developers and Admins
## Summary
One of the things that makes Backstage easier to operate over time is treating
configuration as the primary way to control application behavior. Instead of
writing custom code for every change, many behaviors can be toggled, adjusted,
or extended through configuration files.
By the end of this page, you will understand how Backstage configuration
layering works and how to manage it across environments.
## How configuration layering works
Backstage loads configuration from multiple `app-config*.yaml` files and
merges them together. Files loaded later override values from earlier files.
The Docker image built in the [first step](./001-docker.md) starts with this
command:
```
node packages/backend --config app-config.yaml --config app-config.production.yaml
```
This means `app-config.production.yaml` overrides any values set in
`app-config.yaml`. You can use this pattern to keep your base config for
local development and override only what changes in production.
### Common configuration split
| File | Purpose |
| :--------------------------- | :------------------------------------------------- |
| `app-config.yaml` | Base configuration shared across all environments. |
| `app-config.local.yaml` | Local overrides, not committed to source control. |
| `app-config.production.yaml` | Production-specific overrides. |
## Environment variables in config
Use the `${VAR_NAME}` syntax to reference environment variables. This is
the recommended approach for secrets and values that differ between
environments:
```yaml title="app-config.production.yaml"
backend:
database:
client: pg
connection:
host: ${POSTGRES_HOST}
port: ${POSTGRES_PORT}
user: ${POSTGRES_USER}
password: ${POSTGRES_PASSWORD}
```
This keeps secrets out of your repository and lets the same Docker image be
used across environments by changing the variables at deploy time.
## What to configure vs. what to code
A good rule of thumb: if a change affects _where_ something connects, _how_
it authenticates, or _which_ features are enabled, it belongs in configuration.
If it changes _what_ a feature does, it belongs in code.
Examples of config-driven behavior:
- Database connection details.
- Authentication provider selection and credentials.
- Catalog locations and entity providers.
- Integration tokens (GitHub, GitLab, etc.).
- TechDocs storage backend (local, S3, GCS, Azure).
- Proxy endpoints for external services.
Making configuration your primary lever for environment differences simplifies
your CI/CD pipeline. You build one Docker image and deploy it everywhere,
varying only the config files and environment variables.
## Further reading
For the full configuration reference, see the
[Configuration documentation](../../conf/index.md).
## Next steps
With your deployment running and configuration managed, you should set up
monitoring to keep it healthy.
- [Monitoring your deployment](./006-monitoring.md)
@@ -0,0 +1,83 @@
---
id: monitoring
sidebar_label: 006 - Monitoring
title: Monitoring your deployment
description: Setting up OpenTelemetry and frontend analytics for your Backstage deployment
---
Audience: Admins
## Summary
A production Backstage deployment needs monitoring to track health, diagnose
issues, and understand usage patterns. Backstage provides built-in support
for OpenTelemetry on the backend and an Analytics API on the frontend.
By the end of this page, you will know how to set up both.
## Backend monitoring with OpenTelemetry
Backstage uses [OpenTelemetry](https://opentelemetry.io/) to report metrics
and traces. The setup involves installing a few OpenTelemetry packages,
creating an instrumentation file, and loading it before the backend starts.
Follow the [Setup OpenTelemetry tutorial](../../tutorials/setup-opentelemetry.md)
for step-by-step instructions on installing dependencies, configuring
exporters (Prometheus for metrics, OTLP for traces), and wiring everything
into your backend.
### Key metrics to monitor
Backstage plugins emit metrics that give you insight into system health:
- `catalog_entities_count` - Total number of entities in the catalog.
- `catalog.processed.entities.count` - Number of entities processed.
- `catalog.processing.duration` - Time spent processing entities.
- `scaffolder.task.count` - Number of scaffolder tasks run.
- `scaffolder.task.duration` - Time taken by scaffolder tasks.
These metrics help you set up alerts for things like processing backlogs or
unusually slow scaffolder runs.
### Health checks
Backstage provides built-in health check endpoints that you can use for
liveness and readiness probes in Kubernetes or other orchestrators:
- `/.backstage/health/v1/readiness` - Returns healthy when the backend is
ready to serve traffic.
- `/.backstage/health/v1/liveness` - Returns healthy when the backend
process is alive.
## Frontend analytics
Backstage provides an Analytics API for tracking user behavior on the
frontend. This is useful for understanding which plugins get the most use
and measuring the return on your Backstage investment.
Several analytics tools are supported through community plugins:
- Google Analytics 4
- New Relic Browser
- Matomo
See the [Analytics documentation](../../frontend-system/building-plugins/08-analytics.md)
for setup instructions.
For frontend error reporting, consider integrating with a service like
Sentry, CloudWatch RUM, or Cloudflare RUM to catch and diagnose client-side
errors.
## Logging
The backend emits structured JSON logs on stdout by default. These logs
include fields like `service`, `plugin`, `level`, and `message` that make
them easy to parse with log aggregation tools (Elasticsearch, Datadog,
Splunk, etc.).
## Next steps
As more users adopt your Backstage instance, you may need to scale the
deployment.
- [Scaling your deployment](./007-scaling.md)
@@ -0,0 +1,96 @@
---
id: scaling
sidebar_label: 007 - Scaling
title: Scaling your deployment
description: How to scale Backstage as usage grows
---
Audience: Admins
## Summary
A single Backstage instance handles many users well, but as your organization
grows and more plugins are added, you may need to scale. This page covers the
strategies available.
## Horizontal scaling
The most straightforward approach is to run multiple identical instances of
Backstage behind a load balancer. All instances share the same external
database (and optional cache or search services). The backend plugins
coordinate through the database to share state and distribute work.
In Kubernetes, this is as simple as increasing the replica count in your
deployment:
```yaml
spec:
replicas: 3
```
No additional configuration is needed. The database handles coordination
between instances.
## Splitting the backend
For larger installations, you can break the backend into multiple services,
each running a different set of plugins. For example, you might run the
catalog and scaffolder as separate deployments so that heavy catalog
processing does not affect scaffolder performance.
This is a more advanced approach that requires:
- Separate backend packages, each importing only the plugins they need.
- A custom `DiscoveryService` implementation that routes requests to the
correct backend based on the plugin ID.
- Routing both external (ingress) and internal (backend-to-backend) traffic
appropriately.
See the
[backend system documentation](../../backend-system/building-backends/01-index.md#split-into-multiple-backends)
for details on how to set this up.
## Separating the frontend
By default, the frontend is served from your backend deployment using the
`@backstage/plugin-app-backend` plugin. If you need to reduce load on the
backend or serve the frontend from a CDN for better performance, you can
deploy the frontend separately.
This involves:
1. Removing the `@backstage/plugin-app-backend` plugin from the backend.
2. Building the frontend as a static bundle.
3. Serving it from a separate container (for example, NGINX) or a static
hosting provider.
An example NGINX setup is available in the
[contrib/docker/frontend-with-nginx](https://github.com/backstage/backstage/blob/master/contrib/docker/frontend-with-nginx)
folder.
:::note
When serving the frontend separately, configuration is no longer injected by
the backend at runtime. You need to provide the correct configuration at
frontend build time.
:::
## When to scale
Here are some signals that indicate you should consider scaling:
- API response times are increasing.
- Catalog processing is falling behind (visible in the
`catalog.processing.duration` metric).
- Scaffolder tasks are queuing for longer than expected.
- Users report slow page loads.
Start with horizontal scaling (more replicas) before considering backend
splitting. It is simpler and handles most growth scenarios.
## Further reading
For more details on scaling strategies, see the
[Scaling Backstage Deployments](../../deployment/scaling.md) reference
documentation.
+38
View File
@@ -0,0 +1,38 @@
---
id: index
title: Deploying Backstage to production
description: A guided path for deploying your Backstage app to production
---
### Prerequisites
- You have completed the [create-app golden path](../create-app/index.md) and
have a working Backstage app.
- Your code is pushed to a source control management system (GitHub, GitLab,
etc.).
- You have a general understanding of how your company builds and deploys
software.
### What should I get out of this guide?
This guide walks through everything you need to get your Backstage instance
running in a production environment. By the end, you will have:
- A Docker image containing your Backstage app.
- A production database (PostgreSQL) connected to your deployment.
- A real authentication provider replacing the default guest login.
- A running deployment, whether on Kubernetes, ECS, or another platform.
- Monitoring and observability set up with OpenTelemetry.
- An understanding of how to scale your deployment as usage grows.
### Structure
We start with the Docker image since that is the foundation of any deployment.
Then we set up the two critical pre-deploy dependencies: a database and
authentication. After that, we walk through deploying to Kubernetes and discuss
other deployment options. Finally, we cover operational topics like
configuration management, monitoring, and scaling.
### Next steps
- [Building the Docker image](./001-docker.md)