Skip to content
Go back

ArgoCD for Home Lab GitOps

By SumGuy 9 min read
ArgoCD for Home Lab GitOps

Stop Managing Kubernetes Like It’s 2015

You know that feeling when you SSH into a server, run a kubectl command, and 45 minutes later you’ve got no idea what the current state actually is? Yeah. That’s the problem GitOps solves. And ArgoCD is probably the easiest way to stop doing that.

Here’s the thing: GitOps doesn’t mean “put YAML in a repo and call it DevOps.” It means your Git repo is the source of truth. Full stop. When the cluster drifts, ArgoCD yells at you (or fixes it automatically). When you need to change something, you git commit, ArgoCD syncs, and you’ve got an audit trail. Your 2 AM self will thank you when you need to figure out why Postgres doesn’t match what you pushed last week.

On a home lab running k3s, this is perfect. You get the GitOps experience without the complexity tax. Let’s set it up.


Installing ArgoCD on k3s

First, create the namespace and install the ArgoCD Helm chart:

Terminal window
kubectl create namespace argocd
helm repo add argo https://argoproj.github.io/argo-helm
helm repo update
helm install argocd argo/argo-cd \
--namespace argocd \
--values /tmp/argocd-values.yaml

Use this values file (/tmp/argocd-values.yaml):

global:
domain: argocd.your.domain.com # Update with your actual domain
server:
insecure: false
ingress:
enabled: true
ingressClassName: traefik # or nginx, depends on your ingress
hosts:
- argocd.your.domain.com
tls:
- secretName: argocd-tls
hosts:
- argocd.your.domain.com
configs:
secret:
# Generate: python3 -c 'import bcrypt; print(bcrypt.hashpw(b"your-password", bcrypt.gensalt(rounds=12)).decode())'
argocdServerAdminPassword: '$2a$12$...'
# Optional but recommended: enable notifications + logging
notifications:
enabled: false # Set to true if you have a Discord/Slack webhook
redis:
enabled: true
dex:
enabled: false # Disable if not using SSO

Wait 2-3 minutes for pods to spin up:

Terminal window
kubectl get pods -n argocd -w

Grab the initial password (if you didn’t set one):

Terminal window
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

Port-forward to test locally (no ingress yet):

Terminal window
kubectl port-forward -n argocd svc/argocd-server 8080:443
# Now: https://localhost:8080 (accept self-signed cert)

Log in with username admin and the password you set/retrieved. You’ll see a pretty empty dashboard. Good. That’s because we haven’t told ArgoCD about anything yet.


The Repo Layout: Your Git Source of Truth

This is the hardest part conceptually, but once it clicks, it’s clean. Your repo structure should look like this:

my-homelab-gitops/
├── README.md
├── argocd/
│ ├── projects/
│ │ ├── monitoring/
│ │ │ ├── namespace.yaml
│ │ │ ├── prometheus.yaml
│ │ │ ├── grafana.yaml
│ │ │ └── kustomization.yaml
│ │ ├── storage/
│ │ │ ├── longhorn.yaml
│ │ │ └── kustomization.yaml
│ │ └── apps/
│ │ ├── nextcloud.yaml
│ │ ├── jellyfin.yaml
│ │ └── kustomization.yaml
│ ├── secrets/ # sealed-secrets encrypted, more on this below
│ │ ├── monitoring-secrets.yaml
│ │ └── apps-secrets.yaml
│ └── app-of-apps.yaml # The orchestrator
├── kustomization/
│ ├── base/
│ │ ├── nextcloud/
│ │ │ ├── deployment.yaml
│ │ │ ├── service.yaml
│ │ │ └── kustomization.yaml
│ │ └── jellyfin/
│ │ ├── deployment.yaml
│ │ ├── pvc.yaml
│ │ └── kustomization.yaml
│ └── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── patches/
│ └── prod/
│ ├── kustomization.yaml
│ └── patches/
└── docs/
├── SETUP.md
└── TROUBLESHOOTING.md

The key insight: argocd/ holds Application manifests (pointers to your YAML). kustomization/ and argocd/secrets/ hold the actual Kubernetes resources. This separation keeps your GitOps config tidy.


The App-of-Apps Pattern: Orchestration Without the Headache

Instead of registering 10 separate ArgoCD Applications one-by-one through the UI (which defeats the purpose of declaring everything), you create a single “meta-application” that manages other applications. It’s application inception.

Create argocd/app-of-apps.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: apps
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/you/my-homelab-gitops.git
targetRevision: main
path: argocd/projects
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true # Delete resources if they're removed from Git
selfHeal: true # Sync if cluster drifts from Git
syncOptions:
- CreateNamespace=true

Apply it once:

Terminal window
kubectl apply -f argocd/app-of-apps.yaml

