From f04493906e1fe8b0ebfcd67bdd2a1d2d7dd06431 Mon Sep 17 00:00:00 2001 From: obielin Date: Wed, 24 Jun 2026 01:21:36 +0100 Subject: [PATCH 1/2] chore(deploy): add Render.com deployment configuration - Add render.yaml blueprint for Render.com free-tier web service - Add scripts/render_entrypoint.sh to write GEE key from env var at startup - Add scripts/setup_render_secrets.sh to upload secrets via Render CLI - Update Dockerfile to copy scripts and use PORT env var for Render - Update docs/DEPLOYMENT.md with Render-first deployment instructions - Keep Fly.io instructions as an alternative for paid accounts --- Dockerfile | 7 +- docs/DEPLOYMENT.md | 109 ++++++++++++++++++-------------- render.yaml | 41 ++++++++++++ scripts/render_entrypoint.sh | 22 +++++++ scripts/setup_render_secrets.sh | 61 ++++++++++++++++++ 5 files changed, 189 insertions(+), 51 deletions(-) create mode 100644 render.yaml create mode 100755 scripts/render_entrypoint.sh create mode 100755 scripts/setup_render_secrets.sh diff --git a/Dockerfile b/Dockerfile index 2d4e1d9..03f4274 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,10 +49,12 @@ RUN pip install --no-cache-dir -e . # Copy built frontend from the first stage COPY --from=frontend-builder /app/frontend/dist ./frontend/dist -# Copy config and model directory structure +# Copy config, models, and utility scripts COPY config.yaml ./ COPY config/ ./config/ COPY models/ ./models/ +COPY scripts/ ./scripts/ +RUN chmod +x scripts/*.sh # Create writable directories for SQLite and outputs RUN mkdir -p /app/outputs /app/data /app/logs @@ -61,4 +63,5 @@ EXPOSE 8000 # Note: GEE credentials and other secrets are supplied at runtime via env vars. # Do not bake credentials into the image. -CMD ["uvicorn", "climatevision.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"] +# The default CMD uses port 8000; Render overrides this via the PORT env var. +CMD ["./scripts/render_entrypoint.sh"] diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 96a20ee..3ae2c38 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -1,15 +1,18 @@ # ClimateVision Deployment Guide -This guide covers deploying ClimateVision to **Fly.io** at `https://climatevision.green` with real Google Earth Engine (GEE) satellite data. +This guide covers deploying ClimateVision at `https://climatevision.green` with real Google Earth Engine (GEE) satellite data. + +The primary path uses **Render.com** because it offers a reliable free tier and does not require a paid account to launch. A **Fly.io** path is also documented for users with active billing. --- ## Prerequisites -- A Fly.io account: https://fly.io -- The Fly CLI (`flyctl`) installed and authenticated - A Google Cloud project with Earth Engine enabled - This repository cloned and dependencies installed +- One of the following platforms: + - **Render.com** account: https://render.com + - **Fly.io** account with active billing: https://fly.io --- @@ -48,58 +51,77 @@ The API will be available at http://localhost:8000 and the dashboard at http://l --- -## 3. Fly.io setup +## 3. Deploy to Render.com (recommended, free tier) -### 3.1 Create the app and provision secrets +### 3.1 Install the Render CLI -```bash -# Create the Fly app -fly apps create climatevision-green +https://render.com/docs/cli -# Set all secrets from .env -./scripts/setup_fly_secrets.sh +### 3.2 Create secrets locally -# Create a persistent volume for SQLite and outputs (1 GB is plenty for demos) -fly volumes create climatevision_data --region lhr --size 1 -``` +Make sure `.env` and `secrets/gee-key.json` are populated as described in section 1. -### 3.2 Custom domain +### 3.3 Push secrets to Render ```bash -# Request a certificate for your domain -fly certs create climatevision.green +./scripts/setup_render_secrets.sh ``` -Add the DNS records shown by `fly certs show` to your DNS provider. Once propagated, Fly will serve the app at `https://climatevision.green`. +This uploads: +- `GEE_SERVICE_ACCOUNT` +- `GEE_PROJECT_ID` +- `GEE_SERVICE_ACCOUNT_KEY_JSON` (the contents of your JSON key) +- `API_SECRET_KEY` +- `CLIMATEVISION_ALLOW_DEV_KEY` +- `CLIMATEVISION_CORS_ORIGINS` +- `DATABASE_URL` -### 3.3 Deploy +### 3.4 Deploy via Blueprint -```bash -# Manual deploy -fly deploy --remote-only +1. Push `render.yaml` to your default branch (`main`). +2. In the Render dashboard, go to **Blueprints**. +3. Connect your GitHub repository. +4. Render will read `render.yaml` and create the `climatevision-green` web service. -# Or push to main — GitHub Actions will deploy automatically -# once FLY_API_TOKEN is configured (see section 4). -``` +### 3.5 Custom domain + +1. In the Render dashboard, open the `climatevision-green` service. +2. Go to **Settings → Custom Domains**. +3. Add `climatevision.green` and `www.climatevision.green`. +4. Render will give you DNS records to add in Namecheap. +5. In Namecheap, add the provided CNAME or A records. + +### 3.6 Limitations of the free tier + +- The service spins down after 15 minutes of inactivity. The first request after spin-down will have a ~30-second cold start. +- SQLite data lives on ephemeral disk. It will survive restarts but not full redeploys. For production persistence, switch to a managed PostgreSQL database. +- Memory is limited; large model inference may be slow. --- -## 4. GitHub Actions CI/CD +## 4. Deploy to Fly.io (requires active billing) -The repository includes two workflows: +Use this path only if your Fly.io account has an up-to-date payment method. -- `.github/workflows/ci.yml` — runs on every PR/push to `main` or `develop`. -- `.github/workflows/deploy.yml` — deploys to Fly.io on every push to `main`. +```bash +# Create the Fly app +fly apps create climatevision-green -### Required repository secrets +# Create a persistent volume for SQLite +fly volumes create climatevision_data --region lhr --size 1 + +# Upload secrets +./scripts/setup_fly_secrets.sh -Add these in **GitHub → Settings → Secrets and variables → Actions**: +# Set up SSL +fly certs create climatevision.green -| Secret | Value | -|--------|-------| -| `FLY_API_TOKEN` | A Fly.io access token from https://fly.io/user/personal_access_tokens | +# Add DNS records in Namecheap using the output of: +fly certs show climatevision.green -GEE credentials are **not** stored in GitHub; they are set directly on Fly via `scripts/setup_fly_secrets.sh`. +# Add FLY_API_TOKEN to GitHub Actions secrets, then deploy: +git push origin main +``` --- @@ -145,7 +167,7 @@ The alert generator emits notifications to pluggable channels. By default only t To enable email or webhook alerts, register a delivery function in `src/climatevision/inference/alert_generator.py` or implement the alert worker (see open issue #10). -For email, configure an SMTP provider or a transactional email service such as Mailgun or SendGrid, then set the relevant secrets via `fly secrets set`. +For email, configure an SMTP provider or a transactional email service such as Mailgun or SendGrid, then set the relevant secrets via your platform’s secret manager. --- @@ -153,17 +175,6 @@ For email, configure an SMTP provider or a transactional email service such as M - [ ] `CLIMATEVISION_ALLOW_DEV_KEY` is `0` in production. - [ ] `API_SECRET_KEY` is a strong, unique secret. -- [ ] The GEE service-account key is stored only in `secrets/` or Fly secrets, never in Git. -- [ ] Hopelyn’s old PAT has been revoked in GitHub. -- [ ] The Fly app uses HTTPS via `fly certs`. - ---- - -## 9. Troubleshooting - -| Symptom | Likely cause | Fix | -|---------|--------------|-----| -| `GEE tile download failed` | Missing or invalid GEE credentials | Check `.env` / Fly secrets and service-account registration | -| Frontend shows blank page | `frontend/dist` not built or mounted | Re-run `npm run build` in `frontend/` or redeploy | -| CORS errors | Origin not allowed | Add domain to `CLIMATEVISION_CORS_ORIGINS` | -| SQLite data lost on deploy | No Fly volume mounted | Create and mount `climatevision_data` volume on `/app/outputs` | +- [ ] The GEE service-account key is stored only in `secrets/` or your platform’s secret store, never in Git. +- [ ] Old or exposed service-account keys have been deleted in Google Cloud Console. +- [ ] The production domain serves HTTPS. diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..be71ed8 --- /dev/null +++ b/render.yaml @@ -0,0 +1,41 @@ +# Render.com blueprint for ClimateVision +# Deploys a single web service that serves both the FastAPI backend +# and the built React frontend. +# +# To deploy: +# 1. Create a Render account at https://render.com +# 2. Install the Render CLI: https://render.com/docs/cli +# 3. Run: ./scripts/setup_render_secrets.sh +# 4. Push this file to GitHub; Render will detect it via Blueprint. + +services: + - type: web + name: climatevision-green + runtime: docker + plan: free + dockerfilePath: ./Dockerfile + startCommand: ./scripts/render_entrypoint.sh + envVars: + - key: PYTHONUNBUFFERED + value: "1" + - key: OMP_NUM_THREADS + value: "1" + - key: PORT + value: "8000" + - key: DATABASE_URL + value: sqlite:///app/outputs/climatevision.sqlite3 + - key: CLIMATEVISION_ALLOW_DEV_KEY + value: "0" + - key: CLIMATEVISION_CORS_ORIGINS + value: "https://climatevision.green,https://www.climatevision.green" + # Set these via ./scripts/setup_render_secrets.sh + - key: GEE_SERVICE_ACCOUNT + sync: false + - key: GEE_SERVICE_ACCOUNT_KEY_JSON + sync: false + - key: GEE_PROJECT_ID + sync: false + - key: API_SECRET_KEY + generateValue: true + - key: ANTHROPIC_API_KEY + sync: false diff --git a/scripts/render_entrypoint.sh b/scripts/render_entrypoint.sh new file mode 100755 index 0000000..91203f7 --- /dev/null +++ b/scripts/render_entrypoint.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Render.com entrypoint. +# Writes the GEE service-account key from the environment into a file, +# then starts the ClimateVision API. + +set -e + +mkdir -p /app/secrets + +# Render stores file contents in env vars because it does not support secret +# files on the free tier. Write the JSON key to disk so the app can read it. +if [ -n "$GEE_SERVICE_ACCOUNT_KEY_JSON" ]; then + echo "$GEE_SERVICE_ACCOUNT_KEY_JSON" > /app/secrets/gee-key.json +fi + +# Create writable directories for SQLite and outputs +mkdir -p /app/outputs /app/data /app/logs + +export PORT="${PORT:-8000}" + +echo "Starting ClimateVision API on port $PORT" +exec uvicorn climatevision.api.main:app --host 0.0.0.0 --port "$PORT" --workers 1 diff --git a/scripts/setup_render_secrets.sh b/scripts/setup_render_secrets.sh new file mode 100755 index 0000000..56af2e1 --- /dev/null +++ b/scripts/setup_render_secrets.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Upload ClimateVision secrets to Render.com. +# Usage: ./scripts/setup_render_secrets.sh + +set -e + +cd "$(dirname "$0")/.." + +SERVICE_NAME="climatevision-green" + +if ! command -v render &> /dev/null; then + echo "Error: Render CLI not found. Install it from https://render.com/docs/cli" + exit 1 +fi + +if [ ! -f .env ]; then + echo "Error: .env file not found. Copy .env.example to .env and fill it in." + exit 1 +fi + +if [ ! -f secrets/gee-key.json ]; then + echo "Error: secrets/gee-key.json not found. Place your GEE service-account key there." + exit 1 +fi + +echo "Uploading secrets to Render service: $SERVICE_NAME" + +# Read values from .env and upload +while IFS= read -r line || [ -n "$line" ]; do + # Skip comments and empty lines + case "$line" in + \#*|""|*\ #*) continue ;; + esac + + case "$line" in + *=*) ;; + *) continue ;; + esac + + key="${line%%=*}" + value="${line#*=}" + value=$(echo "$value" | sed -e 's/^["'"'"']//' -e 's/["'"'"']$//') + + [ -z "$value" ] && continue + + # Skip the file-path key; we will upload the file contents separately + if [ "$key" = "GEE_SERVICE_ACCOUNT_KEY" ]; then + continue + fi + + echo "Setting $key" + render env set "$SERVICE_NAME" "$key=$value" --yes + +done < .env + +# Upload the GEE key JSON contents as GEE_SERVICE_ACCOUNT_KEY_JSON +GEE_KEY_JSON=$(cat secrets/gee-key.json) +echo "Setting GEE_SERVICE_ACCOUNT_KEY_JSON" +render env set "$SERVICE_NAME" "GEE_SERVICE_ACCOUNT_KEY_JSON=$GEE_KEY_JSON" --yes + +echo "Done. Verify with: render env list $SERVICE_NAME" From 752229fbd71de8ed2860c68089c2eb56adc82c1c Mon Sep 17 00:00:00 2001 From: Gold okpa Date: Wed, 24 Jun 2026 01:53:47 +0100 Subject: [PATCH 2/2] fix(docker): stop ignoring render_entrypoint.sh in .dockerignore Narrow `scripts/*.sh` to `scripts/setup_*.sh` so the local-only setup scripts stay excluded from the build context while scripts/render_entrypoint.sh is included. Fixes the docker CI job failing on `chmod +x scripts/*.sh` (exit 1) and the runtime CMD that runs the entrypoint. --- .dockerignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index dd7fb7f..67349ba 100644 --- a/.dockerignore +++ b/.dockerignore @@ -42,7 +42,7 @@ team_docs *.pdf tests notebooks -scripts/*.sh +scripts/setup_*.sh scripts/setup_gee.py # Large pretrained weights if they exist locally