1. Apa itu Backstage?
Backstage adalah open-source platform dari Spotify (sekarang CNCF) untuk membangun Internal Developer Portal (IDP). Backstage menyatukan tooling, layanan, dokumentasi, dan infrastruktur dalam satu portal yang meningkatkan developer experience.
- Centralized Developer Experience: Semua tool di satu tempat
- Service Discovery: Cari tahu owner, deploy status, monitoring
- Self-service: Developer buat service dari template
- Standardization: Best practices diterapkan konsisten
- Extensible: Plugin system fleksibel
2. Setup & Instalasi
# Prerequisites: Node.js 18+, yarn npx @backstage/create-app@latest my-portal cd my-portal yarn install yarn dev # Buka http://localhost:3000 # Project structure: # my-portal/ # ├── app-config.yaml # ├── packages/ # │ ├── app/ # Frontend React # │ └── backend/ # Backend Node.js # └── plugins/
app:
title: My Company Developer Portal
baseUrl: http://localhost:3000
organization:
name: My Company
backend:
baseUrl: http://localhost:7007
listen:
port: 7007
database:
client: better-sqlite3
connection: ':memory:'
# Production: gunakan PostgreSQL
# client: pg
# connection:
# host: ${POSTGRES_HOST}
# port: 5432
# user: ${POSTGRES_USER}
# password: ${POSTGRES_PASSWORD}
# database: backstage
auth:
providers:
github:
development:
clientId: ${GITHUB_CLIENT_ID}
clientSecret: ${GITHUB_CLIENT_SECRET}
catalog:
rules:
- allow: [Component, System, API, Resource, Group, User, Template]
locations:
- type: file
target: ../catalog-model/examples/all.yaml
- type: github-discovery
target: https://github.com/my-org/blob/main/catalog-info.yaml
integrations:
github:
- host: github.com
token: ${GITHUB_TOKEN}
3. Software Catalog
Software Catalog adalah jantung Backstage — registry terpusat semua service, library, API, dan resource. Setiap repo mendefinisikan catalog-info.yaml.
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: user-service
description: Service autentikasi dan manajemen user
annotations:
github.com/project-slug: my-org/user-service
argocd/app-name: user-service
jira/project-key: USER
backstage.io/techdocs-ref: dir:.
backstage.io/kubernetes-id: user-service
sonarqube.org/project-key: my-org_user-service
tags: [go, grpc, production]
links:
- url: https://grafana.example.com/d/user-service
title: Grafana Dashboard
icon: dashboard
- url: https://github.com/my-org/user-service
title: Source Code
icon: github
labels:
tier: critical
team: platform
spec:
type: service
lifecycle: production
owner: group:team-platform
system: auth-system
providesApis:
- user-api
dependsOn:
- component:postgres-database
consumesApis:
- notification-api
3.1 Entity Types di Backstage
| Kind | Fungsi | Contoh |
|---|---|---|
Component | Service, library, website | user-api, shared-utils |
API | API specification | REST API, gRPC |
Resource | Infrastructure | Database, S3 bucket |
System | Grup related components | payment-system |
Domain | Business domain | user-domain |
Group | Team/org unit | team-platform |
User | Individual person | developer accounts |
# API Definition
apiVersion: backstage.io/v1alpha1
kind: API
metadata:
name: user-api
description: REST API untuk manajemen user
spec:
type: openapi
lifecycle: production
owner: group:team-platform
system: auth-system
definition: |
openapi: "3.0.0"
info:
version: "1.0.0"
title: User API
paths:
/users:
get:
summary: List all users
responses:
'200':
description: List of users
/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: User details
---
# System Definition
apiVersion: backstage.io/v1alpha1
kind: System
metadata:
name: auth-system
description: Autentikasi dan authorisasi
tags: [auth, security]
spec:
owner: group:team-platform
domain: user-domain
---
# Domain Definition
apiVersion: backstage.io/v1alpha1
kind: Domain
metadata:
name: user-domain
description: Semua terkait user management
spec:
owner: group:team-platform
---
# Group Definition
apiVersion: backstage.io/v1alpha1
kind: Group
metadata:
name: team-platform
description: Platform Engineering Team
spec:
type: team
profile:
displayName: Platform Team
email: platform@company.com
children: []
members: [user:jane, user:john]
---
# User Definition
apiVersion: backstage.io/v1alpha1
kind: User
metadata:
name: jane
spec:
profile:
displayName: Jane Doe
email: jane@company.com
memberOf: [group:team-platform]
Gunakan github-discovery untuk otomatis menemukan semua repo yang memiliki catalog-info.yaml. Ini menghilangkan kebutuhan registrasi manual — cukup tambahkan file catalog-info.yaml ke repo dan Backstage mendeteksinya.
4. TechDocs
TechDocs memungkinkan dokumentasi yang di-commit langsung bersama kode menggunakan MkDocs.
# mkdocs.yml di root repository site_name: User Service Documentation nav: - Home: index.md - Getting Started: getting-started.md - API Reference: api-reference.md - Architecture: architecture.md - Runbook: runbook.md plugins: - techdocs-core # Tambahkan annotation di catalog-info.yaml: # metadata: # annotations: # backstage.io/techdocs-ref: dir:. # Struktur docs/ # docs/ # ├── index.md # ├── getting-started.md # ├── api-reference.md # ├── architecture.md # └── runbook.md # Build locally: mkdocs serve # Build untuk Backstage: npx @backstage/cli techdocs:build
# User Service ## Overview User Service menangani autentikasi dan manajemen user. Menyediakan REST API untuk registrasi, login, dan CRUD. ## Architecture ```mermaid graph LR Client --> Gateway[API Gateway] Gateway --> UserService[User Service] UserService --> Postgres[(PostgreSQL)] UserService --> Redis[(Redis Cache)] UserService --> Kafka[Event Bus] ``` ## Quick Start ### Prerequisites - Go 1.22+, PostgreSQL 15+, Redis 7+ ### Running Locally ```bash git clone https://github.com/my-org/user-service.git cd user-service cp .env.example .env docker-compose up -d postgres redis go run cmd/migrate/main.go up go run cmd/server/main.go ``` ## Environment Variables | Variable | Description | Default | |----------|-------------|---------| | DB_HOST | PostgreSQL host | localhost | | DB_PORT | PostgreSQL port | 5432 | | REDIS_URL | Redis URL | redis://localhost:6379 | | JWT_SECRET | JWT signing secret | (required) | ## Monitoring - Grafana Dashboard: https://grafana.example.com/d/user-service - PagerDuty: https://pagerduty.com/user-service - Logs: https://grafana.example.com/explore?query=user-service
5. Scaffolder Templates
Scaffolder memungkinkan developer membuat service baru dari template. Mendefinisikan steps yang dijalankan untuk meng-generate, register, dan deploy service.
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
name: create-node-microservice
title: Node.js Microservice
description: Buat Node.js microservice baru dengan standar perusahaan
tags: [nodejs, microservice, recommended]
annotations:
backstage.io/time: '3 minutes'
spec:
owner: group:team-platform
type: service
parameters:
- title: Service Information
required: [name, description, owner]
properties:
name:
title: Service Name
type: string
pattern: '^[a-z][a-z0-9-]*[a-z0-9]$'
examples: [user-service, payment-gateway]
description:
title: Description
type: string
owner:
title: Owner Team
type: string
ui:field: OwnerPicker
system:
title: System
type: string
ui:field: EntityPicker
ui:options:
catalogFilter:
- kind: System
- title: Technology Options
properties:
database:
title: Database
type: string
enum: [none, postgres, mysql, mongodb]
default: postgres
ci:
title: CI/CD
type: string
enum: [github-actions, gitlab-ci]
default: github-actions
steps:
- id: fetch
name: Fetch Template
action: fetch:template
input:
url: https://github.com/my-org/templates/node-microservice
values:
name: ${{ parameters.name }}
description: ${{ parameters.description }}
owner: ${{ parameters.owner }}
system: ${{ parameters.system }}
database: ${{ parameters.database }}
- id: publish
name: Publish to GitHub
action: github:repo:create
input:
repoUrl: 'github.com?repo=${{ parameters.name }}&owner=my-org'
description: ${{ parameters.description }}
visibility: private
- id: register
name: Register in Catalog
action: catalog:register
input:
repoContentsUrl: ${{ steps.publish.output.remoteUrl }}
catalogInfoPath: '/catalog-info.yaml'
- id: argocd
name: Create ArgoCD Application
action: argocd:create-app
input:
appName: ${{ parameters.name }}
output:
links:
- title: Repository
url: ${{ steps.publish.output.remoteUrl }}
- title: Catalog
entityRef: ${{ steps.register.output.entityRef }}
6. Plugin Ecosystem
Backstage memiliki ekosistem plugin yang kaya untuk integrasi berbagai tool DevOps.
| Plugin | Fungsi | Package |
|---|---|---|
| Kubernetes | Status pods, deployments | @backstage/plugin-kubernetes |
| GitHub Actions | CI/CD pipeline status | @backstage/plugin-github-actions |
| Grafana | Embed dashboard | @k-phoen/backstage-plugin-grafana |
| ArgoCD | GitOps status | @roadiehq/backstage-plugin-argo-cd |
| SonarQube | Code quality | @backstage/plugin-sonarqube |
| Jira | Issue tracking | @backstage/plugin-jira |
| PagerDuty | On-call & incidents | @backstage/plugin-pagerduty |
# Install plugins
cd packages/app
yarn add @backstage/plugin-kubernetes
yarn add @backstage/plugin-github-actions
yarn add @k-phoen/backstage-plugin-grafana
# Register di packages/app/src/App.tsx
import { KubernetesPage } from '@backstage/plugin-kubernetes';
import { EntityGitHubActionsContent } from '@backstage/plugin-github-actions';
# Entity page: tab untuk component
# <EntityLayout>
# <EntityLayout.Route path="/" title="Overview">
# <EntityOverviewPage />
# </EntityLayout.Route>
# <EntityLayout.Route path="/ci-cd" title="CI/CD">
# <EntityGitHubActionsContent />
# </EntityLayout.Route>
# <EntityLayout.Route path="/kubernetes" title="K8s">
# <EntityKubernetesContent />
# </EntityLayout.Route>
# </EntityLayout>
# Backend plugin
# packages/backend/src/index.ts
backend.add(import('@backstage/plugin-kubernetes-backend'));
backend.add(import('@backstage/plugin-catalog-backend'));
7. RBAC & Authentication
# Custom Permission Policy
# packages/backend/src/plugins/permission.ts
import { PermissionPolicy } from '@backstage/plugin-permission-node';
class CustomPermissionPolicy implements PermissionPolicy {
async handle(request: PolicyQuery, user?: BackstageIdentityResponse) {
const identity = user?.identity;
// Admin: full access
if (identity?.ownershipEntityRef.includes('group:team-platform')) {
return { result: AuthorizeResult.ALLOW };
}
// Read access for all authenticated users
if (request.permission.attributes.action === 'read') {
return { result: AuthorizeResult.ALLOW };
}
// Write: only owner
return {
result: AuthorizeResult.CONDITIONAL,
conditions: {
rule: 'IS_OWNER',
params: { claims: identity?.ownershipEntityRef ?? [] },
},
};
}
}
# OAuth2/OIDC config (app-config.yaml)
# auth:
# providers:
# github:
# development:
# clientId: ${GITHUB_CLIENT_ID}
# clientSecret: ${GITHUB_CLIENT_SECRET}
# signIn:
# resolvers:
# - resolver: usernameMatchingUserEntityName
# Group ownership mapping
# team-platform di GitHub → group:team-platform di Backstage
# auth.providers.github.development:
# signIn.resolvers:
# - resolver: usernameMatchingUserEntityName
# - resolver: emailLocalPartMatchingUserEntityName
# - resolver: emailMatchingUserEntityAnnotation
8. Production Deployment
# Dockerfile
# Stage 1: Build
FROM node:18-bullseye-slim AS build
WORKDIR /app
COPY yarn.lock package.json ./
COPY packages/backend/package.json packages/backend/
COPY packages/app/package.json packages/app/
RUN yarn install --frozen-lockfile --production=false
COPY . .
RUN yarn tsc
RUN yarn build:backend
# Stage 2: Production
FROM node:18-bullseye-slim
WORKDIR /app
COPY --from=build /app/packages/backend/dist/bundle.tar.gz .
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
COPY --from=build /app/app-config.yaml .
ENV NODE_ENV=production
EXPOSE 7007
CMD ["node", "packages/backend"]
---
# Kubernetes Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: backstage
namespace: developer-portal
spec:
replicas: 2
selector:
matchLabels:
app: backstage
template:
metadata:
labels:
app: backstage
spec:
containers:
- name: backstage
image: my-registry/backstage:latest
ports:
- containerPort: 7007
env:
- name: APP_CONFIG_app_baseUrl
value: "https://developer-portal.example.com"
- name: GITHUB_TOKEN
valueFrom:
secretKeyRef:
name: backstage-secrets
key: github-token
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 100m
memory: 256Mi
livenessProbe:
httpGet:
path: /api/catalog/health
port: 7007
initialDelaySeconds: 30
readinessProbe:
httpGet:
path: /api/catalog/health
port: 7007
initialDelaySeconds: 10
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: backstage
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts: [developer-portal.example.com]
secretName: backstage-tls
rules:
- host: developer-portal.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: backstage
port:
number: 7007
Jangan gunakan SQLite untuk production. Gunakan PostgreSQL 15+ sebagai database backend. Konfigurasikan connection pooling dengan PgBouncer untuk performa optimal. Pastikan backup database dilakukan secara teratur.
9. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut: