GitOps & Continuous Delivery

GitOps + Crossplane + Backstage

3 min read

The true power of platform engineering emerges when you integrate Backstage, Crossplane, and GitOps into a unified workflow. This lesson shows how to build an end-to-end self-service platform.

The Complete IDP Architecture

┌─────────────────────────────────────────────────────────┐
│                    DEVELOPER PORTAL                      │
│                      (Backstage)                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Catalog    │  │  Templates   │  │   TechDocs   │  │
│  └──────────────┘  └──────┬───────┘  └──────────────┘  │
│                           │                              │
└───────────────────────────┼──────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│                    GIT REPOSITORY                        │
│                  (Source of Truth)                       │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │ App Configs  │  │ Infra Claims │  │   Rollouts   │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                           │                              │
└───────────────────────────┼──────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│                       ArgoCD                             │
│                  (GitOps Controller)                     │
│                           │                              │
│          ┌────────────────┼────────────────┐            │
│          ▼                ▼                ▼            │
│   ┌────────────┐  ┌────────────┐  ┌────────────┐       │
│   │ App Deploy │  │ Crossplane │  │  Rollouts  │       │
│   │ (K8s)      │  │  Claims    │  │            │       │
│   └────────────┘  └─────┬──────┘  └────────────┘       │
│                         │                               │
└─────────────────────────┼───────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│                     CROSSPLANE                           │
│                 (Infrastructure)                         │
│                          │                               │
│          ┌───────────────┼───────────────┐              │
│          ▼               ▼               ▼              │
│     ┌─────────┐    ┌─────────┐    ┌─────────┐          │
│     │   AWS   │    │   GCP   │    │  Azure  │          │
│     └─────────┘    └─────────┘    └─────────┘          │
│                                                          │
└─────────────────────────────────────────────────────────┘

Backstage Template for Full-Stack Service

Create a template that provisions app, infrastructure, and GitOps:

# backstage-template-fullstack.yaml
apiVersion: scaffolder.backstage.io/v1beta3
kind: Template
metadata:
  name: create-fullstack-service
  title: Create Full-Stack Service
  description: |
    Creates a new service with:
    - Application code repository
    - PostgreSQL database (via Crossplane)
    - ArgoCD application for GitOps
    - Monitoring and alerts
  tags:
    - recommended
    - platform
spec:
  owner: platform-team
  type: service

  parameters:
    - title: Service Information
      required:
        - name
        - description
        - owner
      properties:
        name:
          title: Service Name
          type: string
          pattern: '^[a-z][a-z0-9-]*$'
        description:
          title: Description
          type: string
        owner:
          title: Owner
          type: string
          ui:field: OwnerPicker

    - title: Technical Configuration
      properties:
        language:
          title: Language
          type: string
          enum: [nodejs, python, go, java]
          default: nodejs
        framework:
          title: Framework
          type: string
          enum:
            - express
            - fastapi
            - gin
            - spring-boot
          default: express

    - title: Infrastructure
      properties:
        database:
          title: Database Size
          type: string
          enum: [small, medium, large]
          default: small
        environment:
          title: Environment
          type: string
          enum: [development, staging, production]
          default: development

  steps:
    # Step 1: Create application repository
    - id: create-repo
      name: Create Application Repository
      action: publish:github
      input:
        repoUrl: github.com?repo=${{ parameters.name }}&owner=acme
        description: ${{ parameters.description }}
        defaultBranch: main
        repoVisibility: internal

    # Step 2: Scaffold application code
    - id: scaffold-app
      name: Scaffold Application
      action: fetch:template
      input:
        url: ./templates/${{ parameters.language }}-${{ parameters.framework }}
        targetPath: ./app
        values:
          name: ${{ parameters.name }}
          description: ${{ parameters.description }}
          owner: ${{ parameters.owner }}

    # Step 3: Create Crossplane claim for database
    - id: create-database
      name: Create Database
      action: kubernetes:apply
      input:
        namespaced: true
        manifest:
          apiVersion: platform.acme.com/v1alpha1
          kind: Database
          metadata:
            name: ${{ parameters.name }}-db
            namespace: ${{ parameters.environment }}
            labels:
              app: ${{ parameters.name }}
              owner: ${{ parameters.owner }}
          spec:
            size: ${{ parameters.database }}
            engine: postgres
            writeConnectionSecretToRef:
              name: ${{ parameters.name }}-db-credentials

    # Step 4: Add to GitOps repository
    - id: gitops-pr
      name: Create GitOps Configuration
      action: publish:github:pull-request
      input:
        repoUrl: github.com?repo=gitops-repo&owner=acme
        branchName: add-${{ parameters.name }}
        title: "Add ${{ parameters.name }} to GitOps"
        description: |
          Adding new service: ${{ parameters.name }}
          Owner: ${{ parameters.owner }}
          Environment: ${{ parameters.environment }}
        targetPath: apps/overlays/${{ parameters.environment }}/${{ parameters.name }}
        commitMessage: "feat: add ${{ parameters.name }} application"

    # Step 5: Create ArgoCD Application
    - id: create-argocd-app
      name: Create ArgoCD Application
      action: kubernetes:apply
      input:
        namespaced: true
        manifest:
          apiVersion: argoproj.io/v1alpha1
          kind: Application
          metadata:
            name: ${{ parameters.name }}
            namespace: argocd
            labels:
              owner: ${{ parameters.owner }}
          spec:
            project: ${{ parameters.owner }}
            source:
              repoURL: https://github.com/acme/gitops-repo.git
              path: apps/overlays/${{ parameters.environment }}/${{ parameters.name }}
              targetRevision: main
            destination:
              server: https://kubernetes.default.svc
              namespace: ${{ parameters.environment }}
            syncPolicy:
              automated:
                prune: true
                selfHeal: true

    # Step 6: Register in catalog
    - id: register
      name: Register in Catalog
      action: catalog:register
      input:
        repoContentsUrl: ${{ steps['create-repo'].output.repoContentsUrl }}
        catalogInfoPath: /catalog-info.yaml

  output:
    links:
      - title: Repository
        url: ${{ steps['create-repo'].output.remoteUrl }}
      - title: GitOps PR
        url: ${{ steps['gitops-pr'].output.pullRequestUrl }}
      - title: View in Catalog
        icon: catalog
        entityRef: ${{ steps['register'].output.entityRef }}

