Desplegar una app full-stack — paso a paso
Guía completa de una app real: frontend (SPA) + backend (API) + base de datos, desde el dashboard vacío hasta un sistema corriendo con HTTPS, migrado y sembrado. Si tu repo tiene un docker-compose.yml, casi todo se autodetecta.
Contenido
- Qué necesitás
- 1. Importá tu
docker-compose.yml - 2. Revisá los servicios detectados
- 3. Cómo se encuentran los servicios (service discovery)
- 4. Apps que necesitan un
.env/archivo de config en disco - 5. Secrets
- 6. Deploy
- 7. Migraciones y seeders
- 8. Verificá que funciona
- 9. Actualizar tu app
- Ejemplo completo (API)
- Troubleshooting rápido
Qué necesitás
- Una cuenta en el dashboard (
https://app.cynchro.cloud). Las crea el admin de la plataforma. - Un repo Git con un Dockerfile por servicio (ej.
backend/yfrontend/, cada uno con su Dockerfile). Undocker-compose.ymlen la raíz hace que todo sea un click de Import. - Si el repo es privado, un token de acceso de solo lectura (PAT de GitHub).
Una app en la plataforma puede correr varios containers (servicios) — frontend, backend y base de datos conviven bajo una sola app.
1. Importá tu docker-compose.yml
- + Deploy → Multi-service.
- Pegá la URL del repo (y el token si es privado) y hacé click en Import.
- La plataforma lee tu compose y precarga las filas de servicios: build vs imagen, puertos, env, build args, volúmenes persistentes y qué servicios son públicos. Las credenciales que tuvo que generar (passwords de DB, valores
${VAR:?...}) se muestran una sola vez — descargá el.envy guardalo.
Qué mapea el importador por vos:
| Compose | → Plataforma |
|---|---|
build: { context, dockerfile } | servicio build-from-git |
build.args (ej. VITE_API_URL) | build args (horneados en la imagen al buildear) |
image: | servicio con imagen prearmada |
ports: con puerto web publicado | marca el servicio público (SPA y API pueden ser públicos) |
volumes: con nombre | rutas persistentes (ej. el data dir de una DB) |
environment: / ${VAR:-default} / ${VAR:?requerida} | valores de env (secrets autogenerados y reusados entre servicios) |
bind mounts de host (./x:/y), env_file, depends_on | se descartan con un warning — ver abajo |
El importador busca
docker-compose.yml/compose.ymlen la raíz del repo. Si tu archivo de producción se llama distinto (ej.docker-compose.prod.yml), copiá las filas a mano o renombralo en una rama de deploy.
2. Revisá los servicios detectados
Para cada servicio confirmá:
- ¿Público? Un servicio público recibe su dominio HTTPS + certificado. El primer público toma
https://<app>.cynchro.cloud; los públicos extra recibenhttps://<app>-<servicio>.cynchro.cloud. Los servicios internos (una base de datos, un cache) no se exponen a internet. - Puerto — el puerto que escucha el container (no el puerto del host del compose).
- Volúmenes — una base de datos necesita persistir su data dir (ej.
/var/lib/mysql,/var/lib/postgresql/data), o perdés los datos en cada redeploy.
Un stack típico queda: frontend (público), backend (público), db (interno).
3. Cómo se encuentran los servicios (service discovery)
Los servicios de una app comparten una red privada y se alcanzan por nombre de servicio — ese nombre es el hostname. No adivinás IPs. La plataforma además inyecta variables de discovery en cada container:
| Variable | Ejemplo |
|---|---|
<SVC>_HOST | DB_HOST=db |
<SVC>_PORT | DB_PORT=5432 |
<SVC>_URL | BACKEND_URL=http://backend:3000 |
<SVC>_PUBLIC_URL (servicios públicos) | BACKEND_PUBLIC_URL=https://miapp-backend.cynchro.cloud |
Tu env explícito siempre gana sobre estas. Así tu backend conecta a la DB con DB_HOST=db, y —clave— un SPA que corre en el navegador debe llamar al backend por su URL pública, no http://backend. Para un SPA cuya URL de API se hornea en build (ej. VITE_API_URL de Vite, NEXT_PUBLIC_* de Next), pasala como build arg apuntando a la URL pública del backend:
buildArgs: ["VITE_API_URL"]+env VITE_API_URL=https://miapp-backend.cynchro.cloud
El error #1: el
DB_HOST(o la URL de la API) de tu app tiene que coincidir con el nombre del servicio. Si tu código esperamoduxdb, nombrámoduxdbal servicio de la base.
4. Apps que necesitan un .env/archivo de config en disco
Algunos frameworks exigen que exista un archivo de config en disco (no solo variables de entorno). En vez de tocar tu Dockerfile, seteá el Env file path del servicio:
- Env file path:
/var/www/html/.env
La plataforma renderiza el env del servicio como un .env read-only (en RAM) en esa ruta — tanto en el container corriendo como en los comandos one-off. Sin hacks en el entrypoint.
5. Secrets
Mantené las credenciales fuera del form con Secrets (org-wide o scopeados a una app). Inyectalos como env vars o montalos como archivos en /run/secrets/<KEY>. Ver Secrets.
6. Deploy
Click en Deploy. La plataforma buildea cada servicio desde tu repo, provisiona los dominios HTTPS + certificados Let’s Encrypt, y levanta los containers. Seguí el progreso en el panel Inspect de la app (eventos de build/deploy, logs por servicio).
7. Migraciones y seeders
La plataforma no migra ni siembra la base sola — vos le decís cómo:
- Release command (a nivel app, corre automáticamente en cada deploy): poné acá tu comando de migración, ej.
php artisan migrate --force,npm run migrate,php modux migrate. Corre en un container efímero con el env + red completos de tu app. Que sea idempotente (las herramientas de migración reales solo aplican lo pendiente). - Run a command (one-off, desde el detalle de la app): para seeders y tareas ad-hoc, ej.
npm run seed,php seeders/AdminSeeder.php admin <pass> "Mi Estudio". El output sale en el feed de eventos. Un run que falla no tira abajo tu app.
El readiness es automático. Las imágenes de DB (mysql/mariadb/postgres/mongo) reciben un healthcheck, y la plataforma espera a que la DB acepte conexiones antes de correr el release command — así las migraciones no compiten con una base a medio inicializar. No necesitás un loop de espera manual.
8. Verificá que funciona
- Abrí las URLs públicas. El panel Connections en el detalle de la app lista la dirección interna de cada servicio (
http://backend:3000) y su URL pública. - Inspect muestra estado/health de containers, eventos de build & deploy (donde aparecen los errores de build) y logs en vivo por servicio.
- Manage database abre un gestor web de DB (Adminer) auto-logueado a la base de tu app.
- Show env / credentials descarga los valores exactos con los que corre la app (solo el dueño).
9. Actualizar tu app
- Un deploy desde repo configura un webhook:
git pusha la rama desplegada → rebuild + redeploy automático (las migraciones se re-corren vía release command). - O click en Redeploy en el dashboard para reconstruir la config actual.
- Restart solo rebota los containers (sin rebuild).
Ejemplo completo (API)
Lo mismo que el dashboard, vía POST /deploy — un SPA + API + MySQL, migrando en el deploy:
curl -X POST $API/deploy -H "authorization: bearer $TOKEN" -H 'content-type: application/json' -d '{
"name": "shop",
"repo": "https://github.com/acme/shop.git",
"branch": "main",
"repoToken": "ghp_...", // solo para repos privados
"releaseCommand": "php artisan migrate --force",
"services": [
{ "name": "backend", "context": "backend", "dockerfile": "backend/Dockerfile",
"port": 80, "public": true,
"envFilePath": "/var/www/html/.env", // la app exige un .env en disco
"env": { "DB_HOST": "db", "DB_DATABASE": "shop", "DB_USERNAME": "shop", "DB_PASSWORD": "s3cret",
"APP_URL": "https://shop-backend.cynchro.cloud" } },
{ "name": "frontend", "context": "frontend", "dockerfile": "frontend/Dockerfile",
"port": 80, "public": true,
"buildArgs": ["VITE_API_URL"],
"env": { "VITE_API_URL": "https://shop-backend.cynchro.cloud" } },
{ "name": "db", "image": "mysql:8.0", "port": 3306, "public": false,
"volumes": ["/var/lib/mysql"],
"env": { "MYSQL_DATABASE": "shop", "MYSQL_USER": "shop", "MYSQL_PASSWORD": "s3cret",
"MYSQL_ROOT_PASSWORD": "r00t" } }
]
}'
Después del deploy, corré los seeders one-off desde el detalle de la app (Run a command), apuntando al servicio backend.
Troubleshooting rápido
| Síntoma | Solución |
|---|---|
getaddrinfo for X failed / no resuelve la DB | Tu DB_HOST ≠ el nombre del servicio de la DB. Hacelos coincidir. |
| El SPA carga pero el login/API falla | El backend tiene que ser público, y la URL de API del SPA tiene que ser su URL pública (build arg), no localhost ni http://backend. |
Connection refused a la DB durante el migrate | La DB no estaba lista — ahora la plataforma espera sola; asegurate de que la DB use una imagen conocida (mysql/postgres/…). |
| La app devuelve 503 “config no encontrada” | Tu app necesita un archivo en disco — seteá Env file path (ej. /var/www/html/.env). |
| Build falló, sin container | Abrí Inspect → Build & deploy events (la fuente de verdad de los errores de build). |
Ver también Apps multi-servicio, Gestionar apps y Debugging.