# GitOps: config-as-code with `.tinyforge.yml` A **dockerfile** or **static** workload can read part of its deploy config from a `.tinyforge.yml` file in its own repo. Tinyforge fetches the file, shows you how it differs from the live config (**drift**), and applies it when you click **Sync** — so the repo becomes the source of truth for the declared fields. This is opt-in per workload and **manual-sync only** in v1: nothing is applied automatically on deploy, and a sync never runs without an explicit admin action. ## Enabling it 1. Open the workload (Apps → your app). 2. In the **GitOps** panel, toggle it on. The default file path is `.tinyforge.yml` at the repo root; change it if your file lives elsewhere (e.g. `deploy/.tinyforge.yml`). 3. Add a `.tinyforge.yml` to the repo (schema below) and push. 4. The panel shows the parsed file and any drift vs. the live config. Click **Sync now** to apply the repo's values to the workload. Only **dockerfile** and **static** sources are eligible — they're the git-backed sources. `image` and `compose` workloads don't show the panel. ## `.tinyforge.yml` schema (v1) ```yaml version: 1 # required, must be 1 deploy: # dockerfile only: port: 8080 # container port the app listens on healthcheck: /healthz # HTTP path probed before a blue-green cutover ("" to disable) # dockerfile + static: deploy_strategy: blue-green # "" | recreate | blue-green ``` Notes: - **Only the fields above are honored.** Unknown keys are rejected with an error (so a typo surfaces instead of being silently ignored). - Fields you omit are **left untouched** — the file overlays only what it declares; it never clears the rest of your config. - The file is **source-aware**: a `static` workload only honors `deploy_strategy` (a static site has no port/healthcheck); `port`/`healthcheck` in a static site's file are ignored. - `deploy_strategy: ""` and `recreate` are equivalent (both are the default for dockerfile and static), so they never show as drift against each other. ## What `.tinyforge.yml` does **not** contain - **No repo location** (provider / owner / repo / branch) and **no access token** — those stay in Tinyforge's encrypted database. This is deliberate: it keeps credentials out of your repo. (You need the repo coords to find the file in the first place, so they can't live in it.) ## Drift and sync - **Drift** is computed only over the fields the file declares, after normalization (so a defaulted strategy or a YAML-int vs stored-number difference isn't a false positive). - **Sync** fetches the file, merges the declared fields onto a copy of the live config, **validates the merged result** with the source's own rules, and only persists it if it passes — a bad file is rejected as a whole and never leaves a partial config. The sync is recorded to the workload's activity log (not the deploy ledger — it changes config, it isn't a deploy). - While GitOps is enabled, the edit form shows a banner noting which fields the repo manages; editing them in the UI works, but the next Sync overwrites them with the repo's values. ## Not in v1 (planned) These are intentionally out of scope for the first version; the design leaves clean seams for them: - **`env` and `faces` (public subdomains)** — they live in separate stores and (for `env`) would re-introduce a secrets-in-repo risk; deferred to a typed multi-target apply. - **Auto-apply on deploy** — applying the repo config automatically on every push. v1 keeps a human in the loop with the drift view + manual Sync. When added, it will read the file at the exact deployed commit (a source-plugin concern), not at dispatch time. - **Multi-workload reconcile** — one repo declaring/creating/deleting many workloads (the full Flux/Argo model). v1 is per-workload, config-only, with no create/delete. - **`image` / `compose` sources** — not git-backed / overlapping config surface.