GitOps Repository Structure

gitops-repo/
├── apps/
│   ├── base/
│   │   └── user-service/
│   │       ├── deployment.yaml
│   │       ├── service.yaml
│   │       ├── rollout.yaml        # Argo Rollout
│   │       └── kustomization.yaml
│   └── overlays/
│       ├── development/
│       │   └── user-service/
│       │       ├── database-claim.yaml  # Crossplane
│       │       └── kustomization.yaml
│       ├── staging/
│       └── production/
├── infrastructure/
│   ├── crossplane/
│   │   ├── xrds/
│   │   │   └── database-xrd.yaml
│   │   ├── compositions/
│   │   │   ├── database-aws.yaml
│   │   │   └── database-gcp.yaml
│   │   └── provider-configs/
│   └── argocd/
│       ├── projects/
│       └── applicationsets/
└── clusters/
    └── production/
        ├── apps.yaml
        └── infrastructure.yaml

Unified Application Manifest

Combine app deployment with Rollout and infrastructure:

# apps/base/user-service/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - rollout.yaml
  - service.yaml
  - service-canary.yaml
  - ingress.yaml
# apps/overlays/production/user-service/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../../base/user-service
  - database-claim.yaml      # Crossplane claim
  - analysis-template.yaml   # Argo Rollouts analysis
patches:
  - path: patch-replicas.yaml
  - path: patch-image.yaml
# apps/overlays/production/user-service/database-claim.yaml
apiVersion: platform.acme.com/v1alpha1
kind: Database
metadata:
  name: user-service-db
spec:
  size: medium
  engine: postgres
  version: "15"
  writeConnectionSecretToRef:
    name: user-service-db-credentials

ApplicationSet for Everything

Manage all components with one ApplicationSet:

# infrastructure/argocd/applicationsets/all-apps.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: all-platform-apps
  namespace: argocd
spec:
  generators:
    - matrix:
        generators:
          - list:
              elements:
                - environment: development
                  cluster: https://dev.k8s.acme.com
                - environment: staging
                  cluster: https://staging.k8s.acme.com
                - environment: production
                  cluster: https://prod.k8s.acme.com
          - git:
              repoURL: https://github.com/acme/gitops-repo.git
              revision: main
              directories:
                - path: apps/overlays/{{environment}}/*

  template:
    metadata:
      name: '{{environment}}-{{path.basename}}'
      namespace: argocd
      labels:
        environment: '{{environment}}'
        app: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/acme/gitops-repo.git
        targetRevision: main
        path: '{{path}}'
      destination:
        server: '{{cluster}}'
        namespace: '{{path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

End-to-End Workflow

Developer Workflow:
┌─────────────────────────────────────────────────────────┐
│                                                         │
│  1. Developer opens Backstage                           │
│     └─► "Create Full-Stack Service"                    │
│                                                         │
│  2. Fills form:                                         │
│     └─► name: payment-service                          │
│     └─► database: medium                               │
│     └─► environment: production                        │
│                                                         │
│  3. Backstage creates:                                  │
│     └─► GitHub repo with code template                 │
│     └─► PR to gitops-repo with configs                 │
│     └─► Crossplane Database claim                      │
│     └─► ArgoCD Application                             │
│     └─► Catalog entry                                  │
│                                                         │
│  4. PR merged → ArgoCD syncs:                          │
│     └─► Deployment/Rollout created                     │
│     └─► Crossplane provisions RDS                      │
│     └─► Connection secret available                    │
│                                                         │
│  5. Service running with:                               │
│     └─► Progressive delivery (canary)                  │
│     └─► Database ready                                 │
│     └─► Full observability                             │
│                                                         │
│  Time: ~10 minutes (fully automated)                   │
│                                                         │
└─────────────────────────────────────────────────────────┘

In the next module, we'll explore platform observability and reliability patterns. :::

Quiz

Module 4: GitOps & Continuous Delivery

Take Quiz