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)