How to Deploy Backstage on Kubernetes Step by Step
Deploying an internal developer portal requires a robust, scalable foundation that separates stateless application logic from persistent data layers. This guide provides a production-ready workflow for migrating from local development to a managed Kubernetes cluster. Before provisioning infrastructure, ensure your team has evaluated the broader Developer Portal Architecture & Frameworks landscape to align container orchestration choices with organizational security and compliance standards.
Context: Architecture & Prerequisites
Backstage operates as a stateless Node.js frontend and API layer backed by persistent storage for catalog metadata, software templates, and authentication sessions. Understanding how plugins, the catalog processor, and external identity providers interact is essential before containerization. A comprehensive review of the Backstage Architecture Deep Dive clarifies how to decouple plugin dependencies and manage database connections in distributed environments.
Prerequisites:
- Active Kubernetes cluster (v1.24+) with RBAC enabled
- Helm v3 CLI installed and configured
- Provisioned PostgreSQL instance (managed or self-hosted)
- Container registry with authenticated push/pull access
kubectlconfigured with cluster-admin or namespace-scoped privileges
Exact Configuration: Step-by-Step Deployment
1. Containerize the Application
Backstage requires a multi-stage build to strip development dependencies, compile TypeScript, and produce a lean production image.
FROM node:18-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn tsc && yarn build
FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package.json ./
RUN yarn install --production --frozen-lockfile
EXPOSE 7007
CMD ["node", "dist/packages/backend"]
2. Push Image & Tag
Build the image locally or in CI, then push it to your registry with a semantic version tag.
docker build -t registry.company.io/backstage:v1.2.0 .
docker push registry.company.io/backstage:v1.2.0
3. Define Helm Overrides
Create a values.yaml file to target the image, inject production environment variables, and configure TLS ingress. Reference database credentials via Kubernetes Secrets, never plaintext.
image:
repository: registry.company.io/backstage
tag: v1.2.0
pullPolicy: IfNotPresent
env:
- name: NODE_ENV
value: production
- name: BACKSTAGE_BASE_URL
value: https://backstage.company.io
ingress:
enabled: true
hosts:
- host: backstage.company.io
paths: ["/"]
tls:
- secretName: backstage-tls
hosts: ["backstage.company.io"]
4. Execute Deployment
Add the official Backstage Helm repository, create the namespace, and deploy. Ensure the PostgreSQL connection URI is injected via --set or a pre-created Secret.
helm repo add backstage https://backstage.github.io/charts
helm repo update
kubectl create namespace backstage
helm install backstage backstage/backstage \
-f values.yaml \
--namespace backstage \
--set postgresql.url="postgresql://user:pass@postgres-host:5432/backstage"
Validation: Health Checks & Rollout Verification
Immediately verify deployment health and routing before routing production traffic.
# 1. Confirm rolling update completion
kubectl rollout status deployment/backstage -n backstage
# 2. Validate database & catalog initialization
kubectl logs -l app.kubernetes.io/name=backstage -n backstage | grep -E "Database connection established|CatalogClient initialized"
# 3. Test frontend accessibility via ingress
curl -I https://backstage.company.io/health
# 4. Local debugging (if ingress is pending)
kubectl port-forward svc/backstage 7007:7007 -n backstage
# 5. Monitor resource consumption
kubectl top pods -n backstage
Rollback Strategy: If health checks fail or pods enter CrashLoopBackOff, immediately revert to the previous stable release:
helm rollback backstage 1 -n backstage
Edge Cases: Production Hardening & Troubleshooting
| Symptom | Root Cause | Resolution |
|---|---|---|
| Plugin compilation fails | Missing native build tools in builder stage | Add RUN apk add --no-cache python3 make gcc g++ to the builder stage of your Dockerfile. |
| Catalog processor cannot read cluster resources | RBAC misconfiguration | Create a dedicated ServiceAccount and bind a minimal ClusterRole with get, list, watch permissions for required API groups. |
| Pod scheduling fails / PVC pending | Missing storageClassName or constrained nodes |
Explicitly define storageClassName in Helm values and configure volumeMounts with subPath to avoid node-local storage conflicts. |
| TLS handshake errors | Ingress controller lacks cert rotation | Deploy cert-manager with a ClusterIssuer, reference the generated secret in ingress.tls.secretName, and never expose raw DB ports externally. |
| OOMKilled during catalog sync | Missing resource limits | Define resources.requests and resources.limits in values.yaml. Start with cpu: 500m, memory: 1Gi and scale based on kubectl top pods metrics. |
Critical Pitfalls to Avoid:
- Running with
NODE_ENV=developmentin production causes memory leaks and disables asset optimization. - Hardcoding PostgreSQL credentials in
values.yamlviolates security baselines; always useSecretreferences. - Misconfiguring ingress path routing strips static assets; ensure the ingress controller proxies
/and/apicorrectly to the backend service.
Frequently Asked Questions
Can I deploy Backstage without an external PostgreSQL database? While SQLite works for local development, it is unsupported in production Kubernetes environments. PostgreSQL is required for concurrent catalog processing, template execution, and high-availability scaling.
How do I handle Backstage plugin updates on Kubernetes?
Rebuild the container image with updated dependencies, push a new tag, and update the Helm release using helm upgrade. Kubernetes will perform a rolling update with zero downtime if readiness probes are correctly configured.
Why does the Backstage pod crash with ‘EACCES: permission denied’ errors?
This typically occurs when the container runs as root but attempts to write to a read-only filesystem or restricted volume. Set securityContext.runAsUser: 1000 in the Helm values and ensure WORKDIR has appropriate write permissions.
Is it necessary to run a separate frontend and backend deployment? No. The official Backstage architecture bundles the frontend static assets and backend API into a single Node.js process. Deploying them together simplifies ingress routing, session management, and scaling.