Skip to content

Cloud Hosting (GCP)

Migas runs in production at migas.nipreps.org on Google Cloud: the container on Cloud Run, Postgres on Cloud SQL, a managed Redis, and images built by Cloud Build. This is that setup.

The image and environment variables are the same on any container platform (AWS ECS/Fargate, Azure Container Apps, Kubernetes); GCP is just the worked example.

Note

For a local instance see getting started; for your own host see self-hosting. The configuration reference covers the environment variables used below, and administration covers tokens and the admin API.

Contents

  1. Architecture on GCP
  2. Prerequisites
  3. Provision Cloud SQL (PostgreSQL)
  4. Provision Redis
  5. Initialize the schema
  6. Deploy
  7. Bootstrap the admin token
  8. Post-release cost management

1. Architecture on GCP

flowchart LR
    client["Client<br/>(migas-py)"]
    run["Cloud Run<br/>migas-server"]
    sql[("Cloud SQL<br/>PostgreSQL")]
    redis[("Managed Redis<br/>Redis Cloud / Memorystore")]

    client --> run
    run -->|"Cloud SQL socket<br/>/cloudsql/&lt;conn&gt;"| sql
    run -->|"rediss://"| redis
  • Cloud Run runs the container built from the repo Dockerfile (BUILDTYPE=release), scaling instances with traffic and reachable publicly (--allow-unauthenticated). Cloud Run terminates TLS, so the server runs with --proxy-headers --forwarded-allow-ips='*'.
  • Cloud SQL holds the database. Setting GCP_SQL_CONNECTION makes the server connect over the /cloudsql/<project:region:instance> Unix socket (see connections.py).
  • Redis is any managed instance Cloud Run can reach. Production uses a free Redis Cloud instance; Memorystore also works.
  • Geolocation databases are baked into the image at build time (the deploy pipeline runs scripts/download_geodbs.py before the build), so there is no runtime download and MIGAS_GEOLOC=1 is set.

2. Prerequisites

  • A GCP project with billing enabled and the gcloud SDK authenticated (gcloud auth login, gcloud config set project <id>).
  • Enabled APIs: Cloud Run, Cloud Build, Cloud SQL Admin, Artifact/Container Registry.
  • A managed Redis instance and its connection URI.
  • uv and hatch locally for manual deploys (the version tag comes from hatch version).

The commands below use a $REGION shell variable. Set it first:

export REGION=us-central1   # or your preferred region

3. Provision Cloud SQL (PostgreSQL)

Create the instance and the migas database, or let deploy/gcp/release-gcp.sh create them if they are missing. Use the newest PostgreSQL major your provider offers; POSTGRES_18 is current on Cloud SQL at the time of writing.

gcloud sql instances create migas-postgres \
  --database-version=POSTGRES_18 \
  --region=$REGION \
  --tier=db-g1-micro \
  --root-password='<choose-a-strong-password>'

gcloud sql databases create migas --instance=migas-postgres

The connection name is project_id:region:instance_name (e.g. <project-id>:$REGION:migas-postgres); that is your GCP_SQL_CONNECTION.


4. Provision Redis

Create a managed Redis instance (Redis Cloud free tier or Memorystore) and note its URI. Pass it as MIGAS_REDIS_URI, or REDIS_TLS_URL for a rediss:// endpoint.


5. Initialize the schema

Cloud Run does not run migrations, so apply them yourself: once at setup, and again after any release that adds one. The easiest route is the Cloud SQL Auth Proxy:

# In one terminal, open a local tunnel to the instance:
cloud-sql-proxy <project-id>:$REGION:migas-postgres

# In another, point Alembic at the tunnel and migrate:
DATABASE_URL="postgresql+asyncpg://postgres:<password>@127.0.0.1:5432/migas" \
  uv run alembic upgrade head

That creates the migas schema and tables. The self-hosting guide's schema section has the details.


6. Deploy

Automated

Production deploys run through the prod-deploy.yml workflow on a tag push:

git tag -a 1.2.3 -m "Release 1.2.3"
git push origin 1.2.3

It authenticates via Workload Identity, builds the image with Cloud Build (skipping the build if the tag's image already exists), rolls out a new Cloud Run revision, and creates a GitHub Release for annotated tags.

Required repository secrets:

Secret Purpose
PROJECT_ID GCP project id
WORKLOAD_IDENTITY_PROVIDER, SERVICE_ACCOUNT Keyless GCP auth
GCP_SQL_CONNECTION project:region:instance
DATABASE_USER, DATABASE_PASSWORD, DATABASE_NAME Cloud SQL credentials
REDIS_URI Maps to MIGAS_REDIS_URI
MAX_REQUEST_SIZE Maps to MIGAS_MAX_REQUEST_SIZE

Manual

To deploy from your workstation, run the script (or make release-gcp):

# Provide PROJECT_ID, SQL_INSTANCE_PASSWORD, and REDIS_URI in the environment,
# or a deploy/gcp/.env file (see deploy/gcp/example.env).
./deploy/gcp/release-gcp.sh

It creates the Cloud SQL instance if needed, builds via Cloud Build (cloudbuild.yml), and deploys. The deploy command itself is:

gcloud run deploy migas-server \
  --region=$REGION \
  --image=gcr.io/$PROJECT_ID/migas-server:<version> \
  --platform=managed \
  --min=1 --max-instances=3 \
  --ingress=all --allow-unauthenticated \
  --set-cloudsql-instances=$GCP_SQL_CONNECTION \
  --memory=512Mi --cpu=1 --cpu-throttling \
  --args=--host,0.0.0.0,--port,8080,--proxy-headers,--forwarded-allow-ips='*' \
  --set-env-vars="MIGAS_GEOLOC=1|MIGAS_REDIS_URI=...|DATABASE_USER=...|DATABASE_PASSWORD=...|DATABASE_NAME=migas|GCP_SQL_CONNECTION=$GCP_SQL_CONNECTION"

Note

--set-cloudsql-instances attaches the socket, and GCP_SQL_CONNECTION tells the server to use it. --forwarded-allow-ips='*' is fine here because only Cloud Run's front end can reach the container.


7. Bootstrap the admin token

A fresh database has no master token, and the API can't create one. Run the bootstrap script over the Auth Proxy, the same way as the migration above:

cloud-sql-proxy <project-id>:$REGION:migas-postgres -p <PORT>

# in a separate terminal
DATABASE_URL="postgresql+asyncpg://postgres:<password>@127.0.0.1:<PORT>/migas" \
  uv run python scripts/bootstrap_admin_token.py

Save the printed token; it's your admin credential for the deployed instance. With it you can register projects and issue scoped tokens — see Administration.


8. Post-release cost management

The deploy above uses service-level minimum instances (--min=1), which Cloud Run divides among revisions in proportion to the traffic they serve. When a new release takes 100% of traffic, the previous revision drops to 0 instances on its own — it stays available as a rollback candidate at no idle cost, and you never pay for two warm containers once traffic has cut over. No per-release cleanup is needed.

Important

This is different from revision-level --min-instances, which bakes an immutable warm-instance count into each revision and keeps billing even at 0% traffic. There is no gcloud run revisions update to change it — revision-level minimums can only be dropped by deleting the revision. See Cloud Run — minimum instances.

If you have older revisions deployed before switching to --min (i.e. with --min-instances=1), delete them once to stop their idle billing — keep the current serving revision plus one prior for rollback:

gcloud run revisions list --service=migas-server --region=$REGION
gcloud run revisions delete <old-revision> --region=$REGION