Skip to content

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 from agent to main when 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 to agent and on every PR to main. Runs the full pytest suite (~1100 tests). Must pass before deploy.
  • agent-deploy.yml — runs ONLY on push to main. 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:

  1. Pulls the new image from GAR
  2. Stops the running container
  3. Restarts it with the same env + volume mounts
  4. Verifies the new container is healthy
  5. 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 in prod-stacks/shared-services and 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.