Deploy flow
Continuous deployment, fully scripted. A push to main updates all
three production containers in ~3-5 minutes with no manual steps.
The pipeline
flowchart LR
A["git push main"] --> B["GH Actions<br/>Agent Tests"]
B --> C["GH Actions<br/>Agent Deploy"]
C --> D["Build Docker image"]
D --> E["Push to<br/>Google Artifact Registry"]
E --> F["Webhook to dockhand"]
F --> G["dockhand pulls + restarts<br/>3 containers"]
Branches
main— production. Push here only via merged PR.agent— development. All feature work happens here. Open a PR fromagenttomainwhen ready; merge to deploy.
The branch model is intentional. agent is the working branch;
main is the deployed branch. The two diverge briefly between
PR opens and PR merges, then re-converge on merge.
GitHub Actions
Workflow files at .github/workflows/:
agent-tests.yml— runs on every push toagentand on every PR tomain. Runs the full pytest suite (~1100 tests). Must pass before deploy.agent-deploy.yml— runs ONLY on push tomain. Builds the Docker image, pushes to Google Artifact Registry (europe-west3-docker.pkg.dev/flaredog/flaredog/companybot), then POSTs to dockhand's webhook URL to trigger the redeploy.
The agent-deploy.yml job depends on agent-tests.yml having
passed for the same commit — PR merges that haven't passed CI
won't deploy.
dockhand
Source: ~/platform/infra/prod-stacks/dockhand/. Watches the
registry tags for prod-accounting-bot, prod-accounting-webhooks,
prod-accounting-booker (all three pull from the same
companybot:latest image — they differ only in CMD).
On webhook receipt, dockhand:
- Pulls the new image from GAR
- Stops the running container
- Restarts it with the same env + volume mounts
- Verifies the new container is
healthy - Logs success/failure to its own systemd journal
If any of the three fails to come up, dockhand keeps the running container alive and surfaces an alert to Sentry.
Secrets
Never deployed via the image. Three env vars get baked in at container start by the entrypoint wrapper:
INFISICAL_TOKEN long-lived machine-identity token
INFISICAL_PROJECT_ID project UUID
INFISICAL_PATH secret folder path (/companybot/agent)
The entrypoint runs infisical run --projectId=... --env=prod
--path=... -- <cmd> so all 32+ secrets land as env vars only in
the running process. They never touch disk, never appear in
image layers, never get logged.
Manual kick
Sometimes you want to redeploy the same code (e.g. config flip, restart after Infisical secret rotation). Two ways:
- GH Actions UI → Agent Deploy workflow → "Run workflow" on main → same pipeline runs
- dockhand directly → POST to its webhook with the container name in the payload → it pulls latest tag and restarts
The bot also exposes /restart-bot (allowlist-gated) for when
the gateway connection wedges and you just need a process bounce
without a full deploy.
What you cannot deploy
- The shared
prod-postgres,prod-redis,prod-falkordb,prod-nats, etc. — those live inprod-stacks/shared-servicesand are managed independently. Agent code never redeploys shared infra. - DNS / Traefik config — that's in
prod-stacks/traefik/dynamic/routes.yml; manual edits + Traefik reloads. - Cloudflare Pages (this docs site!) — separate CF Pages project watching the same GitHub repo.