Git as the source of truth
The private GitOps repository stores bootstrap objects, child applications, namespace definitions, chart versions, and application values.
ENGINEERING JOURNAL
A field note from moving a three-node Kubernetes home lab beyond cluster bootstrap and into a repeatable GitOps operating model. This phase established Argo CD application management, version-pinned Helm deployments, dynamic persistent storage, the first stateful service, and Discord-based operational alerting.
OBJECTIVE
The Kubernetes nodes and Cilium networking were already healthy, but the environment still behaved like a collection of manually administered systems. The objective of this phase was to establish a repeatable deployment path where application configuration lived in Git, Argo CD reconciled the intended state, Helm supplied reusable packaging, and every service appeared in a central health and synchronization view.
PLATFORM PATTERN
flowchart LR
GIT[Private GitHub Repository] --> ROOT[Argo CD Root Application]
ROOT --> NS[Namespace Application]
ROOT --> STORAGE[Local Path Provisioner]
ROOT --> KUMA[Uptime Kuma]
STORAGE --> PVC[Persistent Volume Claim]
PVC --> KUMA
KUMA --> DISCORD[Discord Lab Alerts]
The private GitOps repository stores bootstrap objects, child applications, namespace definitions, chart versions, and application values.
The root Argo CD application watches the applications directory and creates or reconciles each child application.
Argo CD uses read-only repository access while the administration path uses a separate write-enabled deploy key.
Application health, synchronization status, source, chart revision, namespace, and destination are visible from one dashboard.
HELM CONFIGURATION STANDARD
Each Helm-based service keeps a complete upstream values file as values.defaults.yaml and a separate editable values.yaml. This leaves the supported configuration surface searchable in Git, preserves the original defaults used during deployment, and makes later upgrades or troubleshooting easier to reason about.
Repository pattern
infrastructure/
├── local-path-provisioner/
│ ├── values.defaults.yaml
│ └── values.yaml
└── uptime-kuma/
├── values.defaults.yaml
└── values.yaml
DYNAMIC STORAGE
The cluster initially returned no StorageClass resources. Rancher Local Path Provisioner was deployed through Argo CD and configured as the default storage class. It provides functional dynamic provisioning for the current lab phase without introducing the operational weight of a distributed storage system before it is needed.
The immediate requirement is reliable functionality, repeatable deployment, and persistent application state. Local Path Provisioner meets that requirement with minimal infrastructure overhead.
The storage is node-local and not replicated. It is suitable for this learning phase, but it is not the final answer for workload mobility or high availability.
The default StorageClass uses delayed volume binding so storage is created after Kubernetes selects the workload node.
Ceph or another distributed storage layer can replace this implementation when resilience becomes a primary platform requirement.
FIRST OPERATIONAL SERVICE
Uptime Kuma was selected because it immediately adds operational value while exercising the full platform path. The application was deployed from a version-pinned OCI Helm chart, configured through the private Git repository, given a dedicated namespace, and attached to a 2 Gi persistent volume. SQLite was selected because the deployment is small and does not justify a separately managed database service.
Validated deployment path
Git commit
↓
Private GitHub repository
↓
Argo CD root application
↓
Uptime Kuma child application
↓
Pinned Helm chart + tracked values
↓
Kubernetes Deployment, Service, and PVC
↓
Running monitor with persistent state
ALERTING AND TROUBLESHOOTING
A dedicated Discord webhook connects Uptime Kuma to the lab alert channel. Initial testing exposed two configuration failures: the notification was first set to post to a thread without a valid thread identifier, and a later test used an invalid webhook token. Separating the channel mode problem from the credential problem led to a clean configuration using a normal channel webhook with no channel or thread ID required.
The request included an undefined thread identifier because the Discord notification mode did not match the target channel type.
Discord returned an unauthorized response until the webhook was recreated and the complete URL was copied into Uptime Kuma.
OUTCOME
This phase changed the cluster from a working Kubernetes installation into an environment with a defined operating model. Application state is visible in Argo CD, configuration is versioned in Git, Helm releases are pinned, persistent storage is provisioned automatically, service state survives container replacement, and monitoring events can leave the cluster through an operational notification path.
RELATED WORK