Practical GitOps in a Homelab: From Bare Metal to Automated Deployments

Practical GitOps in a Homelab: From Bare Metal to Automated Deployments

Most people think a self-hosted DevOps stack means a sprawling datacenter or a pricey NAS. In reality, you can ship a rock-solid pipeline on a single, well-chosen box and a handful of open-source tools. This is not a theoretical exercise; it’s the exact setup I run in my homelab: a small, inexpensive machine, a rock-solid version control plane, a light Kubernetes cluster, and a GitOps-driven deployment flow. If you want automation that actually reduces toil, not just adds another dashboard, this is for you.

Hook: You don’t need a giant server farm to automate like a pro. You need a repeatable, resilient baseline, clear ownership of artifacts, and a pipeline that doesn’t break on a Sunday update.

1) Define the minimal, reliable core you’ll grow from

The first rule of homelab pragmatism: start with a lean, boring core you can trust, then layer complexity only when it’s delivering measurable value.

What to run on day one

  • Single-node k3s cluster (or MicroK8s) on a compact machine: Raspberry Pi 4 with 4-8 GB RAM is pleasant for tinkering, but I prefer a small x86 box (NUC or Mini PC) with 16 GB RAM for predictable memory behavior and better storage performance.
  • A self-hosted Git server (Gitea) for code hosting and collaborator management.
  • A GitOps engine (Argo CD or Flux) to drive deployments from Git.
  • A lightweight CI runner (Drone or GitLab Runner) that can run on the same cluster or on a separate machine if needs demand.

Why Kubernetes? Because it’s API-driven, boringly repeatable, and you’ll learn to work with it anyway. k3s is the easiest path: small footprint, simple install, and most components just run as pods. If you want to keep things simpler for the first few months, you can begin with Docker Compose for a micro-portfolio of services, then migrate to k3s as you grow. The important thing is choosing a path you’ll actually maintain.

2) Self-hosted Git: reliable code hosting without the cloud tax

A credible GitHub-like experience in your own infrastructure is the backbone of a sane DevOps workflow. Gitea is lightweight, easy to operate, and has everything you need for standard workflows (issues, PRs, CI integration hooks).

What you’ll do

  • Run Gitea with a PostgreSQL backend for reliability (sqlite is fine for tinkering but not for ongoing work under load).
  • Use a reverse proxy with TLS (Traefik or Nginx) and a short-lived certificate automation (Let’s Encrypt via acme.sh or Traefik’s built-in ACME support).
  • Enforce basic auth, restricted API tokens, and SSH-based repo access if you want to keep things strictly internal.

Example docker-compose snippet (simplified)

  • version: '3'
  • services:
  • gitea:

image: gitea/gitea:latest

environment:

  • USER_UID=1000
  • USER_GID=1000
  • DB_TYPE=PostgreSQL
  • DB_HOST=db
  • DB_NAME=gitea
  • DB_USER=gitea
  • DB_PASS=changeme

depends_on:

  • db

ports:

  • "3000:3000"
  • "22:22"

volumes:

  • gitea:/data
  • db:

image: postgres:13

environment:

  • POSTGRES_PASSWORD=changeme

volumes:

  • gitea_db:/var/lib/postgresql/data

volumes:

gitea:

gitea_db:

Post-setup tips

  • Enable webhooks for CI to trigger on push/PR events.
  • Create a dedicated “infrastructure” repository for cluster YAMLs, secrets, and the GitOps manifests. Treat this as the single source of truth for your cluster state.
  • Regularly back up the Gitea data directory and the database—this is non-negotiable.

3) GitOps: automated, auditable deployments that actually feel sane

GitOps is where the automation starts paying back. The idea is simple: the desired state of your cluster lives in Git, and your cluster continuously reconciles to that state. Argo CD and Flux are the two popular options; pick one and stay consistent.

What you’ll implement

  • A cluster manifest repo containing environment overlays (dev, stage, prod) and Kubernetes manifests (Deployments, Services, Ingress).
  • A separate application for each service you deploy (e.g., api.example.com, frontend.example.com), with a dedicated sync strategy and health checks.
  • Automated promotion pipelines: PRs that update the manifest repo trigger a CI stage, which then pushes the change to the GitOps tool, which then applies it to the cluster.

Operational tips

  • Use Helm charts for repeatable deployments, but pin chart versions to avoid drift.
  • Keep secrets out of Git. Use a dedicated secret management solution (Sealed Secrets, Sops, or Vault) and mount them as environment variables or volumes in your apps.
  • Create a boring, auditable change log in Git: who changed what, when, and why.

Argo CD quick-start outline

  • Install Argo CD in the cluster (kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml)
  • Expose the API server with a secure ingress or port-forward for initial setup.
  • Create an Application that points to your manifest repo:
  • Spec: project: default, source: repoURL, path: path/to/env/production, destination: cluster/production
  • Ensure automatic pruning and health checks are enabled; this lets Argo CD revert undesired changes automatically.

