Deploy de una app Astro + API Python en un VPS con Docker
Publicar una app en un VPS no debería convertirse en una colección de comandos sueltos, cambios manuales y reinicios nerviosos.
Si tienes:
- un frontend en Astro
- una API en Python
- un VPS con Docker
puedes montar un flujo bastante razonable sin meterte todavía en Kubernetes, pipelines enormes ni complejidad innecesaria.
En este tutorial voy a mostrar una forma práctica de desplegar una app Astro + API Python usando la estructura real de este proyecto:
- frontend estático compilado y servido con Nginx
- backend Python con FastAPI y Uvicorn
docker composepara producciónMakefilecon comandos de despliegue y actualización
Qué vas a construir
La arquitectura final es esta:
Internet
↓
Nginx del VPS / reverse proxy
↓
127.0.0.1:5100 -> frontend Astro servido por Nginx
127.0.0.1:5200 -> API Python con FastAPI
La idea clave es que los contenedores no se exponen públicamente a todo Internet, sino solo a 127.0.0.1, y el acceso externo lo controla tu reverse proxy del VPS.
Cuándo conviene este enfoque
Tiene sentido cuando:
- estás desplegando una app pequeña o mediana
- quieres un flujo mantenible sin montar una plataforma compleja
- necesitas separar frontend y backend
- quieres dejar un deploy reproducible en el repositorio
No es la mejor solución cuando:
- necesitas alta disponibilidad real con múltiples nodos
- operas múltiples entornos complejos con fuerte automatización
- requieres escalado horizontal serio desde el día uno
Stack usado en este proyecto
Este repo ya está preparado con:
docker-compose.ymlpara desarrollodocker-compose.prod.ymlpara produccióndocker/frontend/Dockerfile.prodpara compilar Astro y servirlo con Nginxdocker/backend/Dockerfile.prodpara correr FastAPI en producciónMakefilecon comandos de deploy
Archivos clave:
- docker-compose.yml
- docker-compose.prod.yml
- docker/frontend/Dockerfile.prod
- docker/backend/Dockerfile.prod
- Makefile
Requisitos previos
Antes de empezar, necesitas:
- un VPS Linux
- Docker y Docker Compose instalados
- acceso por SSH
- un dominio apuntando al VPS
- un reverse proxy o Nginx en el host
- el proyecto versionado en Git
Paso 1: Entender la separación entre desarrollo y producción
En desarrollo, este proyecto monta código fuente como volumen y expone puertos para trabajar localmente.
En producción, cambia la estrategia:
- no se montan volúmenes de código del frontend
- Astro se compila durante el build
- el frontend se sirve desde Nginx dentro del contenedor
- el backend corre con configuración productiva
- los puertos se bindean a
127.0.0.1
Ese cambio está en docker-compose.prod.yml.
Ejemplo importante del frontend:
ports:
- "127.0.0.1:${FRONTEND_PORT:-5100}:80"
Y del backend:
ports:
- "127.0.0.1:${BACKEND_PORT:-5200}:8000"
Eso evita dejar los servicios expuestos directamente hacia afuera.
Paso 2: Cómo se construye el frontend
El frontend usa una imagen multi-stage en docker/frontend/Dockerfile.prod.
La lógica es:
- instalar dependencias
- ejecutar
npm run build - copiar
dist/a una imagen ligera de Nginx
La ventaja es clara:
- la imagen final es más liviana
- no necesitas Node.js en runtime
- el frontend queda servido como sitio estático
Paso 3: Cómo se construye el backend
El backend en docker/backend/Dockerfile.prod hace algo más simple:
- parte de
python:3.11-slim - instala
requirements.txt - copia el código
- crea
/app/data - arranca Uvicorn
Comando de producción:
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
En este proyecto además se monta un volumen para persistir contactos:
volumes:
- ./backend/data:/app/data
Eso evita perder datos al recrear el contenedor.
Paso 4: Estructura mínima en el VPS
Una estructura razonable en servidor sería:
/var/www/sincrodev.com/
├── docker-compose.yml
├── docker-compose.prod.yml
├── Makefile
├── frontend/
├── backend/
└── docker/
En este proyecto, esa ruta ya está reflejada en el Makefile:
VPS_HOST = root@72.60.13.147
VPS_PATH = /var/www/sincrodev.com/
Paso 5: Levantar producción manualmente
Si quieres hacer un primer deploy manual, dentro del VPS puedes ejecutar:
docker compose -f docker-compose.yml -f docker-compose.prod.yml build
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
O, usando el Makefile del proyecto:
make prod-build
make prod-start
Eso construye y levanta:
sincrodev-frontendsincrodev-backend
Paso 6: Verificar que los servicios estén vivos
El archivo de producción ya incluye healthchecks.
Frontend:
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/"]
Backend:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/api/health"]
Puedes revisar estado con:
make prod-status
Y logs con:
make prod-logs
Paso 7: Añadir un reverse proxy delante
Como los contenedores escuchan en 127.0.0.1, necesitas un reverse proxy en el host del VPS.
La idea sería algo como:
sincrodev.com -> 127.0.0.1:5100
sincrodev.com/api -> 127.0.0.1:5200
Eso te permite:
- manejar SSL en un solo lugar
- separar acceso público de puertos internos
- mantener más limpio el despliegue
No estoy incluyendo la configuración completa de Nginx del host en este tutorial, pero ese sería el siguiente paso natural si aún no lo tienes montado.
Paso 8: Automatizar el deploy con Make
Uno de los puntos más útiles del proyecto es que ya tiene un comando de deploy remoto en Makefile.
Target:
deploy-vps:
@echo ">>> Pusheando cambios a origin..."
git push origin master
@echo ">>> Conectando al VPS y desplegando..."
ssh $(VPS_HOST) 'cd $(VPS_PATH) && git pull origin master && make prod-update'
Eso te permite lanzar desde local:
make deploy-vps
Y ejecutar el flujo:
- push a
origin - SSH al VPS
git pull- rebuild
- recreación de contenedores
Es simple, entendible y suficiente para muchos proyectos chicos y medianos.
Paso 9: Cómo funciona prod-update
El target prod-update hace algo importante:
- construye imágenes de producción
- detiene contenedores viejos
- los elimina
- garantiza la red Docker
- levanta de nuevo frontend y backend
Eso concentra el procedimiento operativo en un solo punto del proyecto.
Es mejor que depender de una secuencia manual recordada “de memoria”.
Errores comunes
1) Exponer puertos al público sin necesidad
Si publicas 0.0.0.0:5100 o 0.0.0.0:5200 sin un motivo claro, estás abriendo más superficie de ataque de la necesaria.
2) No persistir datos importantes
Si el backend escribe archivos o datos locales y no montas volumen, perderás información al recrear contenedores.
3) Mezclar desarrollo con producción
Montar el código como volumen en producción suele ser mala señal. En producción quieres imágenes construidas, no un entorno mutable.
4) No tener healthchecks
Sin healthchecks, un contenedor “arriba” puede estar roto y no darte ninguna pista rápida.
5) No dejar el deploy en el repositorio
Si el procedimiento de deploy vive en la cabeza de una persona, el sistema no es mantenible.
Qué mejoraría después de este punto
Una vez que esto funcione bien, los siguientes pasos razonables son:
- separar entornos (
staging,production) - agregar backups automatizados
- rotación y centralización de logs
- pipeline CI/CD
- monitoreo básico
- estrategia de rollback más explícita
No necesitas todo eso el primer día. Pero sí conviene saber hacia dónde evolucionar.
Resumen del flujo
Con esta arquitectura:
- construyes frontend y backend en imágenes separadas
- levantas producción con
docker composey override productivo - publicas solo puertos locales
- dejas el acceso público al reverse proxy
- centralizas actualización con
make deploy-vps
No es una plataforma “enterprise”, pero sí una base muy digna para desplegar software real sin convertir la operación en un caos.
Cierre
Un buen deploy no es solo “que funcione ahora”. Es que otro yo de dentro de tres meses pueda entender cómo está montado, actualizarlo sin miedo y detectar rápido si algo se rompió.
Ese estándar ya te pone por encima de muchos despliegues improvisados.
Si necesitas adaptar este esquema a tu proyecto, dejar un VPS operativo o diseñar una arquitectura un poco más sólida para producción, puedes escribirme desde Sobre Mí.