DEV Community

Cover image for From Git Push to Kubernetes: Building a Local GitOps Pipeline
Tingwei
Tingwei

Posted on

From Git Push to Kubernetes: Building a Local GitOps Pipeline

Angular, NestJS, GitHub Actions, GHCR, Argo CD, and Traefik on a Windows laptop.

Introduction

This project uses a local Kubernetes cluster to deploy an Angular frontend and a NestJS backend.

The architecture separates three responsibilities:

  • CI: GitHub Actions builds and publishes container images.
  • GitOps: Git stores the desired Kubernetes configuration.
  • CD: Argo CD reconciles that configuration with the cluster.

The cluster runs on Windows using VirtualBox, Minikube, and containerd.

1. Architecture Overview

arch description
Figure 1 — The complete deployment pipeline, from source code to Kubernetes.

The deployment sequence is:

  1. A developer pushes Angular or NestJS changes to main.
  2. GitHub Actions builds both container images and publishes them to GHCR.
  3. CI verifies the image manifests and downloads.
  4. The workflow updates the Kustomize image tags using the Git commit SHA.
  5. Argo CD detects the manifest changes and reconciles Kubernetes resources.
  6. Kubernetes pulls the requested images and rolls out the Deployments.
  7. Traefik routes incoming requests to Angular or NestJS.

GitHub Actions does not connect directly to the local Kubernetes cluster. Argo CD manages deployment from the repository.

2. Local Kubernetes Environment

Minikube runs inside a VirtualBox VM, with containerd as the container runtime.

minikube start `
  --driver=virtualbox `
  --container-runtime=containerd `
  --profile=k8s-vbox `
  --cpus=2 `
  --memory=4096
Enter fullscreen mode Exit fullscreen mode

Verify the cluster:

kubectl get nodes
kubectl get pods -A
Enter fullscreen mode Exit fullscreen mode

All application workloads and supporting controllers run inside this single-node cluster.

3. Monorepo Structure

Application code and Kubernetes configuration share one repository.

project/
├── agapp/                 # Angular
├── backend/               # NestJS
├── .github/workflows/
│   └── docker-build.yaml
└── infra/
    ├── bootstrap/         # Root Argo CD Application
    ├── app/               # Child Applications
    └── k8s/
        ├── platform/      # Namespace and ConfigMap
        └── workloads/     # Deployments and routing
Enter fullscreen mode Exit fullscreen mode

The separation between infra/app and infra/k8s is intentional:

  • infra/app describes which components Argo CD manages.
  • infra/k8s describes the Kubernetes resources those components deploy.

4. CI: Building and Publishing Images

The GitHub Actions workflow uses a matrix to build Angular and NestJS in parallel.

strategy:
  matrix:
    include:
      - name: frontend
        context: ./agapp
        image: angular-frontend

      - name: backend
        context: ./backend
        image: angular-backend
Enter fullscreen mode Exit fullscreen mode

Docker Buildx builds linux/amd64 images and publishes two tags per application:

ghcr.io/example-org/angular-frontend:latest
ghcr.io/example-org/angular-frontend:<commit-sha>

ghcr.io/example-org/angular-backend:latest
ghcr.io/example-org/angular-backend:<commit-sha>
Enter fullscreen mode Exit fullscreen mode

After publishing, the workflow checks the image manifest and performs a docker pull to validate registry access.

The update-gitops job only runs after both matrix builds have succeeded.

5. GitOps: Updating the Desired Version

Kubernetes does not automatically restart an application when the contents of a latest image change.

Instead, the workflow updates:

infra/k8s/workloads/kustomization.yaml

The images section records an explicit version:

images:
  - name: ghcr.io/example-org/angular-frontend
    newTag: <commit-sha>

  - name: ghcr.io/example-org/angular-backend
    newTag: <commit-sha>
Enter fullscreen mode Exit fullscreen mode

The workflow commits this change to main.

This gives Argo CD a manifest change to detect and provides a traceable relationship between application source code and the requested Kubernetes image version.

The update job requires repository write permission, which must be permitted by the GitHub repository and organization policies.

6. CD: Argo CD and App of Apps

Argo CD uses an App of Apps structure.

The root Application tracks:

source:
  targetRevision: main
  path: infra/app
Enter fullscreen mode Exit fullscreen mode

It manages five child Applications:

Application Responsibility
angular-platform Namespace and ConfigMap
angular-workloads Frontend, backend, HTTPRoute and SealedSecret
traefik Gateway and traffic routing
sealed-secrets Secret controller
prometheus Monitoring

The workload Application reads infra/k8s/workloads.

When an image tag changes, Argo CD updates the Deployment specification. Kubernetes then pulls the new image and performs a rolling update.

Git synchronization and workload readiness are verified independently.

7. Application Routing with Traefik

Angular and NestJS each use an internal Kubernetes Service.

Traefik provides the external entry point through NodePort 30080.

The Traefik Helm values include:

service:
  type: NodePort

ports:
  web:
    port: 8000
    exposedPort: 80
    nodePort: 30080
    expose:
      default: true
Enter fullscreen mode Exit fullscreen mode

Gateway API HTTPRoute sends requests to the appropriate Service:

Request path Destination
/api and /api/* NestJS, port 3000
/ and other paths Angular, port 3000

The application is accessible from Windows at:

http://<minikube-ip>:30080/
Enter fullscreen mode Exit fullscreen mode

This avoids exposing separate NodePorts for the frontend and backend or requiring a permanent port-forward session.

8. Secrets and Monitoring

Sealed Secrets

The backend receives configuration through a ConfigMap and credentials through a Kubernetes Secret.

Sealed Secrets stores encrypted secret values in Git. The controller creates the corresponding Kubernetes Secret inside the cluster.

Plaintext database credentials and API tokens are not included in the repository manifests.

Prometheus

Prometheus is installed through an Argo CD Helm Application.

The current configuration uses:

  • One Prometheus server replica.
  • 24-hour retention.
  • No persistent volume.
  • Enabled kube-state-metrics.
  • Disabled Alertmanager, Pushgateway and node-exporter.

This keeps resource consumption relatively small for the local Minikube environment.

9. Deployment Verification

Each deployment stage is checked separately.

CI: Confirm the GitHub Actions build and GitOps update jobs succeed.

GitOps: Confirm Argo CD synchronization:

kubectl get applications -n argocd
Enter fullscreen mode Exit fullscreen mode

Kubernetes: Verify running Pods and configured images:

kubectl get pods -n angular-local

kubectl get deployments -n angular-local `
  -o custom-columns="NAME:.metadata.name,IMAGE:.spec.template.spec.containers[*].image"
Enter fullscreen mode Exit fullscreen mode

Application: Test the Traefik entry point:

http://<minikube-ip>:30080/
http://<minikube-ip>:30080/api/health
Enter fullscreen mode Exit fullscreen mode

These checks distinguish a successfully published image, an applied Kubernetes configuration, and a healthy application rollout.

Conclusion

This project combines GitHub Actions, GHCR, Kustomize, Argo CD, and Kubernetes into a GitOps deployment workflow running on a Windows laptop.

Each component has one responsibility:

GitHub Actions builds artifacts. Git records the desired version. Argo CD reconciles the configuration. Kubernetes runs the workloads. Traefik exposes the applications.

Sealed Secrets and Prometheus support the environment through credential management and metrics collection.

The result is a local Kubernetes platform with automated deployment configuration and a consistent path from application source code to Kubernetes.

Top comments (0)