Mutable data outside the repo (CAIRN_DATA_DIR)
Source: cairn/ts/src/data.ts · commit
1fc6292Category: Pattern — deployment
CAIRN_DATA_DIR — a small convention for decoupling code from runtime-mutable content in git-deployed Node apps.
What it is
Section titled “What it is”Runtime-mutable files live at a path controlled by an environment variable (CAIRN_DATA_DIR), not inside the repo. The repo ships only seed templates — copied to the target path on first boot if it’s empty.
Why it exists
Section titled “Why it exists”The problem: Cairn’s admin CMS writes ts/data.json and ts/posts/ on the live VPS. Those files were tracked in git. On the next git pull, one of two things happens:
- The VPS’s admin-edited file is silently overwritten by what’s in the repo.
- The pull aborts with a conflict and blocks every deploy.
The fix: split code from content. Code lives in the repo. Content lives on the box at $CAIRN_DATA_DIR. First-boot seeds bridge the gap for fresh installs.
const REPO_TS_DIR = join(__dirname, '..');const DATA_DIR = process.env.CAIRN_DATA_DIR || REPO_TS_DIR;const DATA_PATH = join(DATA_DIR, 'data.json');const SEED_PATH = join(REPO_TS_DIR, 'data.example.json');
// First-boot seed: if data.json is missing, copy from the committed template.if (!existsSync(DATA_PATH)) { if (!existsSync(DATA_DIR)) mkdirSync(DATA_DIR, { recursive: true }); if (existsSync(SEED_PATH)) { copyFileSync(SEED_PATH, DATA_PATH); } else { throw new Error(`No data.json at ${DATA_PATH} and no seed at ${SEED_PATH}`); }}Env var is set on the VPS via a systemd drop-in:
[Service]Environment="CAIRN_DATA_DIR=/var/lib/cairn"ts/data.json and ts/posts/ are gitignored. Committed seeds are ts/data.example.json and ts/posts.example/.
One-time migration
Section titled “One-time migration”For an existing deployment that already has admin-edited data, run before the next git pull:
sudo mkdir -p /var/lib/cairnsudo cp /opt/portfolio/ts/data.json /var/lib/cairn/sudo cp -r /opt/portfolio/ts/posts /var/lib/cairn/sudo chown -R <service-user>:<service-user> /var/lib/cairnThen add the env var to the systemd drop-in and systemctl daemon-reload.
How it’s used
Section titled “How it’s used”- Cairn —
data.jsonand blogposts/are admin-editable surfaces - Pattern generalizes to any app with admin UI + git-based deploys
Gotchas
Section titled “Gotchas”- Migration order is load-bearing. Back up live data to
$CAIRN_DATA_DIRbefore the firstgit pullafter adopting this. Otherwise the pull deletes the tracked files, the service boots, finds no data, seeds from the older template, and admin edits are gone. - Host keys don’t fit this pattern.
host_keyis written on first run and read forever. Gitignored, yes — but no seed template. A seeded host key would mean every fresh install shares the same SSH identity. - Logs, uploads, temp files belong in
$CAIRN_DATA_DIRtoo. Dropping them back in the repo directory re-creates the problem with a different file. - Documentation is part of the pattern. The README deploy section has to say “run the migration before the first git pull” or a future reader will delete their own data.
See also
Section titled “See also”- projects/cairn — the project where this pattern is in use