Image-to-Text
PyTorch
Safetensors
PEFT
English
remote-sensing
satellite-imagery
earth-observation
change-detection
visual-grounding
image-captioning
visual-question-answering
optical-sar-fusion
sar
multimodal
lora
Instructions to use thundercode/SatQuery with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use thundercode/SatQuery with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
|
Download docs/DEPLOYMENT.md from thundercode/SatQuery: direct link, hf CLI and curl.
- Browser
- Download file 16 kB
-
https://huggingface.co/thundercode/SatQuery/resolve/5ce82bef20f9510057a0a4f7bcbb0b5ee643d9cb/docs/DEPLOYMENT.md
- Command line
-
hf download hf://thundercode/SatQuery@5ce82bef20f9510057a0a4f7bcbb0b5ee643d9cb/docs/DEPLOYMENT.md
-
curl -L -o DEPLOYMENT.md https://huggingface.co/thundercode/SatQuery/resolve/5ce82bef20f9510057a0a4f7bcbb0b5ee643d9cb/docs/DEPLOYMENT.md
16 kB
| # Deployment | |
| **Status tags:** `IMPLEMENTED` Β· `VERIFIED` Β· `MEASURED` Β· `OPEN` Β· `DEFERRED` Β· `BLOCKED` Β· `BY DESIGN`. | |
| The live SatQuery AI system runs across **three private repositories** plus one **public umbrella | |
| repository**. The monorepo working copy is **not** the deployed source. This document records the live | |
| topology, every environment variable, the deploy mechanics, the cold-start behaviour, the known | |
| issues, and the platform traps that cost real debugging time. | |
| > **The single most important trap in this document.** `deploy/` inside the monorepo is **stale and | |
| > untracked**. It is **not** the deployed source. Edits must go to the three real repositories. | |
| --- | |
| ## 1. Live revisions (VERIFIED) | |
| Read from the GitHub API during the release reconnaissance. | |
| | Component | Repository | Visibility | Branch | Revision | Host | | |
| |---|---|---|---|---|---| | |
| | Frontend | `Anish-lab-blip/SatQuery-Frontend` | **private** | `main` | **`2d7ae53b482d`** | Cloudflare Pages β `satquery.pages.dev` | | |
| | Backend / orchestrator | `Anish-lab-blip/SatQuery-Backend` | **private** | `main` | **`89d80eaddec5`** | Render β `satquery-backend-m4yv.onrender.com` | | |
| | Inference | `Anish-lab-blip/SatQuery-Inference` | **private** | `main` | **`5a0936ace491`** | Codespace `potential-space-trout-r4ppw969w45j2pvvw`, port 8000 | | |
| | Public umbrella | `Anish-lab-blip/SatQuery-AI` | **public** | `main` | `3dcabd32da41` ("Initial commit") | this release home | | |
| | Monorepo (working copy) | `C:/Users/anish/satquery-ai` | local only | `master` | `9d57aed` | **no git remote**; 334 dirty entries | | |
| The public umbrella repository previously contained **only a 13-byte README** (`# SatQuery-AI`). It is | |
| the intended home for this release. | |
| ## 2. The four tiers | |
| ``` | |
| Browser | |
| β HTTPS | |
| βΌ | |
| Cloudflare Pages β satquery.pages.dev (static frontend, 11 pages) | |
| β HTTPS / JSON β /api/* | |
| βΌ | |
| Render β satquery-backend-m4yv.onrender.com (orchestrator / gateway) | |
| β outbound long-poll POST /tunnel/agent | |
| βΌ | |
| GitHub Codespace β FastAPI inference, CPU, port 8000 | |
| β build_space_app() | |
| βΌ | |
| specialists: SmolVLM Β· RemoteCLIP Β· MiniLM Β· CROMA Β· STANet | |
| β | |
| βΌ | |
| ResultEnvelope β tunnel β Render β browser | |
| ``` | |
| ```mermaid | |
| flowchart LR | |
| U[Browser] -->|HTTPS| CF["Cloudflare Pages<br/>static frontend"] | |
| CF -->|"HTTPS JSON<br/>/api/health Β· /api/capabilities Β· /api/infer Β· /api/assets"| R["Render<br/>orchestrator / gateway"] | |
| R -->|"outbound long-poll<br/>POST /tunnel/agent"| C["GitHub Codespace<br/>FastAPI inference :8000"] | |
| C --> S[(SmolVLM Β· RemoteCLIP<br/>MiniLM Β· CROMA Β· STANet)] | |
| C -->|ResultEnvelope| R | |
| R -->|"envelope + error translation"| CF | |
| ``` | |
| ## 3. Why each tier exists | |
| ### 3.1 Cloudflare Pages β the static tier | |
| Serves the frontend. **No backend, no secrets, and no API calls of its own** on the static pages. The | |
| one exception is the Analyze console, which calls the gateway. Staged by `scripts/stage_pages.mjs` and | |
| deployed with `npx wrangler pages deploy`. | |
| ### 3.2 Render β the gateway | |
| A deliberately **thin, stateless** orchestrator. Its responsibilities, from | |
| `docs/DEPLOYMENT_ARCHITECTURE.md` Β§2: | |
| | Responsibility | Detail | | |
| |---|---| | |
| | Schema validation | reject malformed requests before they cost inference | | |
| | Size limits | whole-request body cap, shared with the Codespace | | |
| | Rate limiting | per-IP count + window β **fairness, NOT a security control** | | |
| | CORS allowlist | the Pages origin; **never `*`** | | |
| | Request ids | correlate a request across tiers | | |
| | Timeouts | sit inside the task budget | | |
| | Secret custody | holds credentials that must never reach the browser | | |
| | Error translation | upstream failures β the documented error envelope | | |
| It is **not** a model host. It has **no database, no auth, and no queue**. | |
| > **Hard rule.** The gateway must **not** retry `POST /api/infer` on its own β a retry would consume | |
| > inference a second time. The client decides on retry. | |
| ### 3.3 GitHub Codespace β the inference tier | |
| Runs the real inference service: `build_space_app()` from `app/space_app.py`, served by | |
| `deploy/codespace/serve.py` on `$PORT`, in **CPU mode**. It honours the four-endpoint contract | |
| (`/v1/health`, `/v1/capabilities`, `/v1/analyze`, `/v1/assets`), imports cheaply without torch, reuses | |
| `app/serving.py` as the composition root, and **degrades rather than crashes** on absent artifacts. | |
| ### 3.4 Hugging Face β the model tier | |
| Holds the six trained artifacts and the model card. It is **not** the runtime inference host; the | |
| Codespace resolves the pinned backbones from the Hub at run time. | |
| ## 4. Why the transport is an outbound tunnel | |
| The inference host is a Codespace in a **private** repository. A forwarded port for a private repo | |
| returns **`302`**, so an inbound-forwarding design cannot work. Instead: | |
| - the Codespace runs `deploy/codespace/tunnel_agent.py`; | |
| - the agent **dials out** to `POST /tunnel/agent` and long-polls; | |
| - work is executed against `http://127.0.0.1:8000` **locally**. | |
| This inverts the usual direction: the inference host needs **no inbound firewall hole**. It also means | |
| the transport is only alive while the agent is polling. | |
| **Measured:** `GET /api/health` reported `tunnel.agent_connected: true` with a non-zero `completed` | |
| counter, and `POST /api/infer {}` returned `422 invalid_request` with the response header | |
| `x-satquery-transport: tunnel`. | |
| ## 5. The full live health payload (VERIFIED, probed) | |
| ```json | |
| { | |
| "status": "ok", | |
| "service": "satquery-orchestrator", | |
| "tunnel": { | |
| "agent_connected": true, | |
| "agent_id": "codespaces-fd1038", | |
| "pending": 0, | |
| "completed": 97 | |
| }, | |
| "config": { | |
| "codespace_name": "potential-space-trout-r4ppw969w45j2pvvw\n", | |
| "codespace_port": 8000, | |
| "transport_mode": "auto", | |
| "tunnel_timeout_s": 150.0, | |
| "wake_timeout_s": 120.0, | |
| "upstream_timeout_s": 90.0, | |
| "device": "cpu", | |
| "has_github_token": true | |
| } | |
| } | |
| ``` | |
| Note `codespace_name` still carries a **trailing `\n`** β this is **B-02**, cosmetic and `OPEN`; the | |
| wake path strips it (`_codespace_name()`), so only the health payload reports the raw value. | |
| ## 6. Environment variables | |
| ### 6.1 Render (gateway) β measured live values | |
| | Variable | Value (live) | Purpose | | |
| |---|---|---| | |
| | `CODESPACE_NAME` | `potential-space-trout-r4ppw969w45j2pvvw` | which Codespace to wake | | |
| | `CODESPACE_PORT` | `8000` | the inference port | | |
| | `SATQUERY_ALLOWED_ORIGINS` | `https://satquery.pages.dev` | CORS allowlist (never `*`) | | |
| | `SATQUERY_DEVICE` | `cpu` | device preference | | |
| | `SATQUERY_TRANSPORT` | `auto` | tunnel first, then forward | | |
| | `SATQUERY_TUNNEL_TIMEOUT_S` | `150` | how long to wait on the tunnel | | |
| | `SATQUERY_WAKE_TIMEOUT_S` | `120` | how long to wait for a cold start | | |
| | `SATQUERY_UPSTREAM_TIMEOUT_S` | `90` | gateway β upstream budget | | |
| | `GITHUB_TOKEN` | present | Codespace control | | |
| | `PORT` | platform-supplied | Render's own listen port | | |
| > **Measured absence.** There is **no** `SATQUERY_UPSTREAM_URL` and **no** `HF_TOKEN` in the live | |
| > config. The transport is the outbound tunnel, not a forwarded port. This contradicts the older | |
| > `docs/DEPLOYMENT_TOPOLOGY.md` Β§3.2 table, which predates the tunnel design. | |
| ### 6.2 Codespace (inference) | |
| | Variable | Purpose | | |
| |---|---| | |
| | `PORT` | platform-assigned; **must be read** (a historical blocker was that no code read it) | | |
| | `SATQUERY_DEVICE` | `cpu` \| `cuda` \| `mps` \| `null`; read **without importing torch** | | |
| | `SATQUERY_MAX_FILE_BYTES` | per-file cap, shared with Render | | |
| | `SATQUERY_ASSET_ENABLED` / `SATQUERY_ASSET_DIR` | both required for `/v1/assets`; **fails closed (503)** otherwise | | |
| | `SATQUERY_ASSET_MAX_FILES` / `SATQUERY_ASSET_TTL_S` | optional handle capacity / lifetime | | |
| ### 6.3 Config-loader environment overrides | |
| Two registry values can be overridden from the environment without editing the YAML | |
| (`core/config.py`): | |
| | Variable | Effect | | |
| |---|---| | |
| | `SATQUERY_PRECISION` | overrides `training.precision` | | |
| | `SATQUERY_TORCH_COMPILE` | overrides `deployment.torch_compile` (`"true"` β `True`) | | |
| Both are still validated by the loader β setting `SATQUERY_TORCH_COMPILE=true` **fails startup** | |
| because finding C-8 forbids `torch.compile`. | |
| ## 7. Deploy mechanics | |
| | Tier | Mechanism | | |
| |---|---| | |
| | Frontend β Cloudflare Pages | `scripts/stage_pages.mjs` builds a Pages bundle; `npx wrangler pages deploy` | | |
| | Backend β Render | `render.yaml` blueprint; `main.py` exposes the ASGI object `app` | | |
| | Inference β Codespace | `deploy/codespace/serve.py` binds `build_space_app()` to `$PORT`; `.devcontainer/` forwards 8000 and starts the tunnel agent via `postStartCommand` | | |
| | Repository writes | the **GitHub Git Data API** β blob β tree β commit β `PATCH` ref | | |
| ### 7.1 Why the Git Data API instead of `git push` | |
| Every deployed file is uploaded as a **blob** whose sha256 is **computed locally and verified against | |
| the uploaded blob**, then assembled into a tree, committed, and the branch ref patched. This means: | |
| - each file is **content-verified** rather than trusted; | |
| - deletions are expressed explicitly as `sha: null` tree entries; | |
| - the deploy is **idempotent** β re-running it with identical content produces no change. | |
| **Measured:** 9 deployed files were re-read from the API and found **sha256 byte-identical** to the | |
| local copies, with the deployed HEAD re-read independently (`verify_deployed_head.py`). | |
| ## 8. Cold start (documented, not hidden) | |
| Render's free tier sleeps when idle, and the Codespace may be stopped. Before a request can be served, | |
| Render must start the Codespace (if stopped) and wait for the tunnel agent to reconnect. The frontend | |
| shows *"Waking inference engineβ¦"* during this. | |
| | Property | Value | | |
| |---|---| | |
| | Cold start | **tens of seconds** | | |
| | Tunnel wait before falling through | `SATQUERY_TUNNEL_TIMEOUT_S` = 150 s | | |
| | Wake wait | `SATQUERY_WAKE_TIMEOUT_S` = 120 s | | |
| | Upstream budget | `SATQUERY_UPSTREAM_TIMEOUT_S` = 90 s | | |
| Cold start is **documented rather than papered over**: an honest "this will take a while the first | |
| time" is better than a silent hang. | |
| ### 8.1 The `transport_mode: auto` fallthrough β B-07 (`OPEN`) | |
| `SATQUERY_TRANSPORT=auto` means: **try the tunnel; on timeout, fall through to the forward path.** | |
| The forward path to a **private** repo returns `302` quickly β but the wake step still consumes | |
| `SATQUERY_WAKE_TIMEOUT_S` (120 s) **first**. So a worst-case failed request takes roughly | |
| ``` | |
| 150 s (tunnel timeout) + 120 s (wake timeout on a 302) β 249 s | |
| ``` | |
| This is the **root shape** of the observed transient tunnel gap, and it is why a request can appear to | |
| hang and then fail. A patch (`fix-b07-forward-unavailable.patch`) was authored and verified | |
| (`py_compile` clean, applies cleanly to the deployed `main.py`), adding distinct | |
| `forward_unavailable` (503, terminal 302/401/403 on the forward path) and `upstream_timeout` | |
| (504, tunnel healthy but slow) codes, plus the `codespace_name` `.strip()` fix. | |
| > **Status: B-07 is `OPEN`.** The patch is **prepared but NOT deployed.** The deployed health payload | |
| > still shows the trailing `\n`. | |
| ## 9. The five historical backend blockers | |
| Before any backend could boot, five verified blockers had to be closed. The current design closes them: | |
| | # | Blocker (verified) | How it is closed | | |
| |---|---|---| | |
| | 1 | `requirements.txt` declared no `fastapi` / `uvicorn` / `httpx` / `starlette` | the runtime installs the ASGI stack so `build_space_app()` and the gateway can import | | |
| | 2 | no code read `$PORT` β a platform port would be ignored | `deploy/codespace/serve.py` binds to `$PORT`; Render reads its own | | |
| | 3 | hand-rolled CORS raised `405` on `OPTIONS`, so browser preflight failed | the gateway registers `OPTIONS` explicitly / uses Starlette's CORS middleware | | |
| | 4 | module-level `app = create_app()` swallowed config errors into `app = None` | construction errors now **propagate** (fail-fast) instead of leaving a dead `app` | | |
| | 5 | adapter integrity unverified on load (`_adapter_sha256` computed but never compared) | the load path compares the digest against an expected value, or fails startup | | |
| ## 10. Platform traps (recorded so they are not rediscovered) | |
| | Trap | Detail | | |
| |---|---| | |
| | **Cloudflare `_headers` CONCATENATE** | Two matching rules are **merged, not overridden**. A specific rule nested under a broad `/assets/img/*` rule yields `max-age=604800, β¦, max-age=0, must-revalidate` β and Chromium takes the **FIRST** `max-age`. The file's own "later rules override" comment is **false**. | | |
| | **Cloudflare 308 redirect** | `X.html` β `/X`. Reference the extensionless path. | | |
| | **Forwarded port returns 302** | for a private repo β this is *why* the tunnel exists. | | |
| | **Tunnel agent must start on boot** | via the devcontainer `postStartCommand`, or a restarted Codespace comes up with `agent_connected: false`. | | |
| | **Never retry `/api/infer` at the gateway** | a retry consumes inference twice. | | |
| | **`deploy/` is stale and untracked** | not the deployed source. | | |
| ## 11. The superseded design, and what did NOT change | |
| The earlier design ran inference on an **HF Space with ZeroGPU** (5 GPU-min/day, `@spaces.GPU` | |
| decoration) behind a **Railway** gateway. The active design changes three things: | |
| 1. **CPU-first instead of ZeroGPU.** No code change was required β `device_preference` honours | |
| `SATQUERY_DEVICE` and defaults to CPU, every specialist defaults to `device="cpu"`, and all | |
| placement is `.to(device)` (never `.cuda()`). | |
| 2. **A real, always-buildable inference environment.** A Codespace gives a reproducible container | |
| without a GPU quota or a Space's cold-start constraint. The wake flow replaces ZeroGPU lazy | |
| loading as the cold-start story. | |
| 3. **No GPU quota to protect at the gateway.** Rate/size limits remain, but as **fairness** controls | |
| rather than quota protection. | |
| **What did NOT change:** | |
| | Unchanged | Detail | | |
| |---|---| | |
| | the four-endpoint contract | `/v1/health`, `/v1/capabilities`, `/v1/analyze`, `/v1/assets` | | |
| | the gateway responsibility table | Β§3.2 above | | |
| | the env-var vocabulary | only host names moved (`SATQUERY_SPACE_URL` β `SATQUERY_UPSTREAM_URL`) | | |
| | the config freeze | `78f1e3700da15aa1` | | |
| `configs/deploy.yaml` still describes the old HF-Space/ZeroGPU target (`platform: huggingface-spaces`, | |
| `sdk: gradio`, `zerogpu: true`, the `gpu_duration_*` values). It is **frozen paperwork**: no Gradio | |
| runtime exists in code, and editing it would move `Config.hash`. It is left undisturbed. | |
| ## 12. Deployment preconditions (open items) | |
| 1. Cloudflare Pages project name / domain (needed for the deploy command and `robots.txt` sitemap). | |
| 2. Artifacts present, **or** capabilities honestly ship `available: false` β the service degrades | |
| rather than crashes on absent artifacts. | |
| 3. `HF_TOKEN` on Render **if** the HF proxy path is used (not used in the live config). | |
| 4. Codespace `.devcontainer/` forwarding `:8000` **and** starting the tunnel agent. | |
| 5. The five blockers in Β§9 closed and verified on the first live run. | |
| ## 13. What is `NOT RUN` / `OPEN` / `BLOCKED` for this topic | |
| | Item | State | | |
| |---|---| | |
| | B-07 tunnel fallthrough | **OPEN** β patch prepared, NOT deployed | | |
| | B-02 `codespace_name` trailing `\n` | **OPEN** (cosmetic) | | |
| | A deployed system-level load test | **NOT RUN** | | |
| | Multi-region / HA deployment | **NOT RUN** | | |
| | A production database / persistence | **BY DESIGN** (absent) | | |
| | The ZeroGPU/Gradio deployment target | **REJECTED** (superseded; frozen paperwork only) | | |
| ## 14. Where the evidence lives | |
| | Evidence | Location | | |
| |---|---| | |
| | Live health + capabilities probe | `release/CURRENT_RELEASE_STATE.md` Β§1 | | |
| | Topology contract | `docs/DEPLOYMENT_TOPOLOGY.md`, `docs/DEPLOYMENT_ARCHITECTURE.md` | | |
| | The undeployed B-07 patch | session scratch: `fix-b07-forward-unavailable.patch` | | |
| | Deployed-artifact integrity check | `verify_deployed_head.py` (session scratch) | | |
| | Live validation (3 passes) | `.workbuddy-ai/scratch/live_validation/` | | |