Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what's failing the docker CI job. The build dies here with:

buildx failed with: ERROR: failed to solve: process "/bin/sh -c chmod +x scripts/*.sh" did not complete successfully: exit code: 1

Root cause: .dockerignore contains scripts/*.sh, so no .sh files are in the build context. COPY scripts/ ./scripts/ brings in the directory without any shell scripts, then chmod +x scripts/*.sh matches nothing and exits 1.

This is also a runtime bug: the final CMD ["./scripts/render_entrypoint.sh"] would fail since that script is excluded by the same rule, even if the chmod line were removed.

Fix: remove (or narrow) the scripts/*.sh entry in .dockerignore so render_entrypoint.sh is included. If you want to keep ignoring the local-only setup scripts, ignore them by name instead (e.g. scripts/setup_*.sh) and let the entrypoint through.


# Create writable directories for SQLite and outputs
RUN mkdir -p /app/outputs /app/data /app/logs
Expand All @@ -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"]
109 changes: 60 additions & 49 deletions docs/DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -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

---

Expand Down Expand Up @@ -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
```

---

Expand Down Expand Up @@ -145,25 +167,14 @@ 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.

---

## 8. Security checklist

- [ ] `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.
41 changes: 41 additions & 0 deletions render.yaml
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions scripts/render_entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions scripts/setup_render_secrets.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading