Skip to content

12: CI/CD Implementation (GitHub Actions)

This section provides the full real-world CI/CD implementation for LocalCloudLab using GitHub Actions. You will receive production-ready YAML workflows, fully aligned with the monorepo structure defined in Section 11 and designed to work directly with:

• .NET 9 APIs
• GHCR (GitHub Container Registry)
• Your k3s cluster
• Kubernetes Deployments
• Envoy Gateway
• Automatic rolling updates

This is one of the largest and most practical sections of the book.

12.1 CI/CD Overview

Every microservice in LocalCloudLab requires:

1. Build
2. Test
3. Publish
4. Docker build
5. Push to GHCR
6. Deploy to Kubernetes

We implement this using GitHub Actions in separate workflows:

.github/workflows/
├── search-api-deploy.yml
├── checkin-api-deploy.yml
└── infra-deploy.yml          # optional, for global k8s changes

12.2 Required GitHub Secrets

Container Registry (GHCR)

These allow GitHub Actions to authenticate and push images:

GHCR_USERNAME
GHCR_TOKEN or CR_PAT

Kubernetes Cluster Credentials

Base64-encoded kubeconfig:

KUBECONFIG_B64

Encode using:

cat ~/.kube/config | base64 -w 0

Optional SSH Deployment (rare)

Not needed if using kubectl directly.

SERVER_SSH_KEY
SERVER_IP
SERVER_USER

12.3 Environment Variables Used in Workflows

All workflows rely on shared defaults:

REGISTRY=ghcr.io/<your-username>
IMAGE_TAG=${{ github.sha }}
DEPLOYMENT_NAMESPACE=search or checkin

Tagging images using the commit SHA ensures:

✔ Every deployment is unique
✔ Rollbacks are trivial
✔ Tags never collide

12.4 search-api-deploy.yml (Build → Push → Deploy)

This is the actual full YAML workflow, ready to drop into your repo.

Create:

.github/workflows/search-api-deploy.yml

Contents:

name: Search API - Build, Push & Deploy

on:
  push:
    branches: [ "main" ]
    paths:
      - "Search.Api/**"
      - "k8s/search-api/**"
      - ".github/workflows/search-api-deploy.yml"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Restore dependencies
      run: dotnet restore Search.Api/Search.Api.csproj

    - name: Build
      run: dotnet build Search.Api/Search.Api.csproj --configuration Release --no-restore

    - name: Publish
      run: dotnet publish Search.Api/Search.Api.csproj -c Release -o ./publish

    - name: Login to GHCR
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ secrets.GHCR_USERNAME }}
        password: ${{ secrets.GHCR_TOKEN }}

    - name: Build Docker image
      run: |
        docker build               -t ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:${{ github.sha }}               -f Search.Api/Dockerfile               .

    - name: Push Docker image
      run: docker push ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:${{ github.sha }}

    - name: Set up kubectl
      run: |
        echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > kubeconfig.yaml
        export KUBECONFIG=kubeconfig.yaml

    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/search-api-deployment               search-api=ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:${{ github.sha }}               -n search
        kubectl rollout status deployment/search-api-deployment -n search

Explanation

This workflow:

• Reacts only when Search API or its YAML changes
• Builds the .NET 9 application
• Publishes the output
• Builds a Docker image
• Pushes it to GHCR
• Updates your k3s deployment using kubectl
• Ensures zero downtime with rollout status

(End of Part 1 — Part 2 will include Checkin API workflow, infra deployment workflow, advanced rollback strategy, caching optimization, multi-stage builds & deployment matrix patterns.)

12.5 Checkin API – CI/CD Workflow

The Checkin API uses the same CI/CD pattern as the Search API, but:

• Targets the Checkin.Api project
• Deploys to the `checkin` namespace
• Uses `checkin-api-deployment` and `checkin-api` container name

Create:

.github/workflows/checkin-api-deploy.yml

Contents:

name: Checkin API - Build, Push & Deploy

on:
  push:
    branches: [ "main" ]
    paths:
      - "Checkin.Api/**"
      - "k8s/checkin-api/**"
      - ".github/workflows/checkin-api-deploy.yml"

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '9.0.x'

    - name: Restore dependencies
      run: dotnet restore Checkin.Api/Checkin.Api.csproj

    - name: Build
      run: dotnet build Checkin.Api/Checkin.Api.csproj --configuration Release --no-restore

    - name: Publish
      run: dotnet publish Checkin.Api/Checkin.Api.csproj -c Release -o ./publish-checkin

    - name: Login to GHCR
      uses: docker/login-action@v3
      with:
        registry: ghcr.io
        username: ${{ secrets.GHCR_USERNAME }}
        password: ${{ secrets.GHCR_TOKEN }}

    - name: Build Docker image
      run: |
        docker build               -t ghcr.io/${{ secrets.GHCR_USERNAME }}/checkin-api:${{ github.sha }}               -f Checkin.Api/Dockerfile               .

    - name: Push Docker image
      run: docker push ghcr.io/${{ secrets.GHCR_USERNAME }}/checkin-api:${{ github.sha }}

    - name: Set up kubectl
      run: |
        echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > kubeconfig.yaml
        export KUBECONFIG=kubeconfig.yaml

    - name: Deploy to Kubernetes
      run: |
        kubectl set image deployment/checkin-api-deployment               checkin-api=ghcr.io/${{ secrets.GHCR_USERNAME }}/checkin-api:${{ github.sha }}               -n checkin
        kubectl rollout status deployment/checkin-api-deployment -n checkin

