Skip to content

veta-auto-pull (continuous deploy)

veta-auto-pull is the continuous deployment mechanism for the production server. Every 5 minutes a systemd timer fires a script that:

  1. Polls origin/main via git ls-remote (no full clone)
  2. Compares the remote SHA against state/last-deployed-sha
  3. If different, clones main shallow and self-installs the latest homelab-deploy.sh over /opt/stacks/veta/deploy.sh
  4. Runs deploy.sh which rsyncs compose files and runs docker compose up -d with healthcheck gating
  5. On success, writes the new SHA to state/last-deployed-sha
  6. On failure, leaves the file unchanged so the next tick retries

This replaced Watchtower in 2026-05 (Watchtower had a recreate-name collision bug that left the platform half-deployed indefinitely).

.github/workflows/ci.yml originally had a deploy job that SSHed from a GH runner to the server’s LAN address. GitHub runners are on public IPs; the server is on a private LAN. The ssh-keyscan step always failed.

Inverting the direction so the server polls GitHub sidesteps the network problem entirely without a tunnel, a Tailscale, or a self-hosted runner.

The 5-minute cadence is the sweet spot:

  • Fast enough that operators do not notice the lag (~3.5 min mean)
  • Slow enough to avoid hammering GitHub’s API
  • Aligns with the 6-minute wait in the post-deploy CI probe
Terminal window
# 1. Copy the auto-pull script to the stack dir
sudo install -m 0755 \
/path/to/repo/scripts/homelab-auto-pull.sh \
/opt/stacks/veta/auto-pull.sh
sudo chown miles:miles /opt/stacks/veta/auto-pull.sh
# 2. Install systemd units
sudo install -m 0644 \
/path/to/repo/scripts/homelab-systemd/veta-auto-pull.service \
/etc/systemd/system/
sudo install -m 0644 \
/path/to/repo/scripts/homelab-systemd/veta-auto-pull.timer \
/etc/systemd/system/
# 3. Enable + start
sudo systemctl daemon-reload
sudo systemctl enable --now veta-auto-pull.timer
PathWritten byMeaning
state/last-deployed-shaauto-pull.shLast main SHA we successfully ran deploy.sh for
state/auto-pull.lockauto-pull.shflock for single-instance guard
.good-shadeploy.shLast SHA the gateway reported as its baked-in version after a successful deploy

.good-sha and last-deployed-sha may legitimately diverge by one CI cycle. They answer different questions and one is not a corrupted copy of the other. See the SHA semantics note below.

Terminal window
# When does it next run?
systemctl list-timers veta-auto-pull.timer
# Recent activity (deploys, retries, errors)
journalctl -u veta-auto-pull.service -n 50 --no-pager
# Force a check now (e.g. you just merged something hot)
sudo systemctl start veta-auto-pull.service
# Pause auto-deploys (e.g. during a manual debug session)
sudo systemctl stop veta-auto-pull.timer
# Resume
sudo systemctl start veta-auto-pull.timer

The two SHA files answer different questions:

  • state/last-deployed-sha: “what main SHA did I last try to deploy?”
  • .good-sha: “what SHA did the gateway report as its baked-in version after the deploy succeeded?”

These may diverge by one CI cycle: if main moves twice in 5 min (dependabot plus a feature merge), CI rebuilds the gateway image twice. By the time the second auto-pull tick fires, the gateway image for the newer SHA might not be ready yet on GHCR. The pull picks up the older image, the gateway reports version = X, and .good-sha = X while last-deployed-sha = Y.

This is expected, not a drift bug.