Cluster Service Checklist¶
Acceptance checklist for every project that ships as a Docker service on the CoRE Stack cluster. Complete this page before asking for a cluster deploy.
If this is your first time, start with Tower Services (what the cluster is and how a job becomes a file). Come back to this list when you are packaging your app for deploy.
Part of Infra → Tower Services. That page has the job-flow diagram and shared terminology.
This page is the acceptance list only. Packaging procedures: Cluster Docker Services. Deploy / update / tower fixes: Deploy, update, and troubleshoot. Use the same names: code/, models/, data/, AIRFLOW_API_BASE, GHCR or Docker Hub, central Postgres, outputs.yaml, corestack-network, NO_PROXY.
How to use this page¶
Copy the boxes into the service GitHub issue or README. Tick an item only when the Acceptance line is true.
1. Store all relevant data and compute output in data/¶
Bind-mount three host folders. Do not bake code, models, or outputs into the image. Same table as runbook §2.
| Host folder | Container path | Contents |
|---|---|---|
code/ |
/app |
Git checkout. Update with git pull + restart. |
models/ |
/app/models |
Weights, checkpoints, .pt / .onnx / .joblib. |
data/ |
/app/data |
Inputs, caches, all compute output. |
- Compose (or
docker run) mountscode/,models/, anddata/separately; none of them is copied in theDockerfile. - Job outputs (rasters, vectors, STAC JSON, exports, temp working files) are written under
data/only — not intocode/or the container layer. - Models live under
models/, not underdata/or the image. -
.gitignoreexcludes generated data and large weights; only small fixtures or empty placeholders are committed. - README documents each host path and container path.
Acceptance: Restarting the container keeps models and compute results; git pull updates code without touching data/ or models/.
2. Compute via Airflow if AIRFLOW_API_BASE is set, otherwise local¶
Do not use a COMPUTE_MODE flag. AIRFLOW_API_BASE is the switch (same table as runbook §8).
AIRFLOW_API_BASE in .env |
Behaviour |
|---|---|
| Set (non-empty) | Trigger and poll Airflow (/api/v1/dags/...). |
| Empty / unset | Local compute in this container. |
-
.env.examplelistsAIRFLOW_API_BASEwith a comment: leave empty for local compute. - Backend reads that variable at runtime; empty means local, no Airflow client calls.
- When set: same-origin proxy so the browser never talks to Airflow;
AIRFLOW_DAG_IDand worker callback URL (CORESTACK_API_BASEor equivalent) are also documented. - On the IITD tower, Airflow API base is the working internal URL (today:
http://corestac-stacd-airflow:8080/act4dws5/airflow) andcorestac-stacd-airflowis onNO_PROXY— ops §6. - Local path does not require an Airflow host.
Acceptance: The same image runs local jobs on a laptop (URL unset) and Airflow jobs on the cluster (URL set) by changing .env only.
3. Docker image pushed to GHCR or Docker Hub¶
The image must be in a registry others can pull. Use GitHub Container Registry (ghcr.io/...) or Docker Hub (docker.io/...).
-
Dockerfileis deps-only;code/,models/, anddata/are mounted at runtime (runbook §2). - Image is tagged with a version (and optionally
latest). - Image is pushed to
ghcr.io/<org>/<name>:<tag>or<dockerhub-user>/<name>:<tag>. - README has the exact
docker pullline.
Acceptance: A clean host can docker pull and start the service without building from source.
4. Google SSO is implemented¶
Users sign in with Google Single Sign-On. Protected APIs and compute triggers require a valid Google session/token.
End-to-end how-to (Google Cloud project → consent screen → Web client → env → frontend button → backend verify → laptop vs cluster URLs): runbook §12.
- Google SSO (OAuth / Google Identity) is implemented on the frontend and validated on the backend.
- Client id/secret (and redirect URIs) come from env, not source.
- Unauthenticated users cannot start compute or mutate data.
-
.env.examplelistsGOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET(or the names you use), no real secrets.
GEE service-account keys are separate from SSO — see Cluster Docker Services §1.
Acceptance: Compute and write APIs reject requests without Google SSO; credentials are not in git.
5. Logging enabled; logs mounted from data/logs/<application_name>¶
Every container writes application logs. The log directory is a bind-mount from the host:
Example: service corestack-lulc → data/logs/corestack-lulc/.
API / application logs must support three levels, selected from env (LOG_LEVEL):
LOG_LEVEL |
What is written |
|---|---|
debug |
Verbose: request/response traces, job params, Airflow poll, filesystem paths. Development and incident debug only. |
info |
Default: start-up, auth success/fail (no secrets), compute trigger, job start/complete, DAG id / run id. |
error |
Failures only: exceptions, failed jobs, Airflow failed, SSO/config errors. |
- Logging is enabled in the process that runs in the container (file and/or stdout).
- Compose mounts
./data/logs/<application_name>into the container log path. - Log files land under that directory (not only inside the ephemeral container FS).
-
.env.examplelistsLOG_LEVEL=infowith allowed valuesdebug|info|error. - No tokens, passwords, or Google client secrets in any level.
- README shows the host path and how to tail (
tail -f data/logs/<application_name>/...anddocker compose logs -f).
Acceptance: After a container recreate, logs remain under data/logs/<application_name>/. Changing LOG_LEVEL switches granularity without a code change.
6. Backend and frontend in the same Docker — no separate frontend container¶
One image, one container, one port. Do not run a second Docker service for the UI (no separate Nginx/SPA container).
- A single Compose service (or
docker run) serves backend and frontend. - The backend serves the built UI (same origin), or both processes run in that one container.
- There is no
frontend:service in Compose.
Acceptance: docker compose up starts one app container; the browser talks only to that host:port.
7. Frontend API base URL from .env, not hardcoded¶
The backend API URL the frontend calls must come from environment config. No localhost:8000, production hostname, or path prefix committed in JS/TS.
-
.env.examplehas the API base (API_BASE_URL,VITE_API_BASE_URL,REACT_APP_API_URL, or equivalent). - All frontend
fetch/ axios calls use that value. - Changing
.envretargets the UI without editing source (rebuild the UI only if the framework inlines env at build time — document that). - Default is a relative base (
/or/api) so the same-container deploy works.
Acceptance: Pointing the UI at another backend is an env change, not a code edit.
8. Architecture diagram — how compute is triggered and from where¶
Every project README (or docs/architecture.md) includes a diagram of how compute is triggered and from where. The cluster-wide picture is on Tower Services — reuse those terms.
Must show:
- Browser → frontend Docker
AIRFLOW_API_BASEset → Airflow–STACD Docker (trigger + poll); empty → local compute- On success, output written to
data/ - FileBrowser over
data/for view / download - Mounts:
code/,models/,data/ data/logs/<application_name>/for logs- Central Postgres if the service uses a database (§9)
- Output retention mode(s) (§10)
- External systems if used (GEE, GeoServer, object storage)
Mermaid in the README is enough:
flowchart TD
Browser[Browser] --> FE[Frontend Docker]
FE -->|AIRFLOW_API_BASE set| AF[Airflow-STACD Docker]
FE -->|AIRFLOW_API_BASE empty| Local[Local compute]
AF -->|success| Data[(data/)]
Local -->|success| Data
Data --> FB[FileBrowser UI]
Acceptance: A new operator can see, from the diagram alone, whether a UI action runs locally or via Airflow, where files and logs land, and how outputs are retained.
9. Database — Postgres on the central server¶
If the service needs a database, do not run a per-project Postgres (or SQLite file) in the application container for cluster deploys.
Use PostgreSQL on the central Postgres instance on the cluster host. Persist state through a connection string in env; the server DBA provisions the database/role.
- Cluster deploys use Postgres only (no SQLite as the production store).
-
.env.examplehas a connection string, for example: Split vars (PGHOST,PGPORT,PGUSER,PGPASSWORD,PGDATABASE) are fine if the app prefers them — document one style. - The host is
corestackpostgres(central instance oncorestack-network), not a private Compose Postgres/SQLite sidecar. - Passwords with
@,#,:,/are URL-encoded in the connection string — ops §7. -
corestackpostgresis listed inNO_PROXY/no_proxy. - No database files under the image; schema migrations (
alembic upgrade head, etc.) run against the shared server and are documented in the README. - README states database name, who owns the role, and how to request access.
If the service has no database, tick N/A in the sign-off table and skip this item.
Acceptance: Recreating the app container does not wipe application data; rows live in central Postgres. Credentials are only in .env.
10. Output retention — public, private-persistent, or delete¶
Each project must declare what happens to compute outputs under data/. A data service on the host enforces the policy. Project teams do not invent ad-hoc cleanup; they publish the policy in a standard file the host service reads.
Three modes:
| Mode | Meaning |
|---|---|
| public | Outputs may be published (catalog, GeoServer, open download). Host data service treats them as shareable. |
| private_persistent | Keep on the workstation/cluster disk; not made public. Survives job and container restarts. |
| delete | Remove after a stated duration (ttl_days). Host data service zaps expired paths. |
Standard publish path — commit outputs.yaml at the repo root (or deploy/outputs.yaml). One entry per output tree:
# outputs.yaml — read by the host data service
application: corestack-lulc
outputs:
- path: data/results/
mode: public # public | private_persistent | delete
ttl_days: null
description: Final LULC rasters and STAC items
- path: data/work/
mode: private_persistent
ttl_days: null
description: Intermediate tiles kept on the workstation
- path: data/scratch/
mode: delete
ttl_days: 7
description: Temp files; host data service deletes after 7 days
Rules:
- Every output path under
data/is listed; uncovered paths are not allowed on the cluster. -
modeis one ofpublic,private_persistent,delete. -
deleterequiresttl_days(integer ≥ 1). - README points at
outputs.yamland restates the modes in one paragraph. - Architecture diagram (§8) mentions the host data service.
Acceptance: An operator (or the host data service) can read outputs.yaml and know, for each folder, whether to publish, keep private, or delete after N days.
11. Front page and demo video¶
Every service has a front page like the Drone Tree-Crown Pipeline: a short pitch of what the project is and why it matters, then Google SSO into the tool.
Record one demo video. The same video is both a brief manual about the project and a tutorial. Embed it on the service front page. The demo video should be reviewed and approved.
How-to: runbook §11.
-
/(the Nginx service path) is a landing page in the same style as drone — project story, then sign-in / enter the app. Not a bare form as the first screen. - One demo video exists (brief manual and tutorial). It is on the front page (embed or prominent play link).
- The demo video has been reviewed and approved. Date and notes are in the issue or README.
- README links the front-page URL and the video.
Acceptance: A new visitor opens the service URL, understands the project from the front page and the video, then can follow the same steps in the UI. The demo video has been reviewed and approved.
12. Shared network, proxy, and NO_PROXY¶
On the IITD tower, containers reach the internet only through Squid. Docker hostnames must not go through Squid. Full write-up: Deploy, update, and troubleshoot.
- Compose attaches the service to the external network
corestack-network. -
.env.exampledocumentsHTTP_PROXY/HTTPS_PROXY/http_proxy/https_proxyand bothNO_PROXYandno_proxy. -
NO_PROXYincludes every internal hostname this app calls (at leastcorestackpostgres,corestac-stacd-airflow, and this app’s own name if others call it). New peers → updateNO_PROXYin every related container, then recreate. - README says: after any env change use
docker-compose … up -d --force-recreate(restart alone does not reload.env). - Dockerfile build args use
ARGfor proxy (not baked-inENV) when the image is built on campus.
Acceptance: From inside the container, curl --noproxy '*' http://corestac-stacd-airflow:8080/… (or your peer) works, and a mistaken proxied call no longer returns Server: squid / ERR_DNS_FAIL.
13. Nginx path, health check, and deploy notes¶
Someone must be able to put your app behind the shared Nginx without guessing.
- README states the requested public path(s), for example
/my-app/and/my-app/api/. - App exposes a health/live endpoint (
/health,/livez, or equivalent) that returns 200 without Google Sign-In. - App listens on
0.0.0.0and documents trust forX-Forwarded-*(--proxy-headers/--forwarded-allow-ips, or framework equivalent). - Frontend asset base path works under the Nginx subpath (no bare
/static/...404s) — ops §5. - Deploy and update steps match Deploy a new service and Update a service (or the README links there).
Acceptance: Maintainer can add an Nginx location, open the health URL through the public path, and follow the README to recreate after a new image tag.
Sign-off¶
| # | Item | Owner | Done |
|---|---|---|---|
| 1 | Mount code/, models/, data/; compute output in data/ |
||
| 2 | AIRFLOW_API_BASE set → Airflow; empty → local compute |
||
| 3 | Image pushed to GHCR or Docker Hub | ||
| 4 | Google SSO | ||
| 5 | Logs under data/logs/<application_name>/; LOG_LEVEL debug/info/error |
||
| 6 | Frontend + backend in one Docker | ||
| 7 | Frontend API base URL from .env |
||
| 8 | Architecture diagram (compute trigger + from where) | ||
| 9 | Postgres via connection string to central server (or N/A) | ||
| 10 | outputs.yaml — public / private_persistent / delete |
||
| 11 | Front page (drone-style), demo video on the front page, video reviewed and approved | ||
| 12 | corestack-network + proxy / NO_PROXY |
||
| 13 | Nginx path, health check, deploy/update notes |
Service: _______________ Date: _______________ Reviewer: _______________