Skip to content

Deployment

How indx ships: one image that carries the API, every engine, the weights and the site, and the targets it runs on.

indx ships as one image. It carries the API, every engine extra but s3, the weights those engines download, and the documentation site, all served from one origin by examples/app2.py (ADR-0039). The image is the unit of deployment; a target runs it and puts a perimeter in front of it.

Dockerfile builds linux/amd64 only (vibrato has no aarch64 wheel), in stages ordered so a change invalidates only what depends on it: manifests, third-party wheels, the packages; the weights and the site are each built in a stage of their own and copied in last.

The weights are an image of their own (ADR-0041). The weights target is the one stage that reaches Hugging Face: scripts/warm_weights.py constructs each engine there and the three caches it fills are the whole image. Its tag is w-<key>, where scripts/weights_key.sh digests the five files that name a model, and both workflows publish it to GHCR only when that tag is absent. Every later build, in CI and in the deploy, pulls the weights from GHCR instead. The runtime stage then reruns the warm script with the network off, so a build fails when the published weights are not complete for its code.

Command What it does
just infra::image::weights Build indx-everything-weights:local from Hugging Face, once
just infra::image::build Build indx-everything:local on top of it
just infra::image::run Run it on :8000
just infra::image::verify Prove an already built image: /health, every capability the extras add, the site, Scalar
just infra::image::smoke build then verify

.github/workflows/image.yml runs verify on every change to the image’s inputs. A deploy workflow builds the same Dockerfile into its registry, tagged sha-<commit>, and shares one layer cache with it.

  • AWS: one Fargate task behind an ALB that admits only Cloudflare’s edge, three Terraform stacks, one dispatch-only workflow. The production deployment.
  • Azure: one Container App on a Consumption environment serving its hostname directly on a managed certificate, one Terraform stack, one dispatch-only workflow.
  • GCP: one Cloud Run service closed behind a global load balancer whose Cloud Armor policy admits only Cloudflare’s edge, one Terraform stack, one dispatch-only workflow.

AWS is the production deployment. Azure and GCP are one stack each, the same image, and the model lanes as variables. GCP keeps the Cloudflare-only perimeter and closes a foreign Host with a Cloud Armor rule (ADR-0053); Azure has no perimeter, because its hostname is a DNS-only record and Azure issues its certificate, and host-based routing is what closes a foreign Host there (ADR-0064). Each has a dispatch-only workflow of its own, shaped like the AWS one and sharing nothing with it by name: its own variables, secrets and GitHub Environments, so no workflow can take another cloud’s hostname (ADR-0054). An operator can still apply either stack by hand; it is the same stack.