Deployment & CI
Self-hosted end to end on the home server (Mac Mini, arm64, OrbStack). Code, CI, and the container registry all live on Forgejo at git.plexultra.com (migrated off GitHub/GHCR in June 2026).
Pipeline
- Commit and push to Forgejo (
originis theforgejo:SSH alias → 192.168.1.124:2222, LAN only). - Forgejo Actions (
.forgejo/workflows/docker-build.yml) runs onmac-mini-runnerand builds + pushes two images, natively arm64 (no QEMU):git.plexultra.com/dlawler489/etsy-finance-tracker:main(API,productionstage)git.plexultra.com/dlawler489/etsy-finance-tracker-nginx:main(nginx,nginx-frontendstage)- Both also tagged with the commit SHA (rollback targets).
- Redeploy in Dockhand, which pulls
docker-compose.deploy.ymland recreates the stack.
The deploy host must be docker login git.plexultra.com (or the packages public) to pull.
Runner setup (gitea/act_runner in docker-compose)
The runner runs each job in a container and has /var/run/docker.sock mounted. Key facts that make the workflow work:
- No Node in job containers → can't use JS actions like
actions/checkout. The workflow is node-free: manualgit init/fetch/checkoutover HTTPS with a token. - Job runs in
container: docker:cli(Alpine) →shell: sh, andapk add gitfor checkout. - act_runner auto-mounts the Docker socket into job containers — do not add it in the workflow's
volumes:(causesDuplicate mount point). runs-on: ubuntu-latest— the label only selects the runner; thedocker:clicontainer override does the work. (The runner re-registered with default labels once aconfig.yaml/CONFIG_FILEwas added, dropping the originaldockerlabel.)- Runner
config.yaml(loaded viaCONFIG_FILE=/data/config.yaml) setscontainer.valid_volumes.
Registry auth
Pushing packages needs a Forgejo token with write:package, stored as the Actions secret REGISTRY_TOKEN and used for docker login. The automatic GITHUB_TOKEN cannot push packages (unauthorized: reqPackageAccess); it's still used for the git checkout.
Traefik
Label discovery was unreliable; routing uses the file provider: /Users/davidlawler/docker/traefik/dynamic/etsy.yml routes Host(etsy.plexultra.com) → etsy-nginx:80 (websecure, le certresolver).
Persistence & secrets
- MongoDB on a named volume (
etsy_mongodb) — survives container recreation. Clearing orders/expenses in-app does not touch the volume. JWT_SECRETset in Dockhand env (don't leave the default).- Etsy keystring/secret and OAuth tokens are stored unencrypted in MongoDB — fine for single-user self-host, but a DB backup contains them.