Skip to content
Learni
View all tutorials
DevOps

How to Implement Platform Engineering with Backstage in 2026

Lire en français

Introduction

Platform Engineering has emerged as a key discipline in 2026, focused on building self-service Internal Developer Platforms (IDPs) for developers. Instead of wasting time on infrastructure, devs can focus on business code thanks to abstractions like ready-to-use templates, automated pipelines, and integrated observability.

Why is it crucial? According to Gartner studies, 80% of enterprises will adopt IDPs by 2026 to cut toil (manual tasks) by 50%. Backstage, developed by Spotify and adopted by Netflix/LinkedIn, is the go-to open-source tool: a unified portal for service catalogs, scaffolding, docs, and CI/CD.

This tutorial walks you step-by-step through creating a basic IDP with Backstage: local installation, advanced config with PostgreSQL, a custom TypeScript plugin for Terraform, and Docker deployment. At the end, you'll have a functional, scalable platform ready for Kubernetes. Ideal for intermediate DevOps engineers. (148 words)

Prerequisites

  • Node.js 20+ and Yarn 1.22+
  • Docker and Docker Compose
  • Basic knowledge of TypeScript and YAML
  • GitHub account for templates (optional)
  • Minimum 4 GB RAM for services

Creating the Backstage Project

terminal
npx @backstage/create-app@latest ./platform-idp --type nodejs --force
cd platform-idp
yarn install
yarn tsc

This command initializes a complete Backstage project with a Node.js backend, React frontend, and ready packages. The --force option overwrites existing files if needed. Avoid npm since Yarn is optimized for Backstage monorepos; test with yarn dev to verify.

Initial Configuration

Once the project is created, Backstage runs in a sandbox with SQLite. We'll switch to PostgreSQL for a production-like setup and enable essential plugins like the scaffolder (project generation) and TechDocs (auto-generated docs).

app-config.yaml Configuration

app-config.yaml
app:
  baseUrl: http://localhost:3000
  bindConfig:
    port: 3000
backend:
  baseUrl: http://localhost:7007
  listen:
    port: 7007
  cors:
    origin: http://localhost:3000
  database:
    client: pg
    connection:
      host: localhost
      port: 5432
      user: backstage
      password: backstage
      database: backstage
  auth:
    providers:
      github:
        clientId: ${GITHUB_CLIENT_ID}
        clientSecret: ${GITHUB_CLIENT_SECRET}
proxy:
  '/catalog':
    target: http://localhost:7007/catalog-provider
  '/scaffolder':
    target: http://localhost:7007/scaffolder
techdocs:
  builder: local
github:
  baseUrl: https://github.com
  token: ${GITHUB_TOKEN}

This file centralizes all config: PostgreSQL for persistence, proxy for internal microservices, GitHub auth, and local TechDocs. Replace env variables (GITHUB_*) with your credentials. Pitfall: without cors.origin, the frontend will block; always restart after changes.

Docker Compose for Services

docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:15
    environment:
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: backstage
      POSTGRES_DB: backstage
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
  backstage:
    build: .
    ports:
      - "3000:3000"
      - "7007:7007"
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_USER: backstage
      POSTGRES_PASSWORD: backstage
      POSTGRES_DATABASE: backstage
      GITHUB_TOKEN: ${GITHUB_TOKEN}
    depends_on:
      - postgres
volumes:
  postgres_data:

This Compose file orchestrates PostgreSQL and Backstage in containers. The build uses the project's Dockerfile. Run with docker compose up -d; expose env vars. Watch for the persistent volume to avoid data loss on restarts.

Launch and First Tests

Start with yarn dev locally or docker compose up. Access http://localhost:3000. Create a user, import a GitHub repo via Catalog. Test the scaffolder by generating a 'guestbook' project.

Custom TypeScript Plugin for Terraform

packages/backend/src/plugins/terraform.ts
import { createRouter } from '@backstage/router';
import { PluginEnvironment } from '@backstage/backend-common';
import express from 'express';

export async function createRouterTerraform(env: PluginEnvironment) {
  const router = createRouter();

  router.get('/provision', async (req, res) => {
    const { clusterName } = req.query;
    // Simule appel Terraform CLI ou API
    const output = {
      status: 'provisioned',
      cluster: clusterName,
      endpoints: [`https://${clusterName}.example.com`]
    };
    res.json(output);
  });

  return router;
}

// Dans index.ts du backend :
// router.use('/api/terraform', await createRouterTerraform(env));

This plugin adds a /api/terraform/provision API to simulate IaC provisioning. Integrate it into packages/backend/src/index.ts. It's extensible to real terraform apply execution. Pitfall: handle async errors with try/catch to prevent backend crashes.

Terraform Scaffolder Template

templates/terraform-aws/default.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: terraform-aws-vpc
  title: AWS VPC Terraform
  description: Déploie une VPC AWS via Terraform
  tags:
    - terraform
    - aws
  owner: platform-team
spec:
  owner: platform-team
  type: service
  parameters:
    - title: Nom VPC
      type: string
      ui:widget: text
      required: true
      validation:
        $$js: "((value) => /^[a-zA-Z0-9-]+$/.test(value))"
  steps:
    - id: terraform-init
      name: Init Terraform
      action: debug:log
      input:
        message: "Provisioning VPC ${{ parameters.vpcName }}"
    - id: publish
      name: Publier template
      action: publish:github
      input:
        repoUrl: github.com?repo=platform-idp&branch=main
        defaultBranch: true
  output:
    links:
      - title: Terraform Plan
        url: https://console.aws.amazon.com
        icon: cloud

This YAML template lets devs generate a Terraform VPC project via the UI. It validates input and publishes to GitHub. Place it in templates/ and import into Catalog. Avoid steps without a valid action to prevent scaffolding failures.

Production Deployment with Helm

terminal-helm
helm repo add backstage https://backstage.github.io/charts
helm repo update
helm install backstage backstage/backstage \
  --namespace backstage --create-namespace \
  --set backend.extraEnv.GITHUB_TOKEN=$GITHUB_TOKEN \
  --set postgresql.auth.postgresPassword=backstage

This script deploys Backstage to Kubernetes using the official Helm chart. Ensure your kubectl context is set. Customize with your secrets; monitor pods with kubectl logs. Common pitfall: forgetting the namespace causes port conflicts.

Best Practices

  • Secure secrets: Use Vault or Kubernetes Secrets instead of plain env vars.
  • Monitor with Prometheus: Add the Backstage plugin for Grafana dashboards.
  • GitOps everything: Store YAML configs in Git and apply via ArgoCD.
  • Horizontal scaling: Set backend replicas >1 for >50 users.
  • Strict RBAC: Limit scaffolding templates to authorized teams.

Common Errors to Avoid

  • Non-persistent DB: Without Docker volumes, Catalog data is lost on restart.
  • Misconfigured CORS: Frontend/backend on different ports → 401 errors; check app-config.cors.
  • Unrecompiled plugins: After TS additions, run yarn tsc && yarn build.
  • Templates without validation: Free inputs cause Terraform errors; add $$js regex.

Next Steps

Dive deeper with the Backstage docs. Integrate Crossplane for true self-service IaC. Check out our Learni Platform Engineering courses for Kubernetes + Backstage masterclasses in enterprise settings.

How to Implement Platform Engineering with Backstage 2026 | Learni