Home Services
This repository manages home-lab services through two delivery paths: existing host Compose stacks and a Talos Kubernetes cluster reconciled by Argo CD.
Repository layout
compose-services/
media_nas1/
compose.yaml
media_rhel1/
compose.yaml
terraform/
bootstrap/applicationsets/
infrastructure/
services/
vaultwarden/
composectl.py
update_containers.sh
.gitea/
workflows/
deploy.yaml
compose-services/contains host-specific Compose stacks managed by Gitea Actions.infrastructure/andservices/contain Helm charts discovered by Argo CD ApplicationSets.terraform/bootstraps Talos and installs the Argo CD engine; it does not manage day-two workloads.composectl.pyinfers Compose stack names and paths fromcompose-services/.update_containers.shis a compatibility shim that delegates to the Python tool.
Runtime support
The CLI detects and uses whichever runtime is available:
docker compose ...podman compose ...
This means the same service directories can be managed from either backend without changing the compose files.
Common commands
From the repository root:
python3 composectl.py --help
python3 composectl.py --dry-run --stack media_nas1 refresh
python3 composectl.py --dry-run --stack media_rhel1 refresh
python3 composectl.py --stack media_nas1 pull
python3 composectl.py --stack media_rhel1 status
The shell entrypoint delegates to the Python tool:
bash update_containers.sh --dry-run --stack media_rhel1 refresh
Refresh flow
The refresh flow validates and updates one stack:
compose configpullup -d --remove-orphans- optionally
image prune -fwhen--pruneis supplied
The up operation avoids an unnecessary down, reducing downtime during normal updates.
GitOps bootstrap
The Kubernetes path accepts a nodes list containing any number of control-plane and worker nodes. Each node has a unique name, IP address, and role; exactly one control-plane node is marked bootstrap = true. The endpoint, Kubernetes version, storage device, Cilium interface, and load-balancer address pool remain explicit inputs because they are site-specific.
- Copy
terraform/terraform.tfvars.exampleto a local.tfvarsfile and fill in the supported Kubernetes version, stable control-plane endpoint, andnodeslist. - Run
terraform init,terraform fmt, andterraform validatefromterraform/. - Boot every KVM VM from the Talos ISO with its install disk attached. Terraform applies the role-specific configuration to every node, installs Talos to
install_disk, bootstraps the selected control-plane node, and installs Argo CD. - Supply the Gitea credential through Terraform variables or create the Argo CD repository Secret out-of-band. Never commit tokens or private keys.
- Apply both manifests with
kubectl apply -f bootstrap/applicationsets/. - Confirm Argo CD discovers
infrastructure/cilium,infrastructure/longhorn, andservices/vaultwarden.
Talos uses its default CNI during bootstrap so Argo CD can start. Cilium is then reconciled as the first infrastructure application. Confirm the LAN interface and address pool before enabling production LoadBalancer services. Longhorn also requires a validated disk or mount on the storage node.
The Terraform Talos provider uses one generated machine-secrets bundle for the whole node list. If the nodes were previously configured with another Talos CA, reset/reinstall them or restore the matching Terraform state before applying. Terraform state contains the generated Talos credentials and must remain private.
Terraform's Talos client certificates must match the CA already installed on the nodes. If apply reports x509: certificate signed by unknown authority, stop and verify the node IPs and recover the original machine-secrets bundle/state. Recreating Terraform secrets without rebuilding the nodes will not repair the trust relationship.
Secrets
SOPS with age is the intended secret boundary. The repository policy is in .sops.yaml; age private keys and plaintext secret files are ignored. Provision the age private key to the cluster through a protected bootstrap process, then commit only files matching the repository's encrypted-file policy. Vaultwarden expects an existing Secret named vaultwarden-secrets.
Gitea Actions deployment
The workflow in .gitea/workflows/deploy.yaml deploys each host-specific stack to its matching self-hosted runner:
- runner label
nas1deploysmedia_nas1with Docker - runner label
rhel1deploysmedia_rhel1with Podman
Install and register one persistent Gitea Actions runner on each server. The runner should use a dedicated service account and should only execute trusted changes from the protected main branch.
Deployments run on:
- pushes to
main - manual workflow dispatch
- the first Friday of each month at
03:00using a fixed UTC-6 offset (09:00 UTC)
The scheduled trigger evaluates every Friday and skips dates after the seventh day, leaving only the first Friday eligible. The host jobs always target their own stack; they do not use --all.
To roll back, dispatch the workflow from a known-good commit or revert the change on main and redeploy.