Desplegar en AWS (Vercel para la app web)
Esta página es el manual de despliegue para una instalación de producción de Meratyn donde la aplicación de navegador se sirve en Vercel (o cualquier host estático) y AWS carga los servicios con estado y los proxies con mayor consumo de ancho de banda. El autoalojamiento completo con Docker Compose está cubierto en Autoalojar la consola de gestión; la infraestructura de capas alojadas GeoNode/GeoServer está cubierta en Desplegar capas alojadas.
Topología de un vistazo
La build web es completamente estática, así que no necesita AWS. Todo lo que sirve datos de proyecto, teselas o tráfico proxy sí.
| Componente del repositorio | Contenedor | Hogar en AWS |
|---|---|---|
App de navegador (apps/mappview) | nginx de mappview | Vercel (estático) |
Consola de gestión + API de Share (apps/admin, services/admin) | admin | EC2 (Docker) |
Proxy edge de teselas e IA (services/edge) + su Redis | edge, edge-redis | EC2 (Docker) |
Sidecar de Python (services/server) | incluido en la imagen del Dockerfile raíz tras nginx en /sidecar/ | EC2 (Docker) |
Almacenamiento de objetos (rustfs) | rustfs | S3 — el contenedor solo imita a S3 localmente |
| Sitio de documentación, registro de plugins, build estática del visor | docs, plugins | S3 + CloudFront, o proyectos adicionales de Vercel |
| Base de datos de GeoNode | geonode-db (PostGIS) | RDS para PostgreSQL (PostGIS soportado) |
| GeoNode, GeoServer, celery, memcached, nginx | contenedores del perfil hosted-layers | EC2 (Docker) — no existe reemplazo nativo de AWS |
Monitoreo (gatus, grafana, prometheus, exportadores) | status, grafana, prometheus, … | Omitir al inicio; el monitoreo básico de CloudWatch es gratis. Volver a añadir en la caja si hace falta |
Una instancia EC2 ejecutando el docker/compose/admin.yaml existente (menos
los contenedores reemplazados arriba) es la forma deliberada: ECS/Fargate
acomoda gestión y edge como tareas individuales, pero GeoNode + GeoServer
necesitan volúmenes compartidos (EFS) y terminan costando más que una sola
instancia burstable a esta escala.
Distribución de dominios
Los valores por defecto de Compose compilan un origen público por servicio
en las builds del navegador (VITE_MAPPVIEW_*_URL). Mantén esa disposición
en AWS:
| Origen | Servicio | Servido por |
|---|---|---|
mappview.… | App web | Vercel |
admin.mappview.… | Consola de gestión + API de Share (puerto 8787), y /sidecar/* (puerto 8790) | CloudFront → EC2 |
tiles.mappview.… | Proxy edge (puerto 8788): proxies de teselas, OAM, Source Cooperative y Planetary Computer más el proxy de IA | CloudFront → EC2 |
plugins.mappview.… | Registro de plugins (registry.json y zips) | S3 + CloudFront; el documento de registro se obtiene en tiempo de ejecución, así que entradas nuevas no requieren recompilar el visor |
Niveles
Comienza con el Nivel 1 y habilita el perfil hosted-layers solo cuando necesites capas gestionadas por la organización.
- Nivel 1 — gestión, edge, imagen web+sidecar, S3. Proyectos Share, versiones, colecciones, tokens de API personales, miniaturas, fotos de campo, proxies de teselas y el proxy de IA.
- Nivel 2 — añade el perfil hosted-layers (GeoNode, GeoServer, celery) más RDS. Añade carga/visualización de capas, la sincronización de catálogo y los endpoints de entrega WMS/WFS/WCS propiedad de gestión.
Paso 1: Bucket de S3 y usuario IAM
La gestión habla con el almacenamiento de objetos a través del SDK de AWS
(services/admin/src/field-photos/rustfs.ts), así que el cambio desde
el contenedor local rustfs es solo de entorno. Las fotos de campo se
suben directamente desde el navegador mediante un POST prefirmado; el bucket
se crea y su política se inicializa en el primer arranque, así que el
usuario IAM necesita derechos de administración solo sobre ese bucket.
Crea una política acotada (mappview-field-photos es el bucket de ejemplo):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "MappViewFieldPhotos",
"Effect": "Allow",
"Action": [
"s3:CreateBucket",
"s3:PutBucketPolicy",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": ["arn:aws:s3:::mappview-field-photos", "arn:aws:s3:::mappview-field-photos/*"]
}
]
}
Crea un usuario IAM con esa política y guarda las llaves de acceso para el Paso 4. Las credenciales son explícitas en esta ruta de código, así que los roles de instancia IAM no se recogen. Habilita el versionado de S3 en el bucket como un deshacer barato para borrados.
Más adelante, crea una segunda distribución de CloudFront sobre el bucket
(o un cache behavior en la distribución admin) y usa su URL HTTPS como
RUSTFS_PUBLIC_URL — esa es la URL desde la que los navegadores descargan
las fotos, así que debe ser HTTPS y legible por CORS desde el origen de la
app web.
Paso 2: Instancia EC2
-
Instancia. us-east-1 (o la región más cercana a tus usuarios; esta página cita precios de us-east-1). t3.medium (4 GB) para el Nivel 1, t3.xlarge (16 GB) con el perfil hosted-layers (GeoServer corre con un heap Java de 2 GB). t3.large cabe en el Nivel 2 solo sin el sidecar y con el heap de GeoServer reducido a 1.5 GB. Todo está conteinerizado — Bun, Python y Java no se necesitan en el host.
-
AMI y user data. Amazon Linux 2023 con este user data:
#!/bin/bash dnf install -y docker git docker-compose-plugin systemctl enable --now docker usermod -aG docker ec2-user -
Almacenamiento. 30 GB gp3 para el Nivel 1, 50 GB para el Nivel 2. El servicio de gestión guarda su base de datos SQLite y los objetos de proyecto/miniatura en el volumen
management-data. Toma snapshots de ese volumen con programación — un respaldo que captura solo la base de datos o soloobjects/deja el inventario inconsistente. -
Security group. Permite entrada solo a los puertos 8787, 8788 y 8790 desde la managed prefix list de CloudFront orientada a orígenes (
com.amazonaws.global.cloudfront.origin-facing), más SSH desde tu IP. TLS termina en CloudFront (ACM, gratis); los orígenes EC2 quedan en HTTP plano sobre esa ruta restringida. -
Red. Subred pública, sin NAT gateway necesaria, sin ALB — CloudFront → EC2 sobre HTTP con el security group haciendo el control de acceso. Un ALB añade ~$16/mes y cargos LCU por nada que CloudFront no haga ya.
-
DNS. Hosted zone de Route 53, registros alias A/AAAA hacia las distribuciones. El certificado ACM que usa CloudFront debe vivir en us-east-1.
Paso 3: Override de Compose para AWS
El archivo Compose original vincula cada servicio a 127.0.0.1, lo correcto
para un host local pero inalcanzable desde CloudFront. Añade un
compose.override.yaml junto a él (Compose lo fusiona automáticamente y
reemplaza las claves con valor de lista como ports):
services:
admin:
ports: ["8787:8787"]
edge:
ports: ["8788:8788"]
Mantén ADMIN_TRUST_PROXY=true (Paso 4) para que la deduplicación de
vistas lea CF-Connecting-IP/X-Forwarded-For (CloudFront establece ambos)
en lugar de la IP del edge de CloudFront.
Paso 4: Desplegar la pila del Nivel 1
Crea el archivo de entorno (Compose lee .env en la raíz del proyecto):
# --- gestión ---
APP_ORIGIN=https://admin.mappview.example.com
VIEWER_URL=https://mappview.example.com/
CORS_ORIGINS=https://mappview.example.com,https://admin.mappview.example.com,https://mappview.example.com/tauri,tauri://localhost,http://tauri.localhost
ADMIN_TRUST_PROXY=true
ADMIN_BIND_ADDRESS=0.0.0.0
ADMIN_LOCAL_TOKEN=
ADMIN_BOOTSTRAP_TOKEN=
CLERK_PUBLISHABLE_KEY=pk_live_...
CLERK_SECRET_KEY=sk_live_...
CLERK_AUTHORIZED_PARTIES=https://mappview.example.com,https://admin.mappview.example.com,https://mappview.example.com/tauri,tauri://localhost,http://tauri.localhost
# --- S3 en lugar de rustfs ---
RUSTFS_ENDPOINT=https://s3.us-east-1.amazonaws.com
RUSTFS_PUBLIC_URL=https://photos.mappview.example.com
RUSTFS_ACCESS_KEY=AKIA...
RUSTFS_SECRET_KEY=...
RUSTFS_BUCKET=mappview-field-photos
RUSTFS_REGION=us-east-1
# --- edge ---
EDGE_ALLOWED_ORIGINS=https://mappview.example.com,https://admin.mappview.example.com
AI_PROVIDERS=...
EDGE_ALLOWED_MODELS=...
EDGE_DEFAULT_MODEL=...
EDGE_RATE_LIMIT=10
MAPPVIEW_AI_PROXY_TOKEN=...
Notas sobre los valores:
RUSTFS_ENDPOINTdebe seguir siendo una URL http(s); el cliente usa direccionamiento path-style, que AWS S3 aún acepta — reverificar tras actualizaciones del SDK.CORS_ORIGINSdebe incluir el origen de Vercel, el origen del visor y — para Share de escritorio —tauri://localhostyhttp://tauri.localhost.- Las llaves de Clerk se compilan en el bundle del navegador en tiempo de build, así que recompila las imágenes después de cambiarlas.
Luego despliega:
git clone https://github.com/your-org/mappview.git && cd mappview
docker compose -f docker/compose/admin.yaml up -d --build admin edge edge-redis mappview
# app web + sidecar en una imagen (Dockerfile raíz): usada solo para /sidecar/*
docker build -t mappview-web .
docker run -d --name mappview-web --restart unless-stopped \
-p 8790:80 \
-e MAPPVIEW_SIDECAR_TOKEN="$(openssl rand -hex 24)" \
-v /data/conversion:/data \
mappview-web
- El nginx del contenedor
mappview-webes el llamador confiable del sidecar: inyecta el headerX-MappView-Tokeny reenvía un Host de loopback, que elTrustedHostMiddlewaredel sidecar exige. Pon CloudFront frente a este nginx (Paso 5), no frente al uvicorn desnudo. -v /data/conversion:/datada a las herramientas de conversión su directorio de trabajo; la imagen confina lecturas/escrituras aMAPPVIEW_CONVERSION_ROOTS(por defecto/data) porque el sidecar es alcanzable same-origin. Si usas los endpoints PostGIS del sidecar, configuraMAPPVIEW_POSTGIS_HOSTScon las entradashost:portpermitidas — rechaza todo destino hasta entonces.- Guarda el
MAPPVIEW_SIDECAR_TOKENgenerado; el Paso 5 lo necesita.
La app web de Vercel alcanza el sidecar same-origin, exactamente como lo
hace la build nginx del navegador. Añade un rewrite para que /sidecar/*
en el origen de Vercel se proxee a la distribución admin:
{
"rewrites": [
{
"source": "/sidecar/:path*",
"destination": "https://admin.mappview.example.com/sidecar/:path*"
}
]
}
Paso 5: Distribuciones de CloudFront
Dos distribuciones, una por origen público (CloudFront enruta los nombres de dominio alternativos por distribución, no por ruta). Crea el certificado ACM en us-east-1, cubriendo ambos nombres.
Distribución admin.mappview.… | Distribución tiles.mappview.… | |
|---|---|---|
| Origen 1 | EC2 : 8787 (HTTP) | EC2 : 8788 (HTTP) |
Comportamiento por defecto (*) | Cache policy CachingDisabled + origin request policy AllViewerExceptHostHeader (Authorization, Cookie y headers de IP del cliente deben llegar a la API de Share) | Cache policy CachingOptimized (las teselas son artefactos estáticos) |
Comportamiento /sidecar/* | Origen: EC2 : 8790 (HTTP), custom origin header X-MappView-Token = el valor del Paso 4, cache policy CachingDisabled | — |
| Protocolo del visor | Redirigir HTTP a HTTPS | Redirigir HTTP a HTTPS |
| Compresión | Activada | Activada |
Los tres orígenes EC2 usan solo HTTP — el security group restringe esos puertos a la prefix list de CloudFront, que es el control de acceso. Añade los registros alias de Route 53, luego verifica:
curl -s https://admin.mappview.example.com/api/health
curl -s https://tiles.mappview.example.com/healthz
curl -s https://mappview.example.com/sidecar/conversion/status
Finalmente, apunta el RUSTFS_PUBLIC_URL de la distribución de fotos
(Paso 1) a su URL HTTPS de CloudFront.
Paso 6: Nivel 2 — capas alojadas
Sigue Desplegar capas alojadas para la topología completa y los requisitos de producción. En AWS dos cosas cambian:
- Base de datos. Reemplaza el contenedor
geonode-dbcon RDS para PostgreSQL (db.t4g.small basta para empezar; PostGIS está soportado en RDS). Crea un security group que permita 5432 solo desde la instancia EC2, y sobrescribe los valoresDATABASE_HOST,DATABASE_URLyGEODATABASE_URLde los servicios geonode en el override de Compose — están fijados endocker/compose/admin.yaml, así que una entrada de override por servicio es la ruta limpia. Multi-AZ duplica el costo de la base de datos (~+$23/mes); single-AZ está bien para empezar. - Memoria. GeoServer (
-Xmx2G), GeoNode/Django, celery, los servicios edge y la imagen del sidecar comparten la instancia — t3.xlarge es el mínimo; t3.large solo funciona si sueltas el contenedor del sidecar y reduces el heap de GeoServer a 1.5 GB.
Levanta el perfil con el mismo override:
docker compose -f docker/compose/admin.yaml --profile hosted-layers up -d --build
Estimación de costo (us-east-1, on-demand, single AZ)
| Ítem | Nivel 1 | Nivel 2 |
|---|---|---|
| EC2 (t3.medium / t3.xlarge) | ~$30 | ~$121 |
| EBS gp3 (30 / 50 GB) | ~$2.4 | ~$4 |
| RDS db.t4g.small PostGIS + 20 GB | — | ~$26 |
| S3 + CloudFront + Route 53 + IPv4 pública | ~$5 | ~$7 |
| Total | ~$40/mes | ~$160/mes |
La variable de escalado real es el egreso, no el cómputo. CloudFront
incluye 1 TB de salida de datos por mes gratis (luego ~$0.085/GB); el
egreso de EC2 puro es solo de 100 GB gratis y luego $0.09/GB, que es la
razón por la que cada origen con mucha carga de ancho de banda pasa por
CloudFront. El tráfico de teselas es de unos 10–50 MB por sesión de
mapa, así que el 1 TB gratis cubre del orden de 20.000–100.000 sesiones al
mes. Los ítems más grandes a vigilar son las descargas de capas alojadas
(hasta 2 GB cada una, MAX_LAYER_BYTES) y las respuestas WMS/WMTS de
GeoServer; el caché en disco GWC de GeoServer en EBS mantiene baratas las
peticiones de teselas repetidas.
Operación
- Actualizaciones.
git pull, recompila las imágenes cambiadas y vuelve a ejecutar los mismos comandosdocker compose up -d. Todos los valoresVITE_*y la clave publicable de Clerk son de tiempo de build — cambiarlos exige recompilar las imágenes y el despliegue de Vercel juntos. - Respaldos. Snapshots de EBS del volumen
management-data(base de datos +objects/deben capturarse juntos) y versionado de S3 en el bucket de fotos. En el Nivel 2, los respaldos automatizados de RDS cubren los datos PostGIS. - Monitoreo. Las métricas básicas de instancia en CloudWatch son gratis. La pila de gatus/Grafana/Prometheus del repositorio corre bien como contenedores adicionales en la misma instancia si quieres de vuelta los chequeos de disponibilidad y los dashboards.
- Secretos. Mueve los valores de
.enva AWS Secrets Manager o SSM Parameter Store antes de que el despliegue crezca más allá de un solo operador; el archivo Compose usa valores de entorno planos a propósito solo para pruebas locales.
Capacidades a esperar
Siempre disponibles (Nivel 1)
- Share — publicación de proyectos, historial de versiones inmutable,
forks, colecciones, miniaturas, conteos de vistas y tokens personales
mappview_…verificados como hashes SHA-256. El Share de escritorio funciona cuando los orígenes Tauri están enCORS_ORIGINS. - Embeds y “Abrir en Meratyn” — la URL del visor pasada en tiempo de
build; las exportaciones HTML y los embeds
<iframe>apuntan a la app de Vercel. - Proxies de teselas — fuentes de OpenAerialMap, Source Cooperative y
Planetary Computer servidas desde
tiles.mappview.…para que el navegador nunca necesite CORS ni tokens por proveedor. - Proxy de IA — el tráfico del asistente configurable por proveedor
fluye por
edgecon modelos permitidos, un rate limit y las llaves upstream guardadas del lado del servidor. - Fotos de campo — subida directa de navegador a S3 vía POST prefirmado
(25 MB por foto por defecto,
FIELD_PHOTO_MAX_BYTES). - Procesamiento web con el sidecar desplegado — las herramientas de
Conversión (GeoParquet, FlatGeobuf, PMTiles, COG), los flujos ráster con
archivos nativos y las herramientas Whitebox fuera del catálogo del
navegador pueden usar el sidecar; el rewrite de Vercel lo hace same-origin
en
/sidecar. Confínalo conMAPPVIEW_CONVERSION_ROOTS(por defecto/data) yMAPPVIEW_POSTGIS_HOSTS. La conversión a PMTiles y las rutas Whitebox del sidecar son solo amd64 — las instancias EC2 estándar son amd64, así que esto no es una restricción en AWS (sí lo es en ARM/Apple Silicon). Sin el sidecar, las operaciones de conversión, ráster y Whitebox compatibles con el navegador siguen disponibles; las rutas a archivos nativos y las operaciones fuera de esos motores necesitan el sidecar. - Cuotas de almacenamiento — 25 MB por documento de proyecto, 100 MB de cuota por defecto por usuario, ambas configurables por entorno.
Añadidas por el Nivel 2 (capas alojadas)
- Publicación de capas — se suben archivos SIG a través de gestión; celery los publica en GeoNode/GeoServer de forma asíncrona.
- Sincronización de catálogo — gestión reconcilia el catálogo de
GeoNode cada
GEONODE_CATALOGUE_SYNC_INTERVAL_MS(5 minutos por defecto). - Entrega OGC — gestión expone endpoints estables de capacidades y
peticiones WMS/WFS/WCS para las capas publicadas, manteniendo privados
los endpoints proveedores y emitiendo acceso firmado de corta vida
(
LAYER_ACCESS_TTL_SECONDS, 300 s por defecto). Estos fluyen por CloudFront como todo lo demás.