|
| 1 | +# ⚙️ Fly.io Deployment Guide for MCP Gateway |
| 2 | + |
| 3 | +This guide covers the complete deployment workflow for the **MCP Gateway** on Fly.io, including common troubleshooting steps. |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +Fly.io is a global app platform for running containers close to your users, with built-in TLS, persistent volumes, and managed Postgres support. It offers a generous free tier and automatic HTTPS with fly.dev subdomains. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## 1 · Prerequisites |
| 14 | + |
| 15 | +| Requirement | Details | |
| 16 | +| -------------------- | ------------------------------------------------------------------ | |
| 17 | +| Fly.io account | [Sign up](https://fly.io) | |
| 18 | +| Fly CLI | Install via Homebrew: `brew install flyctl` or see Fly docs | |
| 19 | +| Docker **or** Podman | For local image builds (optional) | |
| 20 | +| Containerfile | The included Containerfile with psycopg2-binary support | |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## 2 · Quick Start (Recommended) |
| 25 | + |
| 26 | +### 2.1 Initialize Fly project |
| 27 | +```bash |
| 28 | +fly launch --name your-app-name --no-deploy |
| 29 | +``` |
| 30 | +This creates a new Fly app without deploying immediately. |
| 31 | + |
| 32 | +### 2.2 Create and attach Fly Postgres |
| 33 | +```bash |
| 34 | +# Create postgres (choose Development configuration for testing) |
| 35 | +fly postgres create --name your-app-db --region yyz |
| 36 | + |
| 37 | +# Note the connection details from the output, you'll need the password |
| 38 | +``` |
| 39 | + |
| 40 | +### 2.3 Set secrets |
| 41 | +```bash |
| 42 | +# Set authentication secrets |
| 43 | +fly secrets set JWT_SECRET_KEY=$(openssl rand -hex 32) |
| 44 | +fly secrets set BASIC_AUTH_USER=admin BASIC_AUTH_PASSWORD=your-secure-password |
| 45 | + |
| 46 | +# Set database URL (CRITICAL: use postgresql:// not postgres://) |
| 47 | +fly secrets set DATABASE_URL= "postgresql://postgres:[email protected]:5432/postgres" |
| 48 | +``` |
| 49 | + |
| 50 | +**⚠️ Important:** Always use `postgresql://` scheme, not `postgres://`. The latter causes SQLAlchemy dialect loading errors. |
| 51 | + |
| 52 | +### 2.4 Deploy the app |
| 53 | +```bash |
| 54 | +fly deploy |
| 55 | +``` |
| 56 | + |
| 57 | +--- |
| 58 | + |
| 59 | +## 3 · Containerfile Requirements |
| 60 | + |
| 61 | +Ensure your Containerfile explicitly installs PostgreSQL dependencies: |
| 62 | + |
| 63 | +```dockerfile |
| 64 | +# Create virtual environment, upgrade pip and install dependencies |
| 65 | +RUN python3 -m venv /app/.venv && \ |
| 66 | +/app/.venv/bin/python3 -m pip install --upgrade pip setuptools pdm uv && \ |
| 67 | +/app/.venv/bin/python3 -m pip install psycopg2-binary && \ |
| 68 | +/app/.venv/bin/python3 -m uv pip install ".[redis]" |
| 69 | +``` |
| 70 | + |
| 71 | +The explicit `psycopg2-binary` installation is required because uv may not properly install optional dependencies. |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## 4 · fly.toml Configuration |
| 76 | + |
| 77 | +Your `fly.toml` should look like this: |
| 78 | + |
| 79 | +```toml |
| 80 | +app = "your-app-name" |
| 81 | +primary_region = "yyz" |
| 82 | + |
| 83 | +[build] |
| 84 | +dockerfile = "Containerfile" |
| 85 | + |
| 86 | +[env] |
| 87 | +HOST = "0.0.0.0" |
| 88 | +PORT = "4444" |
| 89 | + |
| 90 | +[http_service] |
| 91 | +internal_port = 4444 |
| 92 | +force_https = true |
| 93 | +auto_stop_machines = "stop" |
| 94 | +auto_start_machines = true |
| 95 | +min_machines_running = 0 |
| 96 | +processes = ["app"] |
| 97 | + |
| 98 | +[[vm]] |
| 99 | +memory = "1gb" |
| 100 | +cpu_kind = "shared" |
| 101 | +cpus = 1 |
| 102 | +``` |
| 103 | + |
| 104 | +**Note:** Don't put secrets like `DATABASE_URL` in `fly.toml` - use `fly secrets set` instead. |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +## 5 · Testing Your Deployment |
| 109 | + |
| 110 | +### 5.1 Check app status |
| 111 | +```bash |
| 112 | +fly status |
| 113 | +fly logs |
| 114 | +``` |
| 115 | + |
| 116 | +### 5.2 Test endpoints |
| 117 | +```bash |
| 118 | +# Health check (no auth required) |
| 119 | +curl https://your-app-name.fly.dev/health |
| 120 | + |
| 121 | +# Protected endpoints (require auth) |
| 122 | +curl -u admin:your-password https://your-app-name.fly.dev/docs |
| 123 | +curl -u admin:your-password https://your-app-name.fly.dev/tools |
| 124 | +``` |
| 125 | + |
| 126 | +### 5.3 Expected responses |
| 127 | +- Health: `{"status":"healthy"}` |
| 128 | +- Protected endpoints without auth: `{"detail":"Not authenticated"}` |
| 129 | +- Protected endpoints with auth: JSON response with data |
| 130 | + |
| 131 | +--- |
| 132 | + |
| 133 | +## 6 · Troubleshooting |
| 134 | + |
| 135 | +### Common Issue 1: SQLAlchemy postgres dialect error |
| 136 | +``` |
| 137 | +sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres |
| 138 | +``` |
| 139 | + |
| 140 | +**Solutions:** |
| 141 | +1. Ensure `psycopg2-binary` is explicitly installed in Containerfile |
| 142 | +2. Use `postgresql://` not `postgres://` in DATABASE_URL |
| 143 | +3. Rebuild with `fly deploy --no-cache` |
| 144 | + |
| 145 | +### Common Issue 2: Database connection refused |
| 146 | +**Solutions:** |
| 147 | +1. Verify DATABASE_URL format: `postgresql://postgres:[email protected]:5432/postgres` |
| 148 | +2. Check postgres app is running: `fly status -a your-app-db` |
| 149 | +3. Verify password matches postgres creation output |
| 150 | + |
| 151 | +### Common Issue 3: Machines not updating |
| 152 | +**Solutions:** |
| 153 | +```bash |
| 154 | +# Force machine updates |
| 155 | +fly machine list |
| 156 | +fly machine update MACHINE_ID --image your-new-image |
| 157 | + |
| 158 | +# Or restart all machines |
| 159 | +fly scale count 0 |
| 160 | +fly scale count 1 |
| 161 | +``` |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 7 · Production Considerations |
| 166 | + |
| 167 | +### Security |
| 168 | +- Change default `BASIC_AUTH_PASSWORD` to a strong password |
| 169 | +- Consider using JWT tokens for API access |
| 170 | +- Enable Fly's private networking for database connections |
| 171 | + |
| 172 | +### Scaling |
| 173 | +```bash |
| 174 | +# Scale to multiple machines for HA |
| 175 | +fly scale count 2 |
| 176 | + |
| 177 | +# Scale machine resources |
| 178 | +fly scale memory 2gb |
| 179 | +``` |
| 180 | + |
| 181 | +### Monitoring |
| 182 | +```bash |
| 183 | +# View real-time logs |
| 184 | +fly logs -f |
| 185 | + |
| 186 | +# Check machine metrics |
| 187 | +fly machine status MACHINE_ID |
| 188 | +``` |
| 189 | + |
| 190 | +--- |
| 191 | + |
| 192 | +## 8 · Clean Deployment Script |
| 193 | + |
| 194 | +For a completely fresh deployment: |
| 195 | + |
| 196 | +```bash |
| 197 | +#!/bin/bash |
| 198 | +set -e |
| 199 | + |
| 200 | +APP_NAME="your-app-name" |
| 201 | +DB_NAME="${APP_NAME}-db" |
| 202 | +REGION="yyz" |
| 203 | +PASSWORD=$(openssl rand -base64 32) |
| 204 | + |
| 205 | +echo "🚀 Deploying MCP Gateway to Fly.io..." |
| 206 | + |
| 207 | +# Create app |
| 208 | +fly launch --name $APP_NAME --no-deploy --region $REGION |
| 209 | + |
| 210 | +# Create postgres |
| 211 | +fly postgres create --name $DB_NAME --region $REGION |
| 212 | + |
| 213 | +# Set secrets |
| 214 | +fly secrets set JWT_SECRET_KEY=$(openssl rand -hex 32) |
| 215 | +fly secrets set BASIC_AUTH_USER=admin |
| 216 | +fly secrets set BASIC_AUTH_PASSWORD=$PASSWORD |
| 217 | + |
| 218 | +# Get postgres password and set DATABASE_URL |
| 219 | +echo "⚠️ Set your DATABASE_URL manually with the postgres password:" |
| 220 | +echo "fly secrets set DATABASE_URL=\"postgresql://postgres:YOUR_PG_PASSWORD@${DB_NAME}.flycast:5432/postgres\"" |
| 221 | + |
| 222 | +# Deploy |
| 223 | +echo "🏗️ Ready to deploy. Run: fly deploy" |
| 224 | +``` |
| 225 | + |
| 226 | +--- |
| 227 | + |
| 228 | +## 9 · Additional Resources |
| 229 | + |
| 230 | +- [Fly.io Documentation](https://fly.io/docs) |
| 231 | +- [Fly Postgres Guide](https://fly.io/docs/postgres/) |
| 232 | +- [Fly Secrets Management](https://fly.io/docs/reference/secrets/) |
| 233 | + |
| 234 | +**Success indicators:** |
| 235 | +- ✅ `fly status` shows machines as "started" |
| 236 | +- ✅ `/health` endpoint returns `{"status":"healthy"}` |
| 237 | +- ✅ Protected endpoints require authentication |
| 238 | +- ✅ No SQLAlchemy errors in logs |
0 commit comments