How a merged change reaches production, per service. This page covers the four things we ship on a regular cadence: Groudon, Xatu, Tentacruel, and Honcho.
The mechanics of ArgoCD, charts, batches, and sync waves live in the GitOps pipeline. This page is the layer on top of that: what triggers a build, what writes a new image tag, and how to debug when it goes wrong.
Top-level overview
Images land in GCP Artifact Registry
(us-east4-docker.pkg.dev/plastic-labs-{staging,prod}/plastic-labs-{staging,production}-registry/).
Staging typically deploys on merge to main. Production deploys are gated —
a promotion to stable for Groudon, a manual image-tag PR for Xatu, a manual
flyctl deploy for Tentacruel.
Groudon — gateway and health processes share the same docker image and run
on the kubernetes operations cluster.
- Staging: A merge to
mainbuildsgroudon:<sha>and pushes the new image to GCP Artifact Registry on plastic-labs-staging. It then writes the new image tag into every Groudon manifest file inargo-staging. ArgoCD syncs the Groudon deployments with the new image. - Prod: Someone promotes
main→stable. Same process as staging, but againstargo-production.
Xatu — ingestion and consumer run as separate deployments on the
operations cluster.
- Staging: A push to
main(in the Groudon repo) that touchesxatu/**buildsxatu:<sha>and pushes the new image to GCP Artifact Registry on plastic-labs-staging. It then writes the new image tag intoargo-staging/xatu/{ingestion,consumer}.yaml. ArgoCD syncs the Xatu deployments with the new image. - Prod: Someone triggers a build via the GitHub Actions workflow (in the
Groudon repo), which builds the image and pushes it to GCP Artifact Registry.
Then they open a PR in
argo-productionthat points at the new tag.
Tentacruel — the LiteLLM proxy, still on Fly.
- Staging: merge to
maindeploystentacruel-litellm. - Prod: someone runs
flyctl deployagainsttentacruel-litellm-prod.
Honcho — managed fleet. Merge and deploy are fully decoupled.
- A git tag
v*buildshoncho:deployment-v<ver>into GCP Artifact Registry, for both staging and production, and registers the version with Groudon. Nothing runs it until someone releases it.
Groudon
Gateway and health deployments both run on the same docker image on the
operations cluster. Their kubernetes manifests live in
argo-production/groudon/ and argo-staging/groudon/.
Staging deployment
Merge to main.
- Builds and pushes a new groudon image tagged
groudon:<sha>to GCP Artifact Registry on plastic-labs-staging. - Checks out
argo-stagingand writes the new SHA into every Groudon manifest file that already references the image. - Commits and pushes the changes to
argo-staging. - ArgoCD auto-syncs the gateway and health deployments with the new image, and
runs the
migrate-jobto apply Alembic migrations.
Production deployment
Promotion from main to stable is manual. Once promoted, the production
deployment follows the same process as the staging deployment.
Rolling back
Two options, and the fast one is not the git one:
- Revert the manifest — PR
argo-production/groudon/*.yamlback to the previous SHA. The image already exists in Artifact Registry; no rebuild, ArgoCD syncs in seconds. - Revert the code — revert on
stable, which rebuilds and redeploys. Correct, slower, and the one to use when the bad commit must not stay on the stable branch.
Note that a migration that has already applied is not undone by either of these
rollback options. A migration that has been applied must be manually reverted
by running the alembic downgrade command for the specific migration.
Dashboard
The Groudon dashboard is deployed by Vercel, on merge to main or stable. It
does not pass through ArgoCD.
Xatu
Lives in the Groudon repo under the xatu/ directory, but deploys separately
from Groudon.
Xatu has two process groups — ingestion and consumer — and both run as
their own kubernetes Deployment on the operations cluster in both
plastic-labs-staging and plastic-labs-prod.
Two GitHub Actions are responsible for deploying Xatu to staging and production:
push-gcp-registry-xatu-staging.yml— onmain, buildsxatu:<sha>and writes that tag intoxatu/ingestion.yamlandxatu/consumer.yamlinargo-staging.push-gcp-registry-xatu-prod.yml— a manually triggered workflow. Builds and pushes the new image to GCP Artifact Registry onplastic-labs-prod, but production rollout requires a manual image-tag PR inargo-production.
Rollback is the same shape as Groudon: PR the argo-production/xatu/ manifest
back to a previous SHA rather than reverting and rebuilding.
Tentacruel (LiteLLM proxy)
The LiteLLM proxy every Honcho LLM call goes through. Two Fly apps in iad,
both performance-1x / 2 GB with a minimum of 3 machines:
tentacruel-litellm— staging. This is the appfly.tomldeclares, so a bareflyctl deploytargets it.tentacruel-litellm-prod— production. Deployed with an explicit--app tentacruel-litellm-prod.
Shipping a change
Fly. Merge to main → fly-deploy.yml →
flyctl deploy --remote-only. The workflow passes no --app, so it picks up
fly.toml and deploys staging (tentacruel-litellm). Production
(tentacruel-litellm-prod) is a deliberate manual deploy against that app —
nothing automatic touches it.
Honcho
Honcho is the one service where merging and deploying are fully decoupled. The deployment unit is a git tag, and the rollout is a database write.
1. Tag
Pushing v3.4.5 fires both push-gcp-registry-staging.yml and
push-gcp-registry-prod.yml. Each one:
- Strips the
vand checks the tag againstpyproject.toml’sversion, failing the build on a mismatch. A running instance serves that version at/openapi.json, so the git tag and the version inpyproject.tomlmust agree. Bumppyproject.tomlbefore tagging. - Builds and pushes
honcho:deployment-v<version>. - POSTs
{version, image_label}to<env>/webhooks/v1/add_honcho_versionon Groudon’s API.
2. Register
Groudon’s gateway upserts a honcho_versions row for the newly tagged version.
A new version defaults to available=False, has_migration=True.
available=False means that no one can upgrade this version yet.
has_migration=True means that this version contains a database migration
(default assumption) so tenants will never auto-upgrade to this version.
After this step, the image exists and Groudon knows about it, but no tenants
are running on this version. The release_honcho script in the Groudon repo is
unused; the git tag is the registration.
3. Release
- Run
load_honcho_secrets.pyto write the version’s secrets to AWS Secrets Manager athoncho/secrets/{environment}/{version}. Paths are version-scoped, so every Honcho bump needs its own secret set. This step copies the secrets from the previous Honcho version, and uses a “changes” file to determine which new secrets to add/update/delete for the new version. - Flip the
availablecolumn toTRUEfor the new version in thehoncho_versionstable. This makes the new version widely available to our tenants. Flipping thehas_migrationcolumn on the new version toFALSEallows tenants to auto-upgrade to this version.
4. Roll out to tenants
All tenants individually upgrade to the new version. How this works:
- Either the tenant explicitly upgrades from
app.honcho.devor theupgrademaintenance job triggers an upgrade for a batch of tenants. - These both write the new version to
honcho_instances.versionfor the tenant’s honcho instance row. - The rest happens asynchronously: the
renderjob ingroudon-healthwrites the tenant’s new yaml intoargo-stagingorargo-production/clusters/and ArgoCD performs a rolling update of theapp-apiandapp-deriverdeployments.
Auto-upgrade (the upgrade maintenance job, runs every 5 min): when
AUTO_UPGRADE_ENABLED is set to true, it upgrades LIVE, non-placeholder,
non-cold-storage tenants along patch and minor versions of the same major,
walking the sorted list and stopping at the first version with
has_migration=True. A major bump is always scheduled by hand. At most
MAX_AUTO_UPGRADES_PER_RUN (set to 20 in production) tenants per run.
See also
- GitOps pipeline — charts, batches, sync waves, and the traps
- Infra — where things run, the 10.x plan, telemetry, model routing
- System Map — how the services fit together
- Releasing a Honcho version onto Groudon — secrets, the git tag that registers the version, rolling the fleet
- Where to look · Tenant pod crashloop