Explanation:

• Only runs when Checkin API or its k8s manifests change.
• Uses unique image tag per commit (github.sha).
• Updates deployment and waits for rollout success.
• Gives you strong traceability from commit → image → pods.

12.6 infra-deploy.yml – Infrastructure Manifests

Some changes affect the infrastructure layer, not a specific API:

• Envoy Gateway settings
• HTTPRoutes
• Redis / RabbitMQ / Postgres YAML
• cert-manager resources
• Monitoring stack

We want a workflow that:

• Runs when files under /k8s/** change
• Applies manifests safely
• Does not delete unrelated resources

Create:

.github/workflows/infra-deploy.yml

Contents:

name: Infrastructure - Apply Kubernetes Manifests

on:
  push:
    branches: [ "main" ]
    paths:
      - "k8s/**"
      - ".github/workflows/infra-deploy.yml"

jobs:
  apply-k8s:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4

    - name: Set up kubectl
      run: |
        echo "${{ secrets.KUBECONFIG_B64 }}" | base64 -d > kubeconfig.yaml
        export KUBECONFIG=kubeconfig.yaml
        kubectl version --client

    - name: Apply namespaces
      run: |
        kubectl apply -f k8s/namespaces/

    - name: Apply core infrastructure (MetalLB, cert-manager, gateway)
      run: |
        kubectl apply -f k8s/metallb/
        kubectl apply -f k8s/cert-manager/
        kubectl apply -f k8s/gateway/

    - name: Apply data layer (Postgres, Redis, RabbitMQ)
      run: |
        kubectl apply -f k8s/postgres/
        kubectl apply -f k8s/redis/
        kubectl apply -f k8s/rabbitmq/

    - name: Apply monitoring stack
      run: |
        kubectl apply -f k8s/monitoring/

    - name: Apply app-specific manifests
      run: |
        kubectl apply -f k8s/search-api/
        kubectl apply -f k8s/checkin-api/

Notes:

• Uses kubectl apply → idempotent & safe for repeated runs.
• Assumes folder structure exactly as designed in Section 11.
• You may uncomment or adjust lines depending on your actual folder layout.

12.7 Advanced CI/CD Topics

Once basic CI/CD works, you can refine and optimize.

Key advanced topics:

• Docker layer caching
• Parallel builds
• Rollbacks
• Deployment matrices
• Environments & approvals

12.7.1 Docker layer caching

Add a caching step to speed up Docker builds:

- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Build and push with cache
  uses: docker/build-push-action@v6
  with:
    context: .
    file: Search.Api/Dockerfile
    push: true
    tags: |
      ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:${{ github.sha }}
    cache-from: type=registry,ref=ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:cache
    cache-to: type=registry,ref=ghcr.io/${{ secrets.GHCR_USERNAME }}/search-api:cache,mode=max

This:

• Reuses layers between builds
• Greatly speeds up CI after first run

12.7.2 Rollback Strategy

If deployment fails:

• kubectl rollout status fails
• You can roll back:

Manually:

kubectl rollout undo deployment/search-api-deployment -n search

Or by referencing specific image tag:

kubectl set image deployment/search-api-deployment       search-api=ghcr.io/<user>/search-api:<old-tag>       -n search

You can create a rollback workflow that:

• Accepts image tag as an input
• Calls kubectl set image + rollout status

12.7.3 Deployment Matrices (Optional)

If you later have multiple APIs or environments, you can use a matrix:

strategy:
  matrix:
    service: [ "Search.Api", "Checkin.Api" ]

Loop over both:

- name: Build ${{ matrix.service }}
  run: dotnet build ${{ matrix.service }}/${{ matrix.service }}.csproj ...

This reduces duplicated workflow code, but initial clarity is often better with one workflow per service (as we currently do).

12.8 Environments & Manual Approvals (Optional)

GitHub Actions “environments” allow:

• Manual approvals
• Environment-specific secrets
• Protection rules

Example configuration:

environment: production

Then in GitHub UI:

• Configure environment "production"
• Require manual approval before jobs proceed

This is most useful when you:

• Use the same CI/CD to deploy both dev and prod
• Want to review changes before pushing to a sensitive cluster

12.9 Summary of Section 12

In this section, you:

✔ Defined all required GitHub secrets for CI/CD
✔ Implemented a full CI/CD workflow for Search.Api
✔ Implemented a full CI/CD workflow for Checkin.Api
✔ Implemented an infra-deploy workflow for Kubernetes manifests
✔ Learned how to optimize builds with caching
✔ Learned basic rollback strategies
✔ Prepared for advanced patterns like matrices and approvals

Your LocalCloudLab now has a complete, automated CI/CD pipeline, tightly integrated with:

• GitHub (source of truth)
• GHCR (image registry)
• k3s (runtime)
• Envoy Gateway (exposed entrypoint)
• All observability and infrastructure tooling built earlier

Next section (Section 13) can focus on:

• Application-level architecture (Clean Architecture in .NET)
• Folder structure in Search.Api and Checkin.Api
• Best practices for controllers, services, repositories
• OpenTelemetry integration inside the code

(End of Section 12 — Complete)