Now add an Application to argocd/projects/monitoring/prometheus-app.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: monitoring-prometheus
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "0" # Sync first
spec:
project: default
source:
repoURL: https://github.com/you/my-homelab-gitops.git
targetRevision: main
path: kustomization/base/prometheus
destination:
server: https://kubernetes.default.svc
namespace: monitoring
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

Commit and push. ArgoCD will detect the new Application via the app-of-apps, and deploy it automatically. No kubectl apply. No manual steps. Just Git.


Secrets: The Sealed-Secrets Dance

You can’t commit plaintext secrets to Git. That’s a firing offense. But you can encrypt them with Sealed Secrets and commit the encrypted blob. ArgoCD decrypts at sync time.

Install sealed-secrets:

Terminal window
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.24.0/controller.yaml
# Wait for controller pod to start
kubectl wait --for=condition=ready pod -l app.kubernetes.io/name=sealed-secrets-controller -n kube-system --timeout=300s
# Fetch the sealing key
kubeseal --fetch-cert > ~/.kube/sealing-key.crt

Create a secret (plaintext, local):

Terminal window
kubectl create secret generic nextcloud-db-secret \
--from-literal=db-password=your-super-secret-password \
--dry-run=client -o yaml > /tmp/secret.yaml

Seal it:

Terminal window
cat /tmp/secret.yaml | kubeseal \
--cert ~/.kube/sealing-key.crt \
--scope namespace \
-o yaml > argocd/secrets/nextcloud-sealed.yaml

Commit argocd/secrets/nextcloud-sealed.yaml. The plaintext /tmp/secret.yaml stays local, never touching Git.

Reference the sealed secret in your Application:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nextcloud
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1" # After namespace + secrets (wave 0)
spec:
project: default
source:
repoURL: https://github.com/you/my-homelab-gitops.git
targetRevision: main
path: kustomization/base/nextcloud
destination:
server: https://kubernetes.default.svc
namespace: nextcloud
syncPolicy:
automated:
prune: true
selfHeal: true

When ArgoCD syncs, sealed-secrets controller automatically decrypts the sealed secret back into a working Secret. Clean.


Sync Waves: Orchestrating Deployment Order

You can’t deploy Nextcloud before the database exists. ArgoCD’s sync waves solve that without complex logic.

In your Application manifests, add metadata.annotations:

kustomization/base/postgres/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
annotations:
argocd.argoproj.io/sync-wave: "0" # Deploy first
spec:
# ...
---
# kustomization/base/nextcloud/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextcloud
annotations:
argocd.argoproj.io/sync-wave: "1" # Deploy after wave 0
spec:
# ...

ArgoCD syncs wave 0, waits for it to be healthy (Pods running), then syncs wave 1. It’s like a forklift that actually understands dependencies—no more “oops, database wasn’t ready yet.”


Your First Application: Making It Real

Let’s deploy something simple. Create kustomization/base/hello-world/deployment.yaml:

apiVersion: v1
kind: Namespace
metadata:
name: hello-world
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-world
namespace: hello-world
spec:
replicas: 1
selector:
matchLabels:
app: hello-world
template:
metadata:
labels:
app: hello-world
spec:
containers:
- name: app
image: nginx:latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: hello-world
namespace: hello-world
spec:
selector:
app: hello-world
ports:
- port: 80
targetPort: 80
type: ClusterIP

Create kustomization/base/hello-world/kustomization.yaml:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: hello-world
resources:
- deployment.yaml

Create the Application in argocd/projects/apps/hello-world-app.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: hello-world
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/you/my-homelab-gitops.git
targetRevision: main
path: kustomization/base/hello-world
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

Commit and push:

Terminal window
git add argocd/ kustomization/
git commit -m "Add hello-world app"
git push

Go to the ArgoCD UI. In ~30 seconds, you’ll see the app sync automatically. Click it to see the tree of Deployments, Services, and Pods. That’s GitOps working.


What GitOps Actually Means at Home Scale

Here’s the un-glamorous truth: GitOps at home scale is boring, and that’s the point.

You’re not running Netflix. You don’t need canary deployments or traffic splitting. What you do need is:

You’re trading “just kubectl apply it” for “commit, push, ArgoCD syncs.” Sounds like more work, but it’s not—it’s structured work, and structure scales.


The Optional Upgrades

Once you’re comfortable:

But honestly? You don’t need any of that to start. Just the app-of-apps, sealed-secrets, and sync waves. That’s enough to stop the “SSH into the cluster and pray” flow.


Gotchas (Because There Are Always Gotchas)


The Payoff

In a month, you’ll realize you haven’t SSH’d into the cluster in ages. Your homelab Just Works. Git is your single source of truth. When it’s time to upgrade something, you edit YAML, push, and ArgoCD handles the rest.

That’s not fancy. That’s not cloud-native theater. That’s just smart operations. And honestly? On a home lab, that’s all you need.


Ready to try it? Create that GitHub repo, push your first app-of-apps, and watch GitOps just… work. Your future self will send thank-you notes.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
iperf3 + nload: Network Diagnosis

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts