feat: add ArgoCD bootstrap with ApplicationSet pattern

- Add root ApplicationSet using Git Directory Generator
- Configure AppProjects for infrastructure and apps separation
- Add cert-manager with Let's Encrypt ClusterIssuers (staging/prod)
- Add SOPS configuration for age-encrypted secrets
- Add bootstrap documentation (docs/BOOTSTRAP.md)
- Scaffold infrastructure dirs (networking, storage, monitoring)
- Update README with quick start and architecture

GitOps pattern: directories auto-discovered by ArgoCD ApplicationSets
Reference: CNCF App-of-Apps best practices 2025
This commit is contained in:
Greg Hendrickson
2026-02-02 18:02:32 +00:00
parent 1e402ff027
commit 124a29a0a9
16 changed files with 503 additions and 13 deletions

31
.sops.yaml Normal file
View File

@@ -0,0 +1,31 @@
# .sops.yaml
# SOPS configuration for encrypting Kubernetes secrets
# Generate age key: age-keygen -o key.txt
# Export: export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
# Encrypt: sops -e -i secret.yaml
# Decrypt: sops -d secret.yaml
#
# Reference: https://github.com/getsops/sops
creation_rules:
# Infrastructure secrets (networking, storage, monitoring)
- path_regex: infrastructure/.*/.*secret.*\.yaml$
encrypted_regex: ^(data|stringData)$
age: >-
age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Application secrets
- path_regex: apps/.*/.*secret.*\.yaml$
encrypted_regex: ^(data|stringData)$
age: >-
age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# Cluster-specific secrets
- path_regex: clusters/.*/.*secret.*\.yaml$
encrypted_regex: ^(data|stringData)$
age: >-
age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# NOTE: Replace the age public key above with your actual key
# The encrypted_regex ensures only data/stringData fields are encrypted,
# leaving metadata readable for GitOps tooling

View File

