Skip to content
Deployment and Updates

Deployment and Updates

Declarative layers

    flowchart LR
  git[("Gitea repositories")] --> checks["Restricted CI<br/>validation · build · scan"]
  checks --> registry[("Gitea registry<br/>commit-tagged images")]
  registry --> trusted["Repository-scoped runners<br/>deploy · synchronize"]

  subgraph converge["DECLARATIVE CONVERGENCE"]
    direction TB
    tofu["OpenTofu<br/>Proxmox VMs"]
    ansible["Ansible<br/>hosts · mounts · timers"]
    tofu --> ansible
  end

  trusted --> tofu
  ansible --> compose["Docker Compose<br/>household services"]
  trusted --> k3s["K3s<br/>personal web apps"]
  trusted --> knowledge["Open WebUI<br/>knowledge collections"]
  compose <--> state[("TrueNAS + volumes<br/>mutable state")]
  k3s <--> state

  classDef source fill:#282416,stroke:#c6a95b,color:#f2e4b8;
  classDef automation fill:#2b1d15,stroke:#c47d4a,color:#f3d1ba;
  classDef compute fill:#211b32,stroke:#9c82d4,color:#e3d9fa;
  classDef storage fill:#10271e,stroke:#58b184,color:#c7efda;
  class git source;
  class checks,trusted,tofu,ansible automation;
  class compose,k3s,knowledge compute;
  class registry,state storage;
  

Infrastructure definitions make the intended shape inspectable and repeatable without pretending every runtime byte belongs in Git. Applications receive secrets at deployment time; plaintext credentials do not belong in definitions or documentation.

Validation and deployment

Strict repository checks validate inventory consistency, suspicious tracked files, Ansible syntax, the rendered inventory, and every Compose project. CI uses placeholder values, so routine validation does not require production decryption keys.

Operations-repository deployment is manually dispatched through Gitea Actions. A concurrency guard permits one deployment at a time, and the operator can target the complete active environment or a single VM role. Ansible converges the selected hosts from the reviewed Git revision. Application repositories deploy automatically only after their commit-tagged image build succeeds.

Gitea Actions trust boundaries

Actions run on ward-vm-cicd-01, not on an application VM. General jobs use rootless Docker-in-Docker runners with no host Docker socket. The instance-wide gitea-shared-ci runner exposes only the shared-ci label, runs one job at a time, and is used for application builds. The repository-scoped home-ops-ci runner handles validation, secret scanning, and Renovate for the operations repository.

Both restricted runners have fixed outer-container addresses. Host firewall rules allow them to reach Gitea and DNS while rejecting other RFC1918 networks. They can check out source and push images, but they cannot contact Kubernetes, Open WebUI, or other private services.

Production jobs use separate repository-scoped runners:

RepositoryTrusted labelResponsibility
home-opshome-ops-deployManual Ansible convergence
home-ops-docshome-ops-docs-deployDocumentation rollout to K3s
personal-websitepersonal-website-deployWebsite rollout to K3s
recipe-bookrecipe-book-deployRecipe rollout and knowledge synchronization
web-logweb-log-deployWeb log and files-service rollout
knowledge-baseknowledge-base-syncOpen WebUI knowledge synchronization

These runners execute directly inside hardened runner containers. They have no Docker socket or host filesystem mounts, and each registration identity can accept jobs only from its own repository. Kubernetes configuration and Open WebUI API keys are supplied only to the corresponding trusted job.

Application workflows separate build from deployment. A build runs on shared-ci, pushes an immutable image tagged with the Git commit, and must succeed before the trusted deployment job starts. This keeps untrusted build tools away from production credentials and private network access.

Update policy

Container versions are pinned rather than allowed to float silently. Renovate runs daily, maintains a dependency dashboard, and manages an explicit allowlist of public images. An update must be approved before Renovate opens a pull request.

Release notes are reviewed before merge, then the normal validation and deployment path applies. App-owned databases and caches are upgraded with the parent application’s guidance rather than as unrelated image changes.

Renovate currently covers selected Compose images. Operating-system packages, Ansible collections, Dockerfiles, language dependencies, firmware, and software outside the allowlist still require deliberate maintenance.

Compose and K3s

Compose is the default because it is direct and well suited to stateful household services on known hosts. K3s runs on a dedicated single-node VM for personal web applications and Kubernetes experimentation. It is not a migration target for every mature Compose workload.