A concrete example of a manifest layout

  • repo/
  • apps/
  • prod/
  • app1/
  • kustomization.yaml
  • deployment.yaml
  • service.yaml
  • app2/
  • deployment.yaml
  • infra/
  • ingress.yaml
  • This keeps GitOps deterministic. Any change goes through PR review, CI checks, and then Argo CD reconciles to the desired state.

4) CI that actually pays off: a self-hosted runner you won’t dread

Your CI system must not be a perpetual maintenance nightmare. Self-hosted runners give you control, cost predictability, and the ability to test against the same stack you deploy to.

Options

  • Drone CI: lightweight, workflow-based, super easy to self-host, and integrates cleanly with Git repos. Great for small teams.
  • GitLab Runner: more feature-rich; if you already use GitLab for hosting repos, this is a natural extension.
  • GitHub Actions with self-hosted runners: if you’re committed to GitHub, add runners on the homelab to keep traffic local and reduce egress.

A minimal Drone-based setup

  • Run a Docker-in-Docker (dind) capable runner on a small VM or on the same cluster.
  • Define a pipeline (.drone.yml) for build, test, and deploy stages.
  • Integrate with your Gitea via a webhooks-based trigger (Gitea supports the Drone API).

Sample pipeline steps (conceptual)

  • step: name: Build

image: golang:1.20

commands:

  • go build ./...
  • step: name: Test

image: golang:1.20

commands:

  • go test ./...
  • step: name: Deploy

image: bitnami/kubectl

commands:

  • kubectl apply -f k8s/

Where it pays off

  • You catch integration issues early in a controlled, repeatable environment.
  • You gain confidence to push to production through a single, auditable pipeline.

5) Observability, backups, and resilience that you’ll actually rely on

A self-hosted stack is only valuable if you can observe it, recover from failures, and know when something’s off.

Observability stack

  • Prometheus + Grafana for metrics; Loki (or Tempo) for logs; Alertmanager to route alerts.
  • Basic dashboards: cluster CPU/memory, pod restarts, webhook failures, deployment drift.
  • Use kube-state-mats or metrics-server to gather cluster-level data; keep dashboards lightweight to start.

Backups that don’t require a PhD to restore

  • Use Restic or Borg to back up etcd data, Kubernetes manifest repos, and critical application data (databases, volumes).
  • For cluster state, snapshot etcd (if using a single control plane, which is common in small clusters). If your cluster is single-node, a careful approach is to back up etcd data to object storage (S3-compatible) weekly.
  • Backups should be encrypted, tested, and automated with a simple retention policy.

Storage and backups example

  • Use a local NFS/NAS for bulk storage and an S3-compatible service (MinIO, Backblaze B2, or even self-hosted S3-compatible storage) for offsite backups.
  • A backup job could run as a Kubernetes CronJob that uses Restic to snapshot your key volumes:
  • restic init --repo s3:s3.amazonaws.com/bucket/path
  • restic backup /var/lib/postgresql/data
  • restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12
  • restic prune

Monitoring and reliability checks

  • Set up uptime checks and alerting for your Git hosting, CI, and GitOps reconciliation status.
  • Regularly test disaster recovery: simulate a failure, restore from backup in a staging environment, and verify service health post-restore.
  • Document your runbooks and keep them under version control with the same GitOps discipline you apply to apps.

6) Networking, security, and sensible remote access

Security should never be an afterthought in a homelab. You don’t need to overnight harden every service, but you do need a sane baseline.

Networking

  • Put a reverse proxy in front of all services (Traefik or Nginx) for TLS termination and routing.
  • Use a single public IP with WAF-ish protections (or at least rate limiting) to reduce exposure.
  • Consider a separate VLAN or subnet for your homelab with strict egress controls.

Remote access

  • Run a WireGuard VPN for remote access; keep the VPN behind MFA if possible.
  • For day-to-day access, SSH with key-based authentication, disabled password login, and per-user authorized_keys.
  • Use short-lived tokens for API access and rotate credentials every 3-6 months at minimum.

Secrets management

  • Do not store credentials in Git. Keep them in a vault (HashiCorp Vault, Bitwarden, or Sealed Secrets for Kubernetes) and mount them into pods at runtime.
  • Rotate DB passwords and API keys on a predictable cadence; automate where possible.

7) Day-2 operational discipline: upgrades, maintenance, and governance

A practical homelab requires discipline, not heroic effort every time you want to tweak something.

Upgrade strategy

  • Upgrade one component at a time in a sandbox environment (dev -> staging) before production.
  • Pin versions in your manifests and use immutable tags for container images (e.g., v1.2.3 rather than latest).
  • Schedule quarterly cluster maintenance windows; keep a roll-forward and rollback plan.

Maintenance rituals

  • Daily: health checks via Prometheus alerts; weekly: review backups; monthly: update base OS and container images with minimal downtime.
  • Automate as much as possible: upgrade automation, backup validation, and security patching should be scripted and auditable.

8) A sample 8-week plan to get things rolling

Week 1-2: Build the core

  • Provision a single-node k3s cluster