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
  1. Qué necesitás
  2. 1. Importá tu docker-compose.yml
  3. 2. Revisá los servicios detectados
  4. 3. Cómo se encuentran los servicios (service discovery)
  5. 4. Apps que necesitan un .env/archivo de config en disco
  6. 5. Secrets
  7. 6. Deploy
  8. 7. Migraciones y seeders
  9. 8. Verificá que funciona
  10. 9. Actualizar tu app
  11. Ejemplo completo (API)
  12. 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/ y frontend/, cada uno con su Dockerfile). Un docker-compose.yml en 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

  1. + Deploy → Multi-service.
  2. Pegá la URL del repo (y el token si es privado) y hacé click en Import.
  3. 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 .env y 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.yml en 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 reciben https://<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 espera moduxdb, nombrá moduxdb al 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 push a 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.


This site uses Just the Docs, a documentation theme for Jekyll.