@@ -1,10 +1,20 @@
# Homelab GitOps # Homelab GitOps
![Kubernetes](https://img.shields.io/badge/k3s-1.28+-326CE5?style=flat&logo=kubernetes&logoColor=white) ![Kubernetes](https://img.shields.io/badge/k3s-1.28+-326CE5?style=flat&logo=kubernetes&logoColor=white)
![ArgoCD](https://img.shields.io/badge/GitOps-Ready-EF7B4D?style=flat&logo=argo&logoColor=white) ![ArgoCD](https://img.shields.io/badge/ArgoCD-2.10+-EF7B4D?style=flat&logo=argo&logoColor=white)
![SOPS](https://img.shields.io/badge/SOPS-age-green?style=flat)
![License](https://img.shields.io/badge/License-MIT-blue) ![License](https://img.shields.io/badge/License-MIT-blue)
GitOps repository for homelab Kubernetes infrastructure. Everything as code. GitOps repository for homelab Kubernetes infrastructure. Everything as code, auto-synced by ArgoCD.
## Quick Start
```bash
# Bootstrap cluster (after ArgoCD installed)
kubectl apply -k clusters/defiant/
```
See [docs/BOOTSTRAP.md](docs/BOOTSTRAP.md) for full setup guide.
## Infrastructure ## Infrastructure
@@ -18,16 +28,32 @@ GitOps repository for homelab Kubernetes infrastructure. Everything as code.
``` ```
├── apps/ # Application deployments ├── apps/ # Application deployments
│ ├── base/ # Base manifests │ ├── base/ # Base manifests (Kustomize)
│ └── overlays/ # Environment overrides │ └── overlays/ # Environment overrides
│ ├── prod/ # → Auto-discovered by ApplicationSet
│ └── dev/
├── infrastructure/ # Cluster infrastructure ├── infrastructure/ # Cluster infrastructure
│ ├── networking/ # Ingress, certs, DNS │ ├── cert-manager/ # ✅ TLS with Let's Encrypt
│ ├── storage/ # NFS, PVCs │ ├── networking/ # Istio gateway, NetworkPolicies
── monitoring/ # Prometheus, Grafana ── storage/ # NFS StorageClass
└── clusters/ │ └── monitoring/ # Prometheus, Grafana, Loki
└── defiant/ # k3s cluster config ├── clusters/
│ └── defiant/ # Cluster bootstrap
│ ├── kustomization.yaml
│ ├── root-applicationset.yaml # Git Directory Generator
│ └── projects.yaml # ArgoCD AppProjects
└── docs/
└── BOOTSTRAP.md # Setup guide
``` ```
## GitOps Pattern
Uses **ArgoCD ApplicationSets** with Git Directory Generator:
- `infrastructure/*` → Auto-creates ArgoCD Applications
- `apps/overlays/prod/*` → Auto-creates prod Applications
- Add a directory, push, ArgoCD syncs automatically
## Defiant (k3s) Workloads ## Defiant (k3s) Workloads
- 🏥 MediSynth - FHIR healthcare platform - 🏥 MediSynth - FHIR healthcare platform
@@ -43,9 +69,17 @@ GitOps repository for homelab Kubernetes infrastructure. Everything as code.
- 🏠 Home Assistant - 🏠 Home Assistant
- 📊 Homepage, Uptime Kuma - 📊 Homepage, Uptime Kuma
## Secrets ## Secrets Management
Encrypted with SOPS + age. Never committed in plain text. Encrypted with **SOPS + age**. Configuration in `.sops.yaml`.
```bash
# Encrypt a secret
sops -e -i infrastructure/cert-manager/secret.yaml
# Decrypt for editing
sops infrastructure/cert-manager/secret.yaml
```
## License ## License

2
apps/base/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# Base manifests for applications
# Use Kustomize bases here, overlays in apps/overlays/{env}/

View File

@@ -0,0 +1,2 @@
# Development overlay directory
# Add dev-specific patches and configurations here

View File

@@ -0,0 +1,2 @@
# Production overlay directory
# Each subdirectory here becomes an ArgoCD Application via ApplicationSet

View File

@@ -0,0 +1,12 @@
# clusters/defiant/argocd-namespace.yaml
# ArgoCD namespace with required labels
apiVersion: v1
kind: Namespace
metadata:
name: argocd
labels:
app.kubernetes.io/name: argocd
app.kubernetes.io/part-of: argocd
# Pod Security Standards - privileged for ArgoCD repo-server
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted

View File

@@ -0,0 +1,12 @@
# clusters/defiant/kustomization.yaml
# Root Kustomization for defiant k3s cluster
# Applied by ArgoCD or manually via: kubectl apply -k clusters/defiant/
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: argocd
resources:
- argocd-namespace.yaml
- root-applicationset.yaml
- projects.yaml

View File

@@ -0,0 +1,49 @@
# clusters/defiant/projects.yaml
# ArgoCD AppProjects for access control and grouping
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: infrastructure
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: Core cluster infrastructure (networking, storage, monitoring)
sourceRepos:
- 'https://github.com/ghndrx/homelab-gitops.git'
- 'https://charts.jetstack.io'
- 'https://prometheus-community.github.io/helm-charts'
- 'https://grafana.github.io/helm-charts'
destinations:
- namespace: '*'
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: '*'
kind: '*'
namespaceResourceWhitelist:
- group: '*'
kind: '*'
---
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: apps
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
description: User-facing applications
sourceRepos:
- 'https://github.com/ghndrx/homelab-gitops.git'
destinations:
- namespace: 'prod-*'
server: https://kubernetes.default.svc
- namespace: 'dev-*'
server: https://kubernetes.default.svc
clusterResourceWhitelist:
- group: ''
kind: Namespace
namespaceResourceWhitelist:
- group: '*'
kind: '*'

View File

@@ -0,0 +1,96 @@
# clusters/defiant/root-applicationset.yaml
# Root ApplicationSet using Git Directory Generator
# Automatically creates ArgoCD Applications for each component in infrastructure/
# Reference: https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/Generators-Git/
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: infrastructure
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/ghndrx/homelab-gitops.git
revision: HEAD
directories:
- path: infrastructure/*
template:
metadata:
name: '{{ .path.basename }}'
namespace: argocd
labels:
app.kubernetes.io/part-of: homelab-infrastructure
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: infrastructure
source:
repoURL: https://github.com/ghndrx/homelab-gitops.git
targetRevision: HEAD
path: '{{ .path.path }}'
destination:
server: https://kubernetes.default.svc
namespace: '{{ .path.basename }}'
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: apps
namespace: argocd
spec:
goTemplate: true
goTemplateOptions: ["missingkey=error"]
generators:
- git:
repoURL: https://github.com/ghndrx/homelab-gitops.git
revision: HEAD
directories:
- path: apps/overlays/prod/*
template:
metadata:
name: 'prod-{{ .path.basename }}'
namespace: argocd
labels:
app.kubernetes.io/part-of: homelab-apps
environment: prod
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: apps
source:
repoURL: https://github.com/ghndrx/homelab-gitops.git
targetRevision: HEAD
path: '{{ .path.path }}'
destination:
server: https://kubernetes.default.svc
namespace: 'prod-{{ .path.basename }}'
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 1m

102
docs/BOOTSTRAP.md Normal file
View File

@@ -0,0 +1,102 @@
# Cluster Bootstrap Guide
This guide walks through bootstrapping a new k3s cluster with ArgoCD GitOps.
## Prerequisites
- k3s cluster running
- `kubectl` configured with cluster access
- `age` installed for SOPS encryption
- GitHub repo access configured
## 1. Install ArgoCD
```bash
# Create namespace
kubectl create namespace argocd
# Install ArgoCD
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Wait for pods
kubectl wait --for=condition=available deployment/argocd-server -n argocd --timeout=300s
```
## 2. Configure SOPS
```bash
# Generate age key (one-time)
age-keygen -o ~/.config/sops/age/keys.txt
# Get public key
cat ~/.config/sops/age/keys.txt | grep "public key"
# Update .sops.yaml with your public key
# Create k8s secret for ArgoCD to decrypt
kubectl create secret generic sops-age \
--namespace=argocd \
--from-file=key.txt=~/.config/sops/age/keys.txt
```
## 3. Bootstrap the Cluster
```bash
# Apply root kustomization
kubectl apply -k clusters/defiant/
# This creates:
# - ArgoCD namespace with PSS labels
# - AppProjects (infrastructure, apps)
# - Root ApplicationSets that auto-discover components
```
## 4. Access ArgoCD UI
```bash
# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d
# Port forward
kubectl port-forward svc/argocd-server -n argocd 8080:443
# Open https://localhost:8080
# Username: admin
```
## 5. Verify Infrastructure
After bootstrap, ArgoCD will automatically sync:
- **cert-manager** - TLS certificate management with Let's Encrypt
- **networking** - Istio gateway (when configured)
- **storage** - NFS StorageClass (when configured)
- **monitoring** - Prometheus/Grafana (when configured)
## Adding New Infrastructure
1. Create directory under `infrastructure/<component>/`
2. Add `kustomization.yaml` (required)
3. Add manifests or helmCharts
4. Commit and push
5. ArgoCD auto-discovers via Git Directory Generator
## Adding Applications
1. Create base in `apps/base/<app>/`
2. Create overlay in `apps/overlays/prod/<app>/`
3. Commit and push
4. ArgoCD creates Application automatically
## Troubleshooting
```bash
# Check ApplicationSet status
kubectl get applicationsets -n argocd
# Check Application sync status
kubectl get applications -n argocd
# View ArgoCD logs
kubectl logs -n argocd deployment/argocd-applicationset-controller
```

View File

@@ -0,0 +1,54 @@
# infrastructure/cert-manager/clusterissuers.yaml
# Let's Encrypt ClusterIssuers for TLS certificates
# Usage: Add annotation to Ingress:
# cert-manager.io/cluster-issuer: letsencrypt-prod
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
# Staging endpoint for testing (higher rate limits, fake certs)
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: admin@example.com # TODO: Update with your email
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
# HTTP-01 challenge via Ingress
- http01:
ingress:
ingressClassName: istio
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
# Production endpoint (rate limited, real certs)
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com # TODO: Update with your email
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
# HTTP-01 challenge via Ingress
- http01:
ingress:
ingressClassName: istio
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: selfsigned
spec:
selfSigned: {}
---
# Internal CA for service-to-service mTLS
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: internal-ca
spec:
ca:
secretName: internal-ca-key-pair

View File

@@ -0,0 +1,48 @@
# infrastructure/cert-manager/kustomization.yaml
# Cert-Manager with Let's Encrypt ClusterIssuers
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: cert-manager
resources:
- namespace.yaml
- clusterissuers.yaml
helmCharts:
- name: cert-manager
repo: https://charts.jetstack.io
version: v1.14.4
releaseName: cert-manager
namespace: cert-manager
valuesInline:
installCRDs: true
replicaCount: 1
# Pod Security Standards compliance
podSecurityPolicy:
enabled: false
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containerSecurityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
webhook:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
cainjector:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
# Prometheus ServiceMonitor
prometheus:
enabled: true
servicemonitor:
enabled: true

View File

@@ -0,0 +1,11 @@
# infrastructure/cert-manager/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: cert-manager
labels:
app.kubernetes.io/name: cert-manager
app.kubernetes.io/component: certificate-management
# Pod Security Standards
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted

View File

@@ -0,0 +1,13 @@
# infrastructure/monitoring/kustomization.yaml
# Monitoring: Prometheus, Grafana, Alertmanager
# TODO: Add kube-prometheus-stack helm chart
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: monitoring
resources: []
# Future additions via helmCharts:
# - kube-prometheus-stack
# - loki
# - grafana dashboards

View File

@@ -0,0 +1,12 @@
# infrastructure/networking/kustomization.yaml
# Networking stack: Istio ingress, DNS, Network Policies
# TODO: Add Istio Gateway, VirtualService templates
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: istio-system
resources: []
# Future additions:
# - gateway.yaml
# - default-network-policies.yaml

View File

@@ -0,0 +1,10 @@
# infrastructure/storage/kustomization.yaml
# Storage: NFS provisioner, PVC templates
# TODO: Add NFS StorageClass pointing to TrueNAS
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: []
# Future additions:
# - nfs-storageclass.yaml
# - default-pvc-templates.yaml