GitOps CI/CD & DevSecOps Pipeline

Django · Docker · GitHub Actions · Helm · Argo CD · Kubernetes

A complete GitOps delivery workflow for a Django Library Management System: GitHub Actions builds and publishes container images, Helm keeps the deployment declarative, and Argo CD continuously syncs the Kubernetes cluster to the state described in Git.

2Environments (CI + CD)
Run # tagsImage tagging
HelmDeclared Kubernetes state
Argo CDContinuous delivery

Overview

This project delivers a Django application to Kubernetes using Git as the single source of truth. Developers push to main; GitHub Actions builds and publishes a Docker image to Docker Hub; the image tag is written back into the Helm values.yaml and committed; Argo CD detects the change and rolls the new version into the cluster. Every piece of the pipeline lives in the repository as code.

Problem

Manual deployments are slow, inconsistent and hard to audit. Container artifacts drift from what's actually running, and there's no single place that describes the true state of production. This project solves it with GitOps: the repository declares both the application code and the desired cluster state, and the pipeline makes deployment an automated, reviewable step.

Architecture

The loop closes because the pipeline writes new image tags back to Git — the deployable state is always in the repository.

Technology Stack

LayerTechnology
ApplicationDjango Library Management System
DatabaseMySQL 8.0
ContainerDocker — Python 3.13-slim + Gunicorn WSGI
OrchestrationKubernetes with Helm charts
CIGitHub Actions (build + publish)
CDArgo CD (continuous reconciliation)
RegistryDocker Hub

CI Pipeline (GitHub Actions)

Defined in .github/workflows/docker_build_and_push.yml:

  • Trigger — push to main, or manual workflow dispatch
  • Build — Docker image with Buildx, tagged with the GitHub run number
  • Push — published to Docker Hub (credentials from GitHub secrets)
  • Update — Helm values.yaml rewritten with the new image tag
  • Commit — changes committed back to Git (with [skip ci] to avoid re-triggering)
Scope note: this pipeline's CI job is deliberately focused on build and publish. Running unit tests and adding static-analysis gates in the pipeline is the documented next step — not a claim made here.

CD Pipeline (Argo CD)

Defined in infra/argocd-app.yaml. Argo CD monitors the Git repository, detects the new image tag in values.yaml, and syncs the application to the Kubernetes cluster:

  • Continuously monitors the declared state in Git
  • Detects the image-tag update written by CI
  • Applies the Helm release to the target namespace
  • Deployment status viewable in the Argo CD dashboard

Kubernetes & Helm

Renamed Helm templates (helm/templates/) describe the platform resources:

  • Deployment — the Django app with Gunicorn, running as a non-root user
  • Service + Ingress — routing into the application
  • Liveness / readiness probes — Kubernetes-restartable and traffic-aware
  • ServiceAccount — least-privilege identity for the workload
  • Horizontal Pod Autoscaler — optional CPU-driven scaling
  • MySQL Deployment — infra manifest for the backing database

Security & DevSecOps

The project applies security as configuration rather than afterthought:

  • bcrypt password hashing for stored user credentials
  • Role-based access control across application user types
  • Non-root container execution in the Docker image
  • Environment-based configuration — secrets and settings via environment, not committed files
  • GitHub Secrets for registry credentials — nothing sensitive in the repository

Testing

Unit tests exist for the Django application (app/library/tests.py), and CI builds the image from a context that includes the full app. As noted in the CI section, wiring the test suite into the automated pipeline is the documented next step for this repository — the pipeline today is scoped to build, publish and deploy.

Challenges & Solutions

  • Keeping Git the source of truth — the pipeline writes versioned config back to Git, so Argo CD and humans always reconcile from the same place.
  • Propagating image tags — solving "which tag is deployed" with run-number tags plus a Helm values update committed automatically.
  • Avoiding pipeline loops — the auto-update commit uses [skip ci] so the pipeline doesn't re-trigger on its own changes.
  • Secure by default images — non-root runtime, probes and a ServiceAccount baked into the Helm templates.

What I Learned

GitOps is a discipline, not a tool: the value came from making Git the single source of truth for both code and cluster state, and letting CI + Argo CD do the boring, repeatable work. I learned to think about what an image tag means at each stage of the pipeline, and how to structure a workflow so it can't silently drift from reality.