GitOps#
What Is It?#
GitOps is a way of managing your self-hosted solution where a git repository is the single source of truth for everything. Instead of logging into servers and making changes by hand, you describe what you want in text files, commit them, push — and the system automatically makes it happen.
Think of it like writing a shopping list and handing it to someone who goes to the store for you. You don’t walk the aisles yourself — you just write down what you need, and it gets done. In PSW, the “shopping list” is your user project , and the “someone” is the convergence engine.
Why Does It Matter?#
Without GitOps, managing a self-hosted solution usually looks like this:
- SSH into the server
- Run some commands to install an app
- Edit config files by hand
- Hope you remember what you did next time something breaks
This works fine for one app on one server, but it quickly falls apart:
- No history — what changed? When? Why?
- No reproducibility — if the server dies, can you rebuild it?
- No safety net — a typo in a config can break things with no way to undo
GitOps solves all of this. Your entire self-hosted solution is defined in files that are tracked by git. Every change is recorded, reversible, and auditable. If your server explodes tomorrow, you can rebuild it from scratch — because the truth lives in git, not on the server.
GitOps is also the first layer of disaster recovery . Your off-site git remote is what brings back the shape of your project (which apps, which targets, what your network looks like). The other two layers — the panic backup for secrets, and per-app bundles for application data — are documented separately because they have different cadences and storage needs. Push your project to a remote you don’t share a fate with (GitHub, a Forgejo instance on a different machine) and you’ve already done the work for layer one.
How PSW Implements GitOps#
PSW’s GitOps workflow has four pieces working together:
1. The User Project (Your Blueprint)#
Your user project is a git repository that describes everything about your self-hosted solution: which apps to run, which targets to deploy them on, how your network is laid out, and what secrets are needed. This is the desired state — what you want your self-hosted solution to look like.
2. Forgejo (Your Git Server)#
Forgejo is a self-hosted git server that runs on your bootstrap target as one of the core apps . It holds a copy of your user project and acts as the central repository that the server watches for changes.
3. Convergence (Your Automation Engine)#
The convergence engine runs on the bootstrap target and continuously compares the desired state (what’s in git) with the actual state (what’s running on your servers). When they don’t match, it makes changes to close the gap — creating targets, deploying apps, wiring them together.
4. Pull-Based Deployment#
This is a key distinction: the server pulls changes from git, rather than you pushing commands to the server. You never tell the server “deploy this now.” Instead, you update your project files, push to Forgejo , and the convergence engine picks up the changes on its own — either when the systemd timer fires (every 5 minutes) or immediately via a webhook.
The Workflow#
Here’s what day-to-day management looks like with GitOps:
You: psw app add jellyfin --target media ← describe what you want
psw project render ← generate deployment files
git commit -m "add jellyfin" ← record the change
git push ← send it to Forgejo
│
▼
Forgejo receives the push
│
▼
Convergence detects new commit
├── Compares git diff: last deployed commit → current HEAD
├── Sees jellyfin was added to the media target
├── Creates the media target if it doesn't exist
├── Deploys Jellyfin with all its dependencies
└── Wires up routing, SSO, monitoring (see conventions)
│
▼
jellyfin.yourdomain.ca is live — you didn't touch a serverDeclarative, Not Imperative#
A fundamental idea behind GitOps is that you declare what you want, not how to get there.
| Approach | What You Say |
|---|---|
| Imperative (without GitOps) | “SSH in, pull this image, create this container, set these env vars, configure this reverse proxy…” |
| Declarative (with GitOps) | “I want Jellyfin on the media target” |
PSW figures out the “how” for you — creating targets, installing runtimes, deploying containers, configuring routing
, setting up SSO
, registering monitoring
, and more. All from a simple service.yml file.
Drift Detection#
Every time convergence
runs, it compares the current git HEAD with the last successfully deployed commit using git diff. This tells it exactly what changed:
- A new service file appeared → deploy a new app
- A service was marked
state: absent→ tear it down - Network config changed → re-deploy everything
- Nothing changed → do nothing (this is idempotent )
This means convergence is also self-healing. If someone manually changes something on a server, the next convergence run detects the drift and corrects it — because git is always right.
Self-Loop Prevention#
Convergence
sometimes makes its own commits (updating convention
files, recording discovered IPs). To avoid an infinite loop where convergence triggers itself, it prefixes its commits with converge: and skips runs where all new commits are convergence-made.
Key Principles#
| Principle | What It Means |
|---|---|
| Single source of truth | The git repository defines reality — not what’s running on the server |
| Declarative | You describe the desired state; PSW figures out how to reach it |
| Pull-based | The server pulls changes from git, rather than you pushing commands |
| Versioned | Every change is a git commit — you can see history, diff, and roll back |
| Self-healing | Drift from desired state is automatically corrected |
| Idempotent | Running convergence twice without changes produces zero changes |
| Observable | Every convergence run produces a structured report |
How It All Connects#
- Your user project is the git repository that defines everything
- Forgejo hosts the repository on your server (deployed during bootstrap )
- Convergence watches the repository and deploys changes automatically
- Secrets are encrypted with SOPS so they’re safe to commit to git
- Providers handle the infrastructure plumbing (DNS, DHCP, certificates) that convergence configures
- Conventions ensure apps are automatically wired together after deployment