Módulo 7: Alternative Platforms (Render, Railway, Fly.io)
4. Fly.io: Deployment para AI Apps
Descripción
En esta cápsula vas a desplegar tu app AI en Fly.io. Si Render es simplicidad y Railway es developer experience, Fly.io es distribución geográfica. Fly.io corre tus containers como micro-VMs en edge locations alrededor del mundo: tu app puede estar simultáneamente en São Paulo, Frankfurt, Tokyo y Virginia, respondiendo desde la ubicación más cercana al usuario. Para AI, esto significa baja latencia global — algo que ni Render ni Railway ofrecen de forma nativa.
Contexto: Vienes de desplegar en Render (cápsula 02) y Railway (cápsula 03). Fly.io es la tercera y última plataforma del módulo. Su diferenciador principal — edge deployment — es irrelevante si todos tus usuarios están en una región, pero transformador si necesitas latencia baja global. Al terminar esta cápsula tendrás la experiencia de las tres plataformas y datos reales para la comparativa (cápsula 05).
Fly.io: Overview de la Plataforma
Qué es Fly.io
Fly.io convierte tus Docker containers en micro-VMs (Firecracker) y las ejecuta en su red global de 30+ regiones. La propuesta es:
- Edge deployment: Tu app corre donde están tus usuarios, no en una región centralizada
- Multi-region nativo: Una sola configuración despliega en múltiples regiones
- Micro-VMs (Firecracker): Más ligero que containers, arranque en ~300ms
- Volumes persistentes: Almacenamiento SSD adjunto a las VMs
- Anycast networking: Una IP global que rutea al punto más cercano
- Machines API: Control programático de cada VM individual
Modelo de deployment
Tu código + Dockerfile
↓ flyctl deploy
Fly.io construye imagen
↓
Distribuye a regiones seleccionadas
├── iad (Virginia)
├── gru (São Paulo)
├── fra (Frankfurt)
└── nrt (Tokyo)
↓
Anycast IP: tu-app.fly.dev
↓ Request desde México
→ Rutea a iad (más cercano)
↓ Request desde Japón
→ Rutea a nrt (más cercano)
Arquitectura interna
Internet
↓ Anycast
Fly.io Edge (30+ regiones)
├── Proxy (TLS termination, routing)
└── Firecracker micro-VM
└── Tu container
└── Tu app (FastAPI + AI)
Cada micro-VM tiene:
- CPU dedicado (no compartido como en Render Free)
- RAM dedicada
- Storage efímero (o volume persistente)
- IPv6 privado para comunicación inter-VM
Pricing (datos actualizados 2026)
| Recurso | Precio | Free allowance |
|---|---|---|
| Shared CPU (1x) | $1.94/mes | 3 VMs gratis |
| Shared CPU (2x) | $3.88/mes | — |
| Dedicated CPU (1x) | $31/mes | — |
| Dedicated CPU (2x) | $62/mes | — |
| RAM (256 MB) | Incluido en shared | 256 MB per VM gratis |
| RAM extra (1 GB) | ~$6/mes | — |
| Volume (1 GB SSD) | $0.15/mes | 3 GB gratis |
| Outbound data | $0.02/GB | 100 GB gratis |
| Dedicated IPv4 | $2/mes | Compartido gratis |
Para AI workloads — estimación (1 VM, shared-cpu-1x, 1 GB RAM extra):
- VM: $1.94/mes (o gratis si usas las 3 VMs del free allowance)
- RAM extra: ~$6/mes
- Volume 1 GB: $0.15/mes
- Total: ~$8/mes para una región
Multi-region (3 regiones, shared-cpu-1x, 1 GB RAM cada una):
- 3 VMs: $5.82/mes
- RAM extra: ~$18/mes
- Total: ~$24/mes para presencia global
Limitaciones para AI
| Limitación | Impacto en AI | Workaround |
|---|---|---|
| Shared CPU: performance variable | Inferencia puede ser lenta en shared | Usar dedicated CPU ($31/mes) para producción |
| Max 2 GB RAM (shared) | Puede no caber ChromaDB + modelo + app | Usar 2x shared ($3.88) o dedicated |
| No GPU | No corre modelos locales con CUDA | APIs externas (OpenAI, Together AI) |
| Volumes: region-locked | Un volume solo existe en una región | Usar S3/R2 para datos compartidos entre regiones |
| Auto-stop por defecto | VMs paran después de inactividad | auto_stop_machines = false en fly.toml |
| Cold start: ~300ms (micro-VM) | Más rápido que Render Free (30s), más lento que Railway | Mantener min_machines_running = 1 |
| WebSocket: soportado | Sin limitación — mejor que Render Free | — |
Cuándo edge deployment importa para AI
Edge deployment importa cuando:
✅ Usuarios distribuidos globalmente
→ Un chatbot AI para una empresa con oficinas en 5 países
→ Ahorro: 100-200ms de latencia por cada request
✅ Latencia de primera respuesta es crítica
→ La conexión TLS + ruteo + network hop puede sumar 200-400ms
→ Edge reduce esto a <50ms
✅ Apps con estado conversacional (streaming)
→ Cada token de streaming viaja menos distancia
→ UX perceptiblemente más rápida
❌ Todos los usuarios están en una región
→ No necesitas edge si el 95% de tu tráfico es de un país
→ Una VM en la región más cercana es suficiente
❌ La latencia del LLM domina
→ Si tu LLM (OpenAI) tarda 1-3s, los 200ms de network son irrelevantes
→ Edge no mejora la latencia del API externo
Deploy Step-by-Step: App AI en Fly.io
Paso 1: Instalar y autenticar flyctl
# Instalar
brew install flyctl
# o
curl -L https://fly.io/install.sh | sh
# Login (abre browser)
flyctl auth login
# Verificar
flyctl auth whoami
# email@example.com
Paso 2: Crear la app en Fly.io
cd deployment-cloud-guide/module-07/app
# Crear app (interactivo)
flyctl launch
# ? App name: docusearch-ai
# ? Select region: iad (Virginia) — o la más cercana a ti
# ? Would you like to set up a PostgreSQL database? No (lo haremos después)
# ? Would you like to set up an Upstash Redis database? No
# ? Would you like to deploy now? No (primero configuramos)
Esto genera un fly.toml:
# fly.toml
app = "docusearch-ai"
primary_region = "iad"
[build]
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 0
[[vm]]
memory = "512mb"
cpu_kind = "shared"
cpus = 1
Paso 3: Configurar fly.toml para AI
# fly.toml — configurado para AI workloads
app = "docusearch-ai"
primary_region = "iad"
[build]
[env]
PLATFORM = "flyio"
LOG_LEVEL = "info"
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 1
[http_service.concurrency]
type = "requests"
hard_limit = 250
soft_limit = 200
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
method = "GET"
path = "/health"
timeout = "5s"
[[vm]]
memory = "1gb"
cpu_kind = "shared"
cpus = 1
Paso 4: Configurar secrets
# Fly.io usa "secrets" para variables sensibles (encriptadas)
flyctl secrets set OPENAI_API_KEY=sk-proj-xxx
# Listar secrets (solo nombres, no valores)
flyctl secrets list
# NAME DIGEST CREATED AT
# OPENAI_API_KEY abc123 2026-03-08
# Variables no sensibles van en fly.toml [env]
Paso 5: Deploy
flyctl deploy
# Output:
# ==> Building image
# --> docker build
# ==> Pushing image
# ==> Creating release
# ==> Monitoring deployment
# 1 desired, 1 placed, 1 healthy, 0 unhealthy
# --> v1 deployed successfully
Paso 6: Verificar
# URL
flyctl status
# App: docusearch-ai
# Hostname: docusearch-ai.fly.dev
# Machines:
# ID REGION STATE CHECKS
# abc123 iad started 1 total, 1 passing
# Health check
curl https://docusearch-ai.fly.dev/health
# {"status":"healthy","version":"1.0.0","platform":"flyio"}
# Inferencia
curl -X POST https://docusearch-ai.fly.dev/ask \
-H "Content-Type: application/json" \
-d '{"question": "¿Qué es edge computing?", "max_tokens": 200}'
# Logs
flyctl logs
# 2026-03-08T15:45:00Z app[abc123] iad [info] Application startup complete
# 2026-03-08T15:45:05Z app[abc123] iad [info] POST /ask 200 1.5s
# Dashboard web
flyctl dashboard
# Abre https://fly.io/apps/docusearch-ai
Fly.io: Multi-Region Deployment
Agregar regiones
# Listar regiones disponibles
flyctl platform regions
# CODE NAME GATEWAY
# ams Amsterdam, NL ✓
# cdg Paris, France ✓
# fra Frankfurt, Germany ✓
# gru São Paulo, Brazil ✓
# iad Ashburn, Virginia ✓
# lax Los Angeles, CA ✓
# nrt Tokyo, Japan ✓
# sin Singapore ✓
# syd Sydney, Australia ✓
# ... (30+ regiones)
# Escalar a múltiples regiones
flyctl scale count 3 --region iad,gru,fra
# Verificar
flyctl status
# Machines:
# ID REGION STATE
# abc123 iad started
# def456 gru started
# ghi789 fra started
fly.toml para multi-region
# fly.toml
app = "docusearch-ai"
primary_region = "iad"
[http_service]
internal_port = 8000
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 1
[[vm]]
memory = "1gb"
cpu_kind = "shared"
cpus = 1
# El fly.toml no define regiones secundarias
# Se gestionan con flyctl scale:
flyctl scale count 2 --region iad
flyctl scale count 1 --region gru
flyctl scale count 1 --region fra
# Total: 4 VMs en 3 regiones
Routing inteligente
Fly.io rutea automáticamente al punto más cercano:
Request desde México City → iad (Virginia, ~30ms)
Request desde Buenos Aires → gru (São Paulo, ~20ms)
Request desde Berlín → fra (Frankfurt, ~10ms)
Request desde Tokyo → nrt (si tienes VM ahí) o iad (fallback)
No necesitas configurar nada — el anycast routing lo hace automáticamente.
Fly.io: Volumes y Persistencia
Crear un volume
# Crear volume en una región
flyctl volumes create ai_data --size 1 --region iad
# Volume: vol_abc123
# Size: 1 GB
# Region: iad
# Listar volumes
flyctl volumes list
Montar en fly.toml
# fly.toml
[mounts]
source = "ai_data"
destination = "/data"
# En tu app, /data es persistente entre deploys
import os
DATA_DIR = "/data"
def save_embeddings(embeddings, filename):
path = os.path.join(DATA_DIR, filename)
with open(path, "wb") as f:
import pickle
pickle.dump(embeddings, f)
def load_embeddings(filename):
path = os.path.join(DATA_DIR, filename)
if os.path.exists(path):
with open(path, "rb") as f:
import pickle
return pickle.load(f)
return None
Limitación importante: un volume solo existe en una región. Si tienes VMs en iad, gru, y fra, solo la VM en iad puede acceder al volume de iad. Para datos compartidos entre regiones, usa S3 o Cloudflare R2.
Fly.io: Databases
PostgreSQL en Fly.io
# Crear cluster PostgreSQL managed por Fly.io
flyctl postgres create
# ? App name: docusearch-db
# ? Region: iad
# ? VM size: shared-cpu-1x-256mb
# ? Volume size: 1 GB
# ✅ Created: docusearch-db
# Conectar a tu app
flyctl postgres attach docusearch-db --app docusearch-ai
# ✅ DATABASE_URL added as a secret
# Conectar directamente
flyctl postgres connect -a docusearch-db
# postgres=# SELECT version();
Redis en Fly.io (via Upstash)
# Fly.io ofrece Redis managed via Upstash
flyctl redis create
# ? Name: docusearch-redis
# ? Primary region: iad
# ? Read replicas: (none for now)
# ✅ REDIS_URL set as secret
Fly.io CLI: Comandos Esenciales
# App lifecycle
flyctl launch # Crear nueva app (interactivo)
flyctl deploy # Build + deploy
flyctl deploy --local-only # Build local, push imagen
flyctl destroy # Eliminar app
# Status y monitoreo
flyctl status # Estado de la app y machines
flyctl logs # Logs en tiempo real
flyctl logs --region iad # Logs de una región específica
flyctl dashboard # Abrir dashboard web
# Secrets
flyctl secrets set KEY=val # Agregar secret
flyctl secrets list # Listar secrets
flyctl secrets unset KEY # Eliminar secret
# Scaling
flyctl scale count 3 # 3 VMs en la primary region
flyctl scale count 2 --region gru # 2 VMs en São Paulo
flyctl scale vm shared-cpu-1x # Cambiar tipo de VM
flyctl scale memory 1024 # Cambiar RAM a 1 GB
# Machines (control individual)
flyctl machine list # Listar todas las machines
flyctl machine stop abc123 # Parar una machine
flyctl machine start abc123 # Iniciar una machine
# Volumes
flyctl volumes create name --size 1 --region iad
flyctl volumes list
# Databases
flyctl postgres create # Crear PostgreSQL
flyctl postgres connect -a dbname # Conectar a PostgreSQL
flyctl redis create # Crear Redis (Upstash)
# Network
flyctl ips list # Ver IPs asignadas
flyctl certs list # Ver certificados SSL
flyctl certs add api.tu-dominio.com # Agregar custom domain
# SSH
flyctl ssh console # SSH a una machine running
flyctl ssh console --region gru # SSH a machine en región específica
Troubleshooting
Problema 1: "flyctl deploy falla — health check failing"
Solución: Fly.io verifica que tu app responde antes de cortar el deploy. Si el health check falla, el deploy se revierte.
# Verificar que el path es correcto en fly.toml
[[http_service.checks]]
grace_period = "30s" # Dar más tiempo al startup
interval = "30s"
method = "GET"
path = "/health" # Tu endpoint real
timeout = "10s"
# Debug: ver qué pasa en la VM
flyctl ssh console
# Dentro de la VM:
curl http://localhost:8000/health
Problema 2: "App arranca pero las requests fallan — secret not found"
Solución: Los secrets se inyectan como variables de entorno. Verifica que existen:
flyctl secrets list
# Si falta OPENAI_API_KEY:
flyctl secrets set OPENAI_API_KEY=sk-proj-xxx
# El deploy se reinicia automáticamente
Problema 3: "Volume no accesible — mount failed"
Solución: Los volumes están ligados a una región. Si tu VM está en iad pero el volume en gru, no puede montarlo.
# Verificar región del volume
flyctl volumes list
# ID REGION SIZE CREATED
# vol_abc iad 1GB 2026-03-08
# La VM debe estar en la misma región
flyctl status
# Verificar que al menos una machine esté en iad
Problema 4: "Multi-region caro — no puedo pagar 3+ VMs"
Solución: No necesitas multi-region. Una sola VM en la región más cercana a tus usuarios es suficiente para la mayoría de casos. Multi-region es para cuando tienes usuarios globales Y la latencia es crítica.
# Single region es viable y barato
flyctl scale count 1 --region iad
# ~$8/mes con 1 GB RAM
Problema 5: "Auto-stop mata mi app — cold start al volver"
Solución: Por defecto, Fly.io para las VMs después de inactividad. Para AI apps donde el cold start importa:
# fly.toml
[http_service]
auto_stop_machines = "off" # No parar nunca
min_machines_running = 1 # Al menos 1 VM siempre activa
Ejercicios Prácticos
Ejercicio 1: Deploy básico en Fly.io
Despliega tu app AI en Fly.io en una sola región. Configura secrets y verifica el health check.
Ver solución
cd deployment-cloud-guide/module-07/app
# Crear app
flyctl launch
# Name: docusearch-ai-fly
# Region: iad
# Deploy now: No
# Editar fly.toml
cat fly.toml
# Verificar internal_port = 8000
# Agregar secrets
flyctl secrets set OPENAI_API_KEY=sk-proj-xxx
# Deploy
flyctl deploy
# Verificar
flyctl status
# Machines: 1 running in iad
curl https://docusearch-ai-fly.fly.dev/health
# {"status":"healthy","version":"1.0.0","platform":"flyio"}
curl -X POST https://docusearch-ai-fly.fly.dev/ask \
-H "Content-Type: application/json" \
-d '{"question": "¿Qué es Fly.io?", "max_tokens": 150}'
# Ver logs
flyctl logs
Ejercicio 2: Deploy multi-region y medir latencia
Escala tu app a 3 regiones y mide la latencia desde diferentes puntos geográficos usando encabezados de respuesta.
Ver solución
# Escalar a 3 regiones
flyctl scale count 1 --region iad # Virginia
flyctl scale count 1 --region fra # Frankfurt
flyctl scale count 1 --region nrt # Tokyo
# Verificar
flyctl status
# ID REGION STATE
# abc123 iad started
# def456 fra started
# ghi789 nrt started
# Agregar endpoint que muestra la región
# En main.py:
@app.get("/region")
def get_region():
return {
"fly_region": os.environ.get("FLY_REGION", "unknown"),
"fly_alloc_id": os.environ.get("FLY_ALLOC_ID", "unknown"),
}
flyctl deploy
# Test: La respuesta incluye la región que procesó el request
curl https://docusearch-ai-fly.fly.dev/region
# {"fly_region":"iad","fly_alloc_id":"abc123"}
# Fly.io inyecta FLY_REGION automáticamente en cada VM
# measure_regions.py — Medir latencia por región
import requests
import time
import statistics
FLY_URL = "https://docusearch-ai-fly.fly.dev"
REGIONS = ["iad", "fra", "nrt"]
def measure_with_region_header(target_region: str, n: int = 5) -> dict:
"""Fly.io permite forzar región con header fly-prefer-region."""
latencies = []
actual_regions = []
for _ in range(n):
start = time.time()
resp = requests.get(
f"{FLY_URL}/region",
headers={"fly-prefer-region": target_region},
timeout=10,
)
elapsed = (time.time() - start) * 1000
latencies.append(elapsed)
data = resp.json()
actual_regions.append(data.get("fly_region", "unknown"))
time.sleep(0.5)
return {
"target_region": target_region,
"actual_regions": list(set(actual_regions)),
"avg_latency_ms": round(statistics.mean(latencies), 1),
"p95_latency_ms": round(sorted(latencies)[int(n * 0.95)], 1),
}
print("=== Latencia por Región ===")
for region in REGIONS:
result = measure_with_region_header(region)
print(f" {region}: avg={result['avg_latency_ms']}ms "
f"(served from: {result['actual_regions']})")
Ejercicio 3: Configurar volume persistente para cache
Crea un volume en Fly.io y úsalo para cachear respuestas de inferencia en disco, persistiendo entre deploys.
Ver solución
# Crear volume
flyctl volumes create ai_cache --size 1 --region iad
# fly.toml — agregar mount
[mounts]
source = "ai_cache"
destination = "/cache"
# file_cache.py — Cache en disco persistente
import os
import json
import hashlib
from pathlib import Path
CACHE_DIR = Path("/cache/responses")
CACHE_DIR.mkdir(parents=True, exist_ok=True)
def get_cache_key(question: str) -> str:
return hashlib.sha256(question.encode()).hexdigest()[:32]
def get_cached(question: str) -> dict | None:
key = get_cache_key(question)
path = CACHE_DIR / f"{key}.json"
if path.exists():
with open(path) as f:
return json.load(f)
return None
def save_to_cache(question: str, response: dict) -> None:
key = get_cache_key(question)
path = CACHE_DIR / f"{key}.json"
with open(path, "w") as f:
json.dump(response, f, ensure_ascii=False)
def cache_stats() -> dict:
files = list(CACHE_DIR.glob("*.json"))
total_size = sum(f.stat().st_size for f in files)
return {
"entries": len(files),
"total_size_kb": round(total_size / 1024, 1),
}
# En main.py
from file_cache import get_cached, save_to_cache, cache_stats
@app.post("/ask")
async def ask(query: Query):
cached = get_cached(query.question)
if cached:
return Answer(**cached)
response = await call_openai(query.question, query.max_tokens)
save_to_cache(query.question, response)
return Answer(**response)
@app.get("/cache/stats")
def get_cache_stats():
return cache_stats()
flyctl deploy
# Hacer requests
curl -X POST $FLY_URL/ask -H "Content-Type: application/json" \
-d '{"question": "¿Qué es Docker?", "max_tokens": 100}'
# Verificar cache
curl $FLY_URL/cache/stats
# {"entries": 1, "total_size_kb": 0.8}
# Redeploy — el cache persiste
flyctl deploy
curl $FLY_URL/cache/stats
# {"entries": 1, "total_size_kb": 0.8} ← sigue ahí
Ejercicio 4: Comparar cold start — Render vs Railway vs Fly.io
Mide el cold start de cada plataforma (tiempo desde que la app estaba dormida hasta que responde). Documenta los resultados.
Ver solución
# cold_start_comparison.py
import time
import requests
def measure_cold_start(name: str, url: str, wait_minutes: int = 20) -> dict:
"""
Para medir cold start real:
1. Asegúrate de que la app tiene auto-sleep habilitado
2. No envíes requests durante wait_minutes
3. Luego mide la primera request
"""
print(f"\n=== {name} ===")
print(f"Asumiendo que la app no recibió requests en {wait_minutes} minutos...")
start = time.time()
try:
resp = requests.get(f"{url}/health", timeout=120)
elapsed = (time.time() - start) * 1000
return {
"platform": name,
"cold_start_ms": round(elapsed, 1),
"status": resp.status_code,
"success": True,
}
except requests.exceptions.Timeout:
return {
"platform": name,
"cold_start_ms": 120000,
"status": "timeout",
"success": False,
}
platforms = [
("Render (Free)", "https://docusearch-ai.onrender.com"),
("Railway (Hobby)", "https://docusearch-ai-production.up.railway.app"),
("Fly.io (shared)", "https://docusearch-ai-fly.fly.dev"),
]
print("NOTA: Para resultados precisos, no envíes requests a ninguna")
print("plataforma durante 20+ minutos antes de ejecutar este script.\n")
results = []
for name, url in platforms:
result = measure_cold_start(name, url)
results.append(result)
print(f" Cold start: {result['cold_start_ms']}ms")
print("\n=== Resumen Cold Start ===")
print(f"{'Platform':<25} {'Cold Start':>12} {'Status':>10}")
print("-" * 50)
for r in sorted(results, key=lambda x: x["cold_start_ms"]):
print(f"{r['platform']:<25} {r['cold_start_ms']:>10.0f}ms {str(r['status']):>10}")
## Resultados típicos de cold start
| Plataforma | Cold Start | Notas |
|-----------|-----------|-------|
| **Render Free** | 15,000-45,000ms | Spins up container completo |
| **Render Starter** | N/A (no sleep) | Siempre running |
| **Railway Hobby** | 2,000-8,000ms | Con sleep habilitado |
| **Fly.io shared** | 300-2,000ms | Firecracker micro-VM startup |
| **Fly.io (min=1)** | N/A (no sleep) | Con min_machines_running=1 |
Fly.io tiene el cold start más rápido porque Firecracker micro-VMs
arrancan en ~300ms vs ~10-30s para containers Docker completos.
Resumen
- Fly.io es deployment edge: tu app corre en micro-VMs distribuidas globalmente.
- flyctl es la herramienta principal:
flyctl launch+flyctl deploypara desplegar,flyctl scalepara multi-region. - Multi-region es el diferenciador: una IP, múltiples regiones, ruteo automático al más cercano.
- Firecracker micro-VMs arrancan en ~300ms — cold start significativamente más rápido que containers tradicionales.
- Volumes persisten datos entre deploys, pero están ligados a una región — no comparten datos entre VMs de distintas regiones.
- Pricing por uso es competitivo: ~$8/mes para una VM con 1 GB RAM en una región, ~$24/mes para 3 regiones.
- Para AI: Edge importa si tienes usuarios globales Y latencia de red es significativa vs latencia del LLM. Si tu LLM tarda 2s, los 200ms de network son marginales.
- WebSocket soportado nativamente — mejor que Render para streaming de tokens.
Recursos Adicionales
- Fly.io Documentation — Documentación oficial completa
- Fly.io Regions — Lista completa de regiones
- Fly.io Machines API — API para control programático de VMs
- Fly.io Volumes — Almacenamiento persistente
- Fly.io PostgreSQL — PostgreSQL managed
- Fly.io Pricing — Pricing detallado y calculadora
- Firecracker — AWS Open Source — La tecnología detrás de Fly.io