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¶
- Architecture on GCP
- Prerequisites
- Provision Cloud SQL (PostgreSQL)
- Provision Redis
- Initialize the schema
- Deploy
- Bootstrap the admin token
- 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/<conn>"| 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_CONNECTIONmakes the server connect over the/cloudsql/<project:region:instance>Unix socket (seeconnections.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.pybefore the build), so there is no runtime download andMIGAS_GEOLOC=1is set.
2. Prerequisites¶
- A GCP project with billing enabled and the
gcloudSDK 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.
uvandhatchlocally for manual deploys (the version tag comes fromhatch 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