A new engineer joins the Swiggy backend team. On day one, they need to create a new microservice. Without a developer platform, here is what actually happens: They open a Slack message to the platform team asking how to create a repository. The platform team replies two hours later with a link to a 40-page confluence doc. The engineer follows the doc, but half the steps are out of date. They open three more tickets — one for a Kubernetes namespace, one for an ArgoCD Application, one for secrets access. They spend two weeks asking questions, waiting for approvals, and manually stitching things together. By the time the service is running, they have forgotten why they started. With an Internal Developer Platform, the same engineer opens a web portal, fills out a form — service name, team, description — clicks Create, and gets back a fully configured GitHub repository, Kubernetes manifests, ArgoCD integration, and documentation scaffold. In ten minutes. Without talking to anyone. This capstone builds that portal. You will use the services and infrastructure from Capstones 1, 2, and 3 as the foundation. By the end, every service ever created will be visible in one place with its ownership, deployment status, and documentation — and creating a new one will be a self-service form. **What you are building:** ``` Backstage Portal (http://localhost:3000) │ ├── Software Catalog │ ├── Shows all services from Capstones 1-3 │ ├── Ownership, documentation, health status │ └── Live Kubernetes deployment status per service │ ├── Golden Path Template │ └── Developer fills a form → Backstage: │ 1. Creates GitHub repository │ 2. Adds Kubernetes manifests (Capstone 1 pattern) │ 3. Adds ArgoCD Application manifest (Capstone 3 pattern) │ 4. Registers service in the catalog │ All in under 10 minutes, zero manual steps │ └── TechDocs └── Auto-rendered docs from Markdown in each service repo ``` **After this capstone you will be able to:** * Explain the difference between a developer platform and a developer portal * Install and configure Backstage with GitHub integration * Build a software catalog that auto-discovers services from GitHub * Add Kubernetes integration so developers see live pod status inside Backstage * Write a Golden Path Template that creates a full service end-to-end * Set up TechDocs to render docs-as-code from each repository **Time to complete:** 4-5 hours. **What you need before starting:** * Capstones 1, 2, and 3 completed (or their outputs available) * Node.js 18+ and Yarn installed locally * A GitHub account with a Personal Access Token (repo + workflow scopes) * The EKS cluster from Capstone 2 (or any Kubernetes cluster) ---
### The difference between platform and portal The term **Internal Developer Platform** gets confused with **Internal Developer Portal** constantly. They are different things. The **platform** is the engine — the set of tools, infrastructure, APIs, and automation that actually deploys and runs services. This is everything from Capstones 1-3: Kubernetes, Terraform, ArgoCD, Prometheus, Grafana. It exists whether or not any human looks at it. The **portal** is the front door — the interface that developers use to interact with the platform. Backstage is a portal. It sits on top of the platform and makes it accessible to people who do not need to understand how it works underneath. Think of it like a restaurant. The platform is the kitchen — the stoves, the chefs, the supply chain, the recipes. The portal is the menu and the waiter. You do not need to know how the kitchen works to order food. You just read the menu and make a choice. Backstage is the menu. The goal of this capstone is to build a menu that is so good that engineers never need to go into the kitchen directly. ### Platform-as-product mindset The reason most internal platforms fail is that the platform team builds what they think developers need rather than what developers actually need. They create tools that are powerful but require expert knowledge to use. Then they wonder why nobody uses them. **Platform-as-product** means treating your developer platform like a product with real users (internal developers) and measuring adoption, satisfaction, and time-to-first-deployment the same way a product team measures conversion rates. This capstone puts that mindset into practice. You are not building infrastructure — you are building a product that makes other engineers more productive. ---
### What `create-app` does Backstage is a React and Node.js application that you run yourself. Unlike SaaS tools, you own the deployment. `npx @backstage/create-app@latest` scaffolds a complete Backstage application with a frontend, a backend API, and a plugin system ready for configuration. The scaffolded app comes with a catalog, TechDocs, and a basic template plugin pre-installed. You will configure them and add the Kubernetes plugin in later parts. ```bash ## Scaffold a new Backstage application ## This downloads the latest Backstage app template and sets up the project npx @backstage/create-app@latest ## When prompted: ## App name: devops-network-portal ## Database: SQLite (for local development — use PostgreSQL in production) cd devops-network-portal ## Install all dependencies yarn install ## Verify the app starts yarn dev ## Opens http://localhost:3000 (frontend) and http://localhost:7007 (backend) ``` > 💡 **Tip:** The first `yarn dev` takes 2-3 minutes to compile. After that, hot reload is fast. If you see a blank page, wait 30 seconds and refresh. ### Configure `app-config.yaml` The `app-config.yaml` file at the project root controls everything: authentication, GitHub integration, the Kubernetes plugin, and TechDocs. Here is the complete configuration for this capstone. ```yaml ## app-config.yaml ## Every section explained — do not skip the comments app: title: DevOps Network Portal baseUrl: http://localhost:3000 ## change to your domain in production backend: baseUrl: http://localhost:7007 listen: port: 7007 ## Database — SQLite for local dev, switch to PostgreSQL for production database: client: better-sqlite3 connection: ':memory:' cors: origin: http://localhost:3000 methods: [GET, HEAD, PATCH, POST, PUT, DELETE] credentials: true ## GitHub integration — reads catalog files and creates repositories integrations: github: - host: github.com ## Create a Personal Access Token at https://github.com/settings/tokens ## Scopes needed: repo, workflow, read:org token: ${GITHUB_TOKEN} ## read from environment variable — never hardcode ## Proxy — used by some plugins to reach external APIs securely proxy: '/prometheus/api': target: http://monitoring-kube-prometheus-prometheus.monitoring.svc:9090 ## TechDocs — docs-as-code, rendered from Markdown in each repo techdocs: builder: 'local' ## 'local' builds docs in Backstage; 'external' uses CI generator: runIn: 'docker' ## uses mkdocs inside Docker to build docs publisher: type: 'local' ## stores built docs locally; use GCS/S3 in production ## Authentication — GitHub OAuth so developers log in with their GitHub account auth: providers: github: development: clientId: ${AUTH_GITHUB_CLIENT_ID} clientSecret: ${AUTH_GITHUB_CLIENT_SECRET} ## Catalog — where Backstage discovers services catalog: import: entityFilename: catalog-info.yaml ## filename Backstage looks for in repos pullRequestBranchName: backstage-integration rules: ## Allow these entity types to be registered in the catalog - allow: [Component, System, API, Resource, Location, Group, User, Template, Domain] ## GitHub Discovery — scans all repositories for catalog-info.yaml ## This is the key setting that keeps the catalog up to date automatically providers: github: devops-network-org: organization: 'your-github-org' ## replace with your GitHub org name catalogPath: '/catalog-info.yaml' ## file Backstage looks for in each repo filters: branch: 'main' ## only scan the main branch repository: '.*' ## match all repositories schedule: frequency: { minutes: 30 } ## scan every 30 minutes timeout: { minutes: 3 } ## Kubernetes plugin — shows live cluster status inside each service page kubernetes: serviceLocatorMethod: type: 'multiTenant' clusterLocatorMethods: - type: 'config' clusters: - url: ${K8S_CLUSTER_URL} ## your EKS cluster API server URL name: production-eks authProvider: 'serviceAccount' skipTLSVerify: false serviceAccountToken: ${K8S_SERVICE_ACCOUNT_TOKEN} caData: ${K8S_CA_DATA} ## base64-encoded cluster CA cert ``` > 📌 **Remember:** Never put secrets directly in `app-config.yaml`. Use environment variables (`${VARIABLE_NAME}`) and a `.env` file locally. In production, use Kubernetes Secrets or AWS Parameter Store. ### Set environment variables ```bash ## Create a .env file for local development ## Add .env to .gitignore — never commit this file cat > .env << 'EOF' GITHUB_TOKEN=ghp_your_personal_access_token_here AUTH_GITHUB_CLIENT_ID=your_oauth_app_client_id AUTH_GITHUB_CLIENT_SECRET=your_oauth_app_client_secret K8S_CLUSTER_URL=https://your-eks-api-server.ap-south-1.eks.amazonaws.com K8S_SERVICE_ACCOUNT_TOKEN=eyJhbGciOiJSUzI1NiJ9... K8S_CA_DATA=LS0tLS1CRUdJTi... EOF ## Load env vars when starting the app export $(cat .env | xargs) && yarn dev ``` ---
### What the catalog is and why it matters The **software catalog** is a centralized inventory of every service, API, database, and team in your organization. Without it, answering questions like "who owns the payment service?", "what services depend on Redis?", or "which team should I contact when the order API is down?" requires Slack messages, digging through GitHub, or tribal knowledge. With a catalog, every service has a page showing its owner, dependencies, documentation, deployment status, and on-call contact — all in one place. At Razorpay, this means when a payment service incident fires at 2 AM, the on-call engineer can immediately see which team owns it, what it depends on, and jump directly to the runbook. ### Register the swiggy-clone backend from Capstone 1 The catalog reads from a file called `catalog-info.yaml` that lives in each repository. Here is the complete file for the backend service from Capstone 1. ```yaml ## catalog-info.yaml ## Place this file at the root of the swiggy-clone repository ## Backstage reads this to register the service in the catalog apiVersion: backstage.io/v1alpha1 kind: Component ## Component = a deployable unit (service, library, website) metadata: name: swiggy-clone-backend description: Node.js backend API for the swiggy-clone application annotations: ## Links this component to the GitHub repository github.com/project-slug: your-org/swiggy-clone ## Links this component to Kubernetes resources ## Backstage will show pods, deployments, and health for this label selector backstage.io/kubernetes-label-selector: 'app=backend' ## Links to TechDocs — the docs/ folder in this repo backstage.io/techdocs-ref: dir:. ## Links to Prometheus metrics for this service prometheus.io/alert: 'All' tags: - nodejs - backend - api - postgresql links: - url: https://swiggy-clone.your-domain.com/api title: Production API icon: web - url: https://grafana.your-domain.com/d/backend title: Grafana Dashboard icon: dashboard spec: type: service ## type can be: service, library, website, documentation lifecycle: production ## lifecycle can be: production, experimental, deprecated owner: group:backend-team ## the team that owns this service ## Services this component depends on dependsOn: - resource:default/swiggy-postgres - resource:default/swiggy-redis - component:default/swiggy-clone-frontend ## APIs this component provides providesApis: - swiggy-clone-api ``` ```yaml ## catalog-info.yaml for the team that owns the service ## Usually lives in a central 'org-catalog' repository apiVersion: backstage.io/v1alpha1 kind: Group metadata: name: backend-team description: The team that builds and operates the swiggy-clone backend spec: type: team profile: displayName: Backend Team email: backend-team@devops-network.in parent: engineering ## parent group (department) children: [] ## sub-teams, if any members: - rahul-sharma - priya-nair - arjun-kumar --- apiVersion: backstage.io/v1alpha1 kind: Resource metadata: name: swiggy-postgres description: PostgreSQL database for the swiggy-clone service annotations: backstage.io/managed-by-location: url:https://github.com/your-org/swiggy-clone/blob/main/catalog-info.yaml spec: type: database owner: group:backend-team dependencyOf: - component:default/swiggy-clone-backend ``` ### Configure GitHub auto-discovery Manually registering every service by copying catalog-info.yaml URLs does not scale. Auto-discovery scans your entire GitHub organization on a schedule and registers any repository that has a `catalog-info.yaml` file. ```bash ## Install the GitHub discovery provider plugin cd packages/backend yarn add @backstage/plugin-catalog-backend-module-github ## Register the plugin in packages/backend/src/index.ts ## Add this line to the existing plugin registration section: ``` ```typescript // packages/backend/src/index.ts // Add this import at the top import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; // In the createRouter function, add the provider: const builder = await CatalogBuilder.create(env); // This line enables auto-discovery from GitHub // It reads the 'catalog.providers.github' section from app-config.yaml builder.addEntityProvider( GithubEntityProvider.fromConfig(env.config, { logger: env.logger, scheduler: env.scheduler, ## runs the scan on the schedule you configured }), ); ``` ### Verify: find a service in the catalog ```bash ## Start Backstage and verify the catalog is working export $(cat .env | xargs) && yarn dev ## Open http://localhost:3000/catalog ## You should see the swiggy-clone-backend component appear ## Click it — you should see: ## - Overview tab: owner, lifecycle, dependencies ## - CI/CD tab: empty for now (add GitHub Actions integration later) ## - Kubernetes tab: live pod status (after Part 4) ## - Docs tab: TechDocs (after Part 6) ## If the service does not appear after 2 minutes: ## Check the backend logs — look for GitHub discovery errors ## Verify the catalog-info.yaml is on the main branch of the repository ``` ---
### What the Kubernetes plugin does Without the Kubernetes plugin, a developer who wants to check if their service is healthy needs to either ask the platform team for kubectl access or open a terminal and run commands. This is a barrier. Most developers do not have or want cluster access — they just want to know if their pods are running. The Kubernetes plugin in Backstage shows live deployment status, pod count, replica health, and recent events for every service — directly on the service's catalog page. No kubectl. No cluster access required. The developer sees exactly what they need: is it running, how many replicas, any crashes? ### Install the Kubernetes plugin ```bash ## Install the frontend plugin (shows Kubernetes data on catalog pages) cd packages/app yarn add @backstage/plugin-kubernetes ## Install the backend plugin (fetches data from the cluster) cd packages/backend yarn add @backstage/plugin-kubernetes-backend ``` ```typescript // packages/app/src/components/catalog/EntityPage.tsx // Add the Kubernetes tab to the service entity page import { EntityKubernetesContent, isKubernetesAvailable, } from '@backstage/plugin-kubernetes'; // Find the serviceEntityPage const and add this tab: const serviceEntityPage = ( <EntityLayout> <EntityLayout.Route path="/" title="Overview"> {overviewContent} </EntityLayout.Route> {/* This tab appears only if the component has kubernetes annotations */} <EntityLayout.Route path="/kubernetes" title="Kubernetes" if={isKubernetesAvailable} > <EntityKubernetesContent refreshIntervalMs={30000} /> </EntityLayout.Route> <EntityLayout.Route path="/docs" title="Docs"> <EntityTechdocsContent /> </EntityLayout.Route> </EntityLayout> ); ``` ```typescript // packages/backend/src/index.ts // Register the Kubernetes backend plugin import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend'; // Add inside the createRouter function: const { router: kubernetesRouter } = await KubernetesBuilder.createRouter({ logger: env.logger, config: env.config, permissions: env.permissions, }); apiRouter.use('/kubernetes', kubernetesRouter); ``` ### Create a service account for Backstage to read the cluster Backstage needs read-only access to your EKS cluster to show pod status. Never give it admin access. ```yaml ## k8s/backstage-reader.yaml ## Apply this to your EKS cluster from Capstone 2 apiVersion: v1 kind: ServiceAccount metadata: name: backstage-reader namespace: default --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: backstage-reader rules: ## Read-only access to the resources Backstage needs to display - apiGroups: [""] resources: ["pods", "services", "endpoints", "namespaces", "nodes"] verbs: ["get", "list", "watch"] - apiGroups: ["apps"] resources: ["deployments", "replicasets", "statefulsets", "daemonsets"] verbs: ["get", "list", "watch"] - apiGroups: ["autoscaling"] resources: ["horizontalpodautoscalers"] verbs: ["get", "list", "watch"] - apiGroups: ["batch"] resources: ["jobs", "cronjobs"] verbs: ["get", "list", "watch"] - apiGroups: ["argoproj.io"] ## Argo Rollouts status from Capstone 3 resources: ["rollouts"] verbs: ["get", "list", "watch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: backstage-reader roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: backstage-reader subjects: - kind: ServiceAccount name: backstage-reader namespace: default ``` ```bash ## Apply the RBAC configuration kubectl apply -f k8s/backstage-reader.yaml ## Extract the service account token for app-config.yaml ## Kubernetes 1.24+ requires explicit token creation kubectl apply -f - << 'EOF' apiVersion: v1 kind: Secret metadata: name: backstage-reader-token namespace: default annotations: kubernetes.io/service-account.name: backstage-reader type: kubernetes.io/service-account-token EOF ## Get the token and CA data K8S_SERVICE_ACCOUNT_TOKEN=$(kubectl get secret backstage-reader-token \ -o jsonpath='{.data.token}' | base64 -d) K8S_CA_DATA=$(kubectl get secret backstage-reader-token \ -o jsonpath='{.data.ca\.crt}') K8S_CLUSTER_URL=$(kubectl cluster-info | grep 'Kubernetes control plane' | awk '{print $NF}') echo "Token: ${K8S_SERVICE_ACCOUNT_TOKEN}" echo "CA Data: ${K8S_CA_DATA}" echo "Cluster URL: ${K8S_CLUSTER_URL}" ## Add these to your .env file ``` ### What developers see After this part is complete, a developer who opens the swiggy-clone-backend page in Backstage will see a **Kubernetes** tab showing: * Live pod count: 5/5 Running * Deployment health: Healthy * Most recent pod start times * Any pods in CrashLoopBackOff or OOMKilled state highlighted in red * The current container image tag — so they know which version is deployed They get visibility without needing cluster access. Platform engineers control what can be seen and done through RBAC. This is the principle of least privilege applied to developer experience. ---
### What a Golden Path Template is A **Golden Path Template** is a pre-approved, opinionated way to do something. In Backstage, it is a form that a developer fills out, and clicking Create triggers a sequence of automated steps — creating a repository, adding configuration files, registering the service in the catalog. The name comes from a concept used at Netflix and Spotify: instead of telling developers "you can do anything," you create a golden path — a smooth, well-paved road that handles 80% of use cases perfectly. Developers can go off-road if they need to, but the golden path is so good that most do not need to. This template will create a production-ready Node.js service that follows every pattern from Capstones 1-3. A developer fills in four fields. Ten minutes later, their service is in GitHub, in the catalog, and ArgoCD is watching it. ### The Software Template YAML ```yaml ## templates/nodejs-microservice/template.yaml ## This file lives in your GitOps repository from Capstone 3 ## It defines the form AND the automation steps apiVersion: scaffolder.backstage.io/v1beta3 kind: Template metadata: name: nodejs-microservice title: Node.js Microservice description: Creates a production-ready Node.js service with Kubernetes, ArgoCD, and TechDocs configured tags: - nodejs - kubernetes - gitops - recommended ## shows "recommended" badge in the template gallery spec: owner: group:platform-team type: service ## ─── Step 1: The form developers fill in ───────────────────── parameters: - title: Service Details required: - name - description - owner properties: name: title: Service Name type: string description: Lowercase, hyphen-separated (e.g. payment-service) pattern: '^[a-z][a-z0-9-]*[a-z0-9]$' ## validates the input format ui:autofocus: true description: title: Description type: string description: What does this service do? (shown in the catalog) owner: title: Owner Team type: string description: The team that owns this service ui:field: OwnerPicker ## shows a dropdown of teams from the catalog ui:options: allowedKinds: - Group - title: Infrastructure Settings required: - namespace - replicas properties: namespace: title: Kubernetes Namespace type: string description: Namespace to deploy into (e.g. payments-production) default: default replicas: title: Initial Replica Count type: integer description: How many pods to start with default: 2 minimum: 1 maximum: 10 environment: title: Target Environment type: string description: Which environment is this service for? enum: - staging - production default: staging ## ─── Step 2: The automation steps ───────────────────────────── steps: ## Step 2a: Copy the skeleton files into the new repository ## ${{ parameters.name }} substitutes the service name the developer typed - id: fetch-template name: Fetch Template Files action: fetch:template input: url: ./skeleton ## the skeleton/ directory next to this template.yaml values: name: ${{ parameters.name }} description: ${{ parameters.description }} owner: ${{ parameters.owner }} namespace: ${{ parameters.namespace }} replicas: ${{ parameters.replicas }} destination: ${{ parameters.repoUrl | parseRepoUrl }} ## Step 2b: Create the GitHub repository and push the skeleton files - id: publish-github name: Create GitHub Repository action: publish:github input: allowedHosts: ['github.com'] description: ${{ parameters.description }} repoUrl: github.com?owner=your-org&repo=${{ parameters.name }} repoVisibility: private defaultBranch: main ## Pre-populate the repository with topics for discoverability topics: - nodejs - kubernetes - ${{ parameters.environment }} ## Step 2c: Register the new service in the Backstage catalog - id: register-catalog name: Register in Catalog action: catalog:register input: ## The catalog-info.yaml created by the skeleton in the new repo repoContentsUrl: ${{ steps['publish-github'].output.repoContentsUrl }} catalogInfoPath: '/catalog-info.yaml' ## ─── Step 3: Links shown after creation ───────────────────────── output: links: - title: GitHub Repository url: ${{ steps['publish-github'].output.remoteUrl }} - title: Open in Catalog icon: catalog entityRef: ${{ steps['register-catalog'].output.entityRef }} ``` ### The skeleton directory The skeleton is the set of files that get copied into the new repository. Template variables like `${{ values.name }}` are replaced with the values the developer entered in the form. ```yaml ## templates/nodejs-microservice/skeleton/catalog-info.yaml ## Pre-filled with the values from the form apiVersion: backstage.io/v1alpha1 kind: Component metadata: name: ${{ values.name }} description: ${{ values.description }} annotations: github.com/project-slug: your-org/${{ values.name }} backstage.io/kubernetes-label-selector: 'app=${{ values.name }}' backstage.io/techdocs-ref: dir:. tags: - nodejs - backend spec: type: service lifecycle: experimental ## new services start as experimental owner: ${{ values.owner }} ``` ```yaml ## templates/nodejs-microservice/skeleton/k8s/deployment.yaml ## Follows the exact pattern from Capstone 1 apiVersion: apps/v1 kind: Deployment metadata: name: ${{ values.name }} namespace: ${{ values.namespace }} labels: app: ${{ values.name }} app.kubernetes.io/managed-by: backstage-golden-path spec: replicas: ${{ values.replicas }} selector: matchLabels: app: ${{ values.name }} template: metadata: labels: app: ${{ values.name }} annotations: prometheus.io/scrape: "true" prometheus.io/port: "4000" prometheus.io/path: "/metrics" spec: securityContext: runAsNonRoot: true runAsUser: 1001 containers: - name: ${{ values.name }} image: ghcr.io/your-org/${{ values.name }}:latest ## CI fills the tag ports: - containerPort: 4000 resources: requests: cpu: "100m" memory: "128Mi" limits: cpu: "500m" memory: "512Mi" livenessProbe: httpGet: path: /health/live port: 4000 initialDelaySeconds: 15 periodSeconds: 10 readinessProbe: httpGet: path: /health/ready port: 4000 initialDelaySeconds: 10 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: ${{ values.name }}-service namespace: ${{ values.namespace }} spec: selector: app: ${{ values.name }} ports: - name: http port: 4000 targetPort: 4000 ``` ```yaml ## templates/nodejs-microservice/skeleton/k8s/hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: ${{ values.name }}-hpa namespace: ${{ values.namespace }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: ${{ values.name }} minReplicas: ${{ values.replicas }} maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70 ``` ```yaml ## templates/nodejs-microservice/skeleton/argocd/application.yaml ## ArgoCD Application manifest — follows the Capstone 3 pattern ## Once this file is in the repo, ArgoCD auto-deploys the service apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: ${{ values.name }}-${{ values.environment }} namespace: argocd finalizers: - resources-finalizer.argocd.argoproj.io spec: project: default source: repoURL: https://github.com/your-org/${{ values.name }} targetRevision: HEAD path: k8s/ destination: server: https://kubernetes.default.svc namespace: ${{ values.namespace }} syncPolicy: automated: prune: true selfHeal: true syncOptions: - CreateNamespace=true ``` ```dockerfile ## templates/nodejs-microservice/skeleton/Dockerfile ## Multi-stage build — follows the Capstone 1 pattern FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci --only=production FROM node:20-alpine AS production ## Create non-root user for security (matches runAsUser: 1001 in k8s manifest) RUN addgroup -g 1001 -S appgroup && \ adduser -u 1001 -S appuser -G appgroup WORKDIR /app COPY --from=builder /app/node_modules ./node_modules COPY --chown=appuser:appgroup . . USER appuser EXPOSE 4000 CMD ["node", "src/index.js"] ``` ```markdown <!-- templates/nodejs-microservice/skeleton/docs/index.md --> # ${{ values.name }} ${{ values.description }} ## Overview This service was created using the DevOps Network golden path template. ## Architecture <!-- Add architecture diagram here --> ## API Reference | Endpoint | Method | Description | |----------|--------|-------------| | `/health/live` | GET | Liveness probe — returns 200 if the process is running | | `/health/ready` | GET | Readiness probe — returns 200 if the service can handle traffic | | `/metrics` | GET | Prometheus metrics endpoint | ## Running Locally ```bash npm install npm run dev ``` ## Deployment This service is deployed automatically via ArgoCD. To deploy a new version: 1. Push to main branch 2. CI builds and pushes the Docker image 3. Update the image tag in k8s/deployment.yaml 4. ArgoCD detects the change and deploys within 3 minutes ## Ownership Owner: ${{ values.owner }} ``` ### Register the template in Backstage ```yaml ## catalog-info.yaml in the templates repository root ## This registers all templates so Backstage shows them in Create > Templates apiVersion: backstage.io/v1alpha1 kind: Location metadata: name: devops-network-templates description: Golden path templates for DevOps Network spec: targets: - ./nodejs-microservice/template.yaml ## add more templates here as you build them ``` ```bash ## Register the templates location in Backstage ## Open http://localhost:3000/catalog-import ## Paste the URL of the catalog-info.yaml above: ## https://github.com/your-org/backstage-templates/blob/main/catalog-info.yaml ## Click Analyze → Import ## Or register via CLI: curl -X POST http://localhost:7007/api/catalog/locations \ -H "Content-Type: application/json" \ -d '{"type": "url", "target": "https://github.com/your-org/backstage-templates/blob/main/catalog-info.yaml"}' ``` ### The complete developer experience Here is exactly what a developer sees and does: 1. Open `http://localhost:3000` and sign in with GitHub 2. Click **Create** in the left sidebar 3. See the Node.js Microservice template with a "Recommended" badge 4. Click **Choose** on the template 5. Fill in the form: Service Name = `notification-service`, Description = `Sends push notifications for order updates`, Owner = `backend-team`, Namespace = `notifications-staging`, Replicas = `2` 6. Click **Review** — see a summary of what will be created 7. Click **Create** 8. Watch the steps complete in real time: Fetch Template → Create GitHub Repository → Register in Catalog 9. Click **Open in Catalog** — the notification-service page is live in Backstage 10. Click **GitHub Repository** — the repository exists with all files populated 11. Wait 3 minutes — ArgoCD detects the `argocd/application.yaml` and starts deploying The developer never touched a terminal, never spoke to the platform team, never opened a Kubernetes manifest manually. This is the goal. ---
A new engineer joins the Swiggy backend team. On day one, they need to create a new microservice. Without a developer pl...
The difference between platform and portal The term Internal Developer Platform gets confused with Internal Developer Po...
What create-app does Backstage is a React and Node.js application that you run yourself. Unlike SaaS tools, you own the ...
What the catalog is and why it matters The software catalog is a centralized inventory of every service, API, database, ...
What the Kubernetes plugin does Without the Kubernetes plugin, a developer who wants to check if their service is health...
What a Golden Path Template is A Golden Path Template is a pre-approved, opinionated way to do something. In Backstage, ...
What docs-as-code means Docs-as-code is the practice of writing documentation in Markdown files that live in the same re...
---...
❌ Building the IDP without talking to developers first 💥 The platform team spends three months building a perfect Backs...
Catalog not showing services — GitHub discovery not running: Template failing during GitHub repository creation: Kuberne...
Four capstones. Four layers of a complete platform engineering stack. Here is how all four capstones connect: Capstone 1...
Aligns directly with DevOps, Site Reliability (SRE), and Platform Engineering job descriptions.