generated from atu/saiop-infrastructure
2.2 KiB
2.2 KiB
name, description, version, author, license, platforms, prerequisites, metadata
| name | description | version | author | license | platforms | prerequisites | metadata | |||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| saiop-git-sync-sidecar | Pattern for keeping a container's data volume continuously synced to a Gitea repo via a small sidecar container — used for the Obsidian vault, reusable elsewhere. | 1.0.0 | Claude Code (SAIOP ops session) | MIT |
|
|
|
Git-sync sidecar pattern
When to use
Any time an app's data volume should be continuously backed by a Gitea repo
(used for the Obsidian vault → atu/saiop-knowledge). Generalizes to any
app that just reads/writes files in a directory without needing to know
git exists.
The pattern
A second, tiny container (alpine/git) mounts the same volume as the
main app and loops: git pull → check git status --porcelain → if dirty,
add/commit/push → sleep. The main app never needs to know about
git at all.
Gotchas hit building this
rm -rfon the mount point itself fails ("Resource busy") — you can't remove a bind-mounted directory, only its contents. Usefind "$DIR" -mindepth 1 -delete, orgit clone <url> .directly into the already-existing empty directory instead of removing-then-cloning.- UID mismatch silently breaks the main app's write access. If the
sidecar runs as root (the
alpine/gitdefault) and the main app runs as a non-rootPUID/PGID(common for LinuxServer.io images), every file the sidecar creates is unwritable by the main app. Setuser: "UID:GID"on the sidecar to match the main app's user — don't just chown once after the fact, the mismatch comes back on every commit. - Add a
.gitignorefor the app's own volatile state files (Obsidian:workspace.json,cache/) before the first commit, or every sync cycle produces a noise commit even with zero real content changes. - Don't hardcode a container's internal IP for inter-container access —
IPs churn (confirmed twice on this stack already). Use the container
name if both containers share a network (Docker's embedded DNS
resolves it), which is why the sync sidecar talks to
gitea:3000directly rather than an IP.