Using GitOps principals and workflow to manage a lightweight k3s cluster.
I've used Techno Tim's k3s-ansible playbook to deploy a 6 node (3 masters & 3 workers) cluster across 3 Proxmox hosts, with each host running 2 VMs (1 master node with 4GB RAM/2vCPU and 1 worker node with 8GB RAM/4vCPU).
Host | Model | CPU | Storage |
---|---|---|---|
Beelink S12 Pro | Mini PC | 12th gen N100 | 512GB M.2 |
Beelink S13 | Mini PC | 12th gen N150 | 512GB M.2 |
Beelink EQ14 | Mini PC | 12th gen N150 | 512GB M.2 |
Each Proxmox host runs:
- 1x Kubernetes master node (4GB RAM, 2vCPU)
- 1x Kubernetes worker node (8GB RAM, 4vCPU)
Description | Spec |
---|---|
Server | SONY VAIO - SVE14126CXB (2012) |
RAM | 8GB (maxed out) |
CPU | Intel i5-3210M |
SSD (os) | 128GB |
SDD | 1TB |
Beelink mini PCs & TrueNAS server
- Create flux namespace and the necessary sops secret
export SOPS_AGE_KEY_FILE='<path-to-key.txt>'
make bootstrap0
- Flux installation
export GITHUB_TOKEN='ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
make bootstrap
This homelab uses a dual ExternalDNS setup to manage both internal (homelab) and external (public) DNS records automatically from Kubernetes resources.
Two ExternalDNS Instances:
-
external-dns (AdGuard Home) - Internal DNS
- Provider: AdGuard Home via webhook
- Sources: Ingress resources
- Purpose: Automatically creates A records pointing to nginx-ingress (
10.99.99.25
) for all services - Usage: Internal homelab DNS resolution
-
external-dns-cloudflare - External DNS
- Provider: Cloudflare
- Sources: DNSEndpoint CRDs only
- Purpose: Creates CNAME records pointing to Cloudflare Tunnel for external access
- Usage: Public DNS records for services accessible outside the homelab
For a service to be accessible both internally and externally:
-
Create an Ingress (automatically managed by AdGuard ExternalDNS)
ingress: enabled: true className: nginx hosts: - host: 'notes.${HOMELAB_DOMAIN}'
→ AdGuard creates:
notes.example.com → A → 10.99.99.25
-
Create a DNSEndpoint in
kubernetes/apps/network/external-dns-cloudflare/dnsendpoints.yaml
(managed by Cloudflare ExternalDNS)apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: name: homelab-notes spec: endpoints: - dnsName: notes.${HOMELAB_DOMAIN} recordType: CNAME targets: - ${CF_TUNNEL_ID}.cfargotunnel.com
→ Cloudflare creates:
notes.example.com → CNAME → tunnel
Result: Same DNS name exists in both providers with different targets - internal clients use AdGuard (direct to ingress), external clients use Cloudflare (via tunnel).
This homelab implements a comprehensive three-tier backup strategy covering databases, application data, and file systems.
Type | Primary | Cloud |
---|---|---|
PostgreSQL | CNPG Cluster | Cloudflare R2 |
Volumes | Longhorn Volumes | Cloudflare R2 |
- Tool: CloudNative-PG (CNPG) operator with Point-in-Time Recovery
- Architecture: 3-replica PostgreSQL cluster for high availability
- Backup: Continuous WAL archiving and base backups to Cloudflare R2
- Recovery: Point-in-Time Recovery (PITR) capability
- Covers: Immich, Movary, Fresh RSS, Vikunja, Speedtest Tracker
- Tool: Longhorn volume snapshots and backups
- Schedule: Automated snapshots and backups
- Storage: Longhorn distributed block storage with backups to Cloudflare R2
- Covers: All persistent volumes including SQLite databases, application data, and configuration files
- Tool: Restic
- Schedule: Daily at 03:00 AM
- Retention: 90 days local
- Storage: External HDD (
/home/admin/seagate/backups
)
- Tool: Rclone → Backblaze B2
- Schedule: Every 2 days at 04:30 AM
- Sources: Personal files (
/mnt/mega/aditya
), Photos (/mnt/mega/photos
)
All backup credentials are encrypted with SOPS/Age and managed through GitOps. Recovery procedures involve:
- Database restores from CNPG Point-in-Time Recovery or replica promotion
- Volume restores from Longhorn snapshots and backups
- File system restores using
restic restore
from local or cloud repositories - Application data persistence through Longhorn distributed storage
- sops (secrets management)
- age (encryption)
- precommit
- Proxmox Cloud init Template - https://technotim.live/posts/cloud-init-cloud-image/