Files
saiop-skills/skills/saiop-infrastructure/saiop-git-sync-sidecar/SKILL.md
T

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
linux
env_vars commands
docker
git
hermes
tags
SAIOP
git
sync
Gitea
pattern
sidecar

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/pushsleep. The main app never needs to know about git at all.

Gotchas hit building this

  1. rm -rf on the mount point itself fails ("Resource busy") — you can't remove a bind-mounted directory, only its contents. Use find "$DIR" -mindepth 1 -delete, or git clone <url> . directly into the already-existing empty directory instead of removing-then-cloning.
  2. UID mismatch silently breaks the main app's write access. If the sidecar runs as root (the alpine/git default) and the main app runs as a non-root PUID/PGID (common for LinuxServer.io images), every file the sidecar creates is unwritable by the main app. Set user: "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.
  3. Add a .gitignore for 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.
  4. 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:3000 directly rather than an IP.