.claude/ was ignored wholesale, which meant the deploy skill lived only on one machine. Narrow the rule so .claude/skills/ is tracked while settings.local.json stays local. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
3.3 KiB
name, description
| name | description |
|---|---|
| deploy-drinktracker | Deploy drinktracker to the production LXC and verify it came back healthy. Use when asked to deploy, ship, release, push to prod, or update the running app - and after merging changes that need to go live. Also covers rollback and diagnosing a deploy that did not take effect. |
Deploying drinktracker
Run ./deploy/deploy.sh from the repo root. It typechecks, pushes to Gitea, rebuilds the
image on the LXC, restarts the stack and polls until the app answers. Use --yes to skip
the confirmation prompt when the user has already approved the deploy.
deploy/README.md has the full topology table, the one-time sudoers setup, and rollback
commands. Read it before improvising — the layout is unusual in ways that waste time.
Facts that are not discoverable from the repo
- The LXC is
192.168.2.169.drinktracker.tenseconddelay.net(andNEXTAUTH_URL) resolve to the reverse proxy at192.168.2.172. SSHing there fails with what looks like a rejected key but is simply the wrong machine. - The live stack is
/root/drinktracker. The checkouts under/home/drinkadmin/(drinktracker,drinktracker-latest) are stale June copies. Confirm withdocker inspect drinktracker-app-1 --format '{{index .Config.Labels "com.docker.compose.project.working_dir"}}'. - There is no CI. No Gitea Actions, no GitHub workflows.
git pushupdates the repo and nothing else; the image must be built and the stack restarted separately. If someone reports "I pushed but nothing changed", this is why. - Access:
ssh -i ~/.ssh/drinktracker_ed25519 drinkadmin@192.168.2.169. Root SSH is disabled.drinkadminneeds the sudoers drop-in fromdeploy/README.mdfor the script to work unattended.
Verifying
An unauthenticated request to port 3000 returns 307 (redirect to /login) — that is a
healthy response, not a failure. The script accepts 200, 302 and 307.
Each AI call emits one line:
sudo docker logs drinktracker-app-1 2>&1 | grep '\[switchboard\]'
FAILOVER lines are expected and harmless: the gateway's :batch model variants fail on
every request and fall back. CONTEXT OVERFLOW is worth investigating — it means a request
was too large for any model's window and may have been truncated.
Before deploying schema changes
The migrate service runs prisma db push --skip-generate --accept-data-loss on every
start. Removing a field from schema.prisma therefore drops the column and its data on
the next deploy, in both directions — so a rollback past a schema change is destructive too.
Dump the database first when the schema changed:
sudo docker exec drinktracker-db-1 pg_dump -U drinktracker drinktracker > ~/dt-$(date +%F).sql
This is also why UserPreference.defaultProvider is still in the schema despite being
unused: deleting it would drop the column and break restoring older backups.
If the app does not come back
sudo docker compose -f /root/drinktracker/docker-compose.prod.yml ps
sudo docker logs drinktracker-app-1 --tail 100
Most likely causes, in order: the build failed and compose restarted the previous image;
.env.production is missing a variable the new code reads (compare against .env.example);
or the migrate service exited non-zero and app never started, which ps will show as
migrate in a non-zero exited state.