Servicios
Un servicio es algo que corre: sale de un origen, se construye en una imagen y se ejecuta como un contenedor. Crear un servicio lo despliega en el acto (fundaia new … --no-deploy lo evita).
Todo servicio nace privado: sus vecinos del proyecto lo alcanzan por su nombre (api:8080), pero nadie desde fuera hasta que se publica. Ver Dominios.
De dónde sale
| Origen | Qué hace la plataforma | Cómo se crea |
|---|---|---|
| Repositorio de GitHub | Clona la rama, detecta cómo construirlo y genera una receta | fundaia new tienda api --repo owner/api --branch main |
| Imagen de contenedor | La descarga y la arranca, sin construir nada | fundaia new tienda web --image nginx:1.27 |
| Componente del catálogo | Una base de datos, un bucket, un agente… con sus credenciales generadas | fundaia new tienda Postgres --template postgres-16 |
| Código subido | Construye lo que le mandes, sin repositorio | fundaia new tienda api --upload y luego fundaia up |
Los componentes se explican en Bases de datos y componentes, y lo que implica GitHub (repositorios de otra persona, monorepos, desplegar al hacer push) en GitHub.
Imágenes que se actualizan solas
Una imagen de un registro (nginx:1.27, ghcr.io/acme/web:latest) o un componente del catálogo (postgres:16) puede seguir a su etiqueta: con Ajustes → Origen → Actualizar la imagen sola, cada seis horas se baja la etiqueta y, si detrás hay una imagen nueva, se despliega como cualquier otro despliegue, con su comprobación de salud y marcado por una imagen nueva en el historial.
fundaia settings tienda web --auto-update true
fundaia pull tienda web # mirar ahora, esté activado o no
- Está apagado hasta que lo enciendes: que
postgres:16pase a 16.5 es lo que quieres, quelatestsalte de versión mayor quizá no. La etiqueta que elijas decide cuánto se mueve. - Un servicio dormido no se despierta para actualizarse; se mira la próxima vez que esté en marcha.
- Volver atrás arranca la imagen que corría, no la etiqueta, mientras siga en la máquina (hora y media después de quitarla de en medio, la limpieza de capas se la lleva).
- El agente de Claude Code no lo necesita: la plataforma lo mantiene al día por su cuenta. Con MCP,
imageAutoUpdateenupdate_service_settingsypull_image.
Subir código
Un servicio creado con Subir código espera a que le llegue el código. Desde la carpeta del proyecto, fundaia up tienda api la empaqueta y la despliega; en la web se arrastra un .zip o un .tar.gz sobre la pestaña del servicio. El límite por defecto es de 100 MB.
fundaia up respeta .gitignore y .fundaiaignore, deja siempre fuera .git y node_modules, y retiene los ficheros que parecen credenciales (.env*, *.pem, *.key, .ssh/…) y te dice cuáles. Un secreto va en una variable, nunca dentro de la imagen.
Cómo se construye
La primera vez que se despliega código, la plataforma detecta la pila y escribe una receta: un Containerfile, el puerto y unas notas. La receta se guarda, se ve en la interfaz y se puede editar; en cuanto la editas deja de regenerarse, y lo que se construye es siempre lo que está escrito.
| Pila | Se detecta por | Notas |
|---|---|---|
| Dockerfile propio | Dockerfile o Containerfile | Se usa tal cual; no se genera nada |
| Java | pom.xml, build.gradle(.kts) | Con el wrapper si existe; añade -XX:MaxRAMPercentage=75 |
| Rust | Cargo.toml | Binario sobre Debian, con RUST_LOG=info |
| Frontends | El framework en package.json | Next, Nuxt, SvelteKit, Astro, Vite… ver Frontends |
| Bun | bun.lock, bunfig.toml | bun install --frozen-lockfile |
| Node | package.json | El gestor que indique el fichero de bloqueo |
| Python | requirements.txt, pyproject.toml | Django, FastAPI o main.py |
| Estático | index.html | Caddy sirviendo los ficheros |
fundaia recipe show tienda api --plain > Containerfile
fundaia recipe edit tienda api --file Containerfile
Un despliegue cuyo commit y cuya receta ya se construyeron reutiliza la imagen.
Ajustes
La pestaña Ajustes de cada servicio, o fundaia settings <proyecto> <servicio>, que solo cambia lo que le pases:
| Sección | Qué se ajusta | En el CLI |
|---|---|---|
| Origen | Repositorio, rama y carpeta del monorepo | --repo, --branch, --root |
| Comprobación | Qué se pregunta antes de dar un despliegue por bueno | --health-probe, --health-path, --health-timeout… |
| Caché | Que la pasarela guarde respuestas y conteste sin despertar al servicio | --cache, --cache-ttl |
| Recursos | Memoria y CPU; réplicas, si se reparten entre servidores y si se ajustan solas; dormir sin tráfico y a los cuántos minutos | --memory, --replicas, --spread-replicas, --autoscale, --sleep, --sleep-after |
| Horario | Cuándo tiene que estar encendido; fuera de horario duerme | --awake, --awake-tz, --awake-prewarm (Dormir y despertar) |
| Arranque | Comando de arranque, qué hacer si el proceso termina, cuántos reintentos y si volver atrás solo cuando una versión nueva se cae | --start-command, --restart, --restart-retries, --auto-rollback |
| Deploy hooks | URLs secretas que despliegan el servicio desde fuera | fundaia hooks, ver Despliegues |
| Volúmenes | Directorios que sobreviven a cada despliegue | ver Volúmenes |
La memoria es un techo: el contenedor que la supera muere con código 137. Si la máquina tiene memoria libre, la plataforma puede darle más mientras la necesite, nunca menos de lo escrito.
La comprobación acepta por defecto cualquier respuesta por debajo de 500 en la ruta de salud: un 404 es una ruta mal puesta, no un servicio caído. Para un worker que no escucha en ningún puerto, --health-probe none.
Dormir sin tráfico está activado por defecto, a las ocho horas; ver Dormir y despertar.
Réplicas
Un servicio puede correr en varios contenedores iguales detrás del mismo dominio, de 1 a 10. La pasarela reparte las peticiones por turnos entre ellos, y si uno no responde lo deja diez segundos de lado y contesta otro: una lectura (GET, HEAD) se reintenta en el siguiente, una escritura nunca se envía dos veces. Dentro del entorno, el nombre del servicio (web:8080) resuelve a todas las réplicas.
fundaia settings tienda web --replicas 3
fundaia replicas tienda web # dónde corre cada una y si está en marcha
- Cambiar el número despliega de nuevo la misma imagen, sin cortar peticiones: la versión anterior sigue contestando hasta que todas las réplicas nuevas responden a su comprobación.
- Duermen y despiertan juntas, y una réplica que se para sola se vuelve a arrancar en menos de un minuto; una que desaparece deja de recibir tráfico.
- Cada réplica reserva su memoria: el formulario dice cuánto suma, y no se aceptan más de las que caben.
- No tienen réplicas los componentes del catálogo (Postgres, Redis…), que guardan su estado en un contenedor, ni los servicios con un volumen: dos contenedores escribiendo en el mismo directorio lo romperían.
- Con varios servidores, pueden repartirse entre ellos (Varios servidores).
Para un agente es list_replicas, y replicas/spreadReplicas en update_service_settings.
Autoescalado
En vez de fijar cuántas réplicas corren, puedes darle a la plataforma un rango y un objetivo, y ella elige el número: sube cuando el servicio va apretado y baja cuando sobra.
fundaia settings tienda web --autoscale 1-5 --target-cpu 70
fundaia settings tienda web --autoscale 2-8 --target-rpm 600 --cooldown 10m
fundaia settings tienda web --autoscale off # las réplicas se quedan como estén
fundaia replicas tienda web # cuántas quiere, cuántas tiene y por qué
El objetivo es la CPU de cada réplica (del 10 al 95 %, sobre la CPU que tiene asignada), las peticiones por minuto que atiende cada réplica, o las dos; manda la que más aprieta. Cada 30 segundos mira el último minuto y decide:
- Sube cuando lleva dos lecturas seguidas (un minuto) por encima del objetivo, en proporción a cuánto lo pasa —con la CPU al 92 % y el objetivo al 60 %, de 2 pasa a 4— pero dos réplicas como mucho de cada vez y nunca por encima del máximo.
- Baja de una en una cuando lleva la espera entera (cinco minutos si no dices otra cosa, de 1 a
- por debajo del 60 % del objetivo, si desde el último cambio ha pasado también esa espera y si las que quedan no se pasarían del objetivo. Nunca baja del mínimo.
- No toca nada mientras hay un despliegue en marcha, si el entorno está congelado, si el servicio duerme o si las réplicas nuevas no caben en la memoria del servidor: lo apunta como retenido y lo vuelve a mirar.
Las réplicas nuevas arrancan con la misma imagen y las mismas variables, sin desplegar: cada una espera a pasar su comprobación antes de recibir tráfico. Al bajar, la que sobra deja de recibir peticiones nuevas, termina las que tiene en curso y se para. Si las variables han cambiado desde el último despliegue, una réplica nueva no arrancaría igual que las demás, así que en ese caso despliega de nuevo la misma imagen con el número nuevo.
Cada decisión queda en el historial del proyecto como Fundaia, con el motivo («CPU al 92 % durante 1 min: de 2 a 4 réplicas»), y en el panel de réplicas, que dibuja cómo ha ido cambiando el número. Con el autoescalado puesto, el número de réplicas de los ajustes lo lleva él: solo se puede cambiar dentro del rango, y si el mínimo pasa de las que hay, sube hasta él.
En un entorno protegido, guardar el autoescalado pide escribir su nombre, y una copia del entorno lo hereda pero no actúa hasta que alguien lo vuelve a guardar en ella. Para un agente es autoscale en update_service_settings ("off" lo quita) y el bloque autoscale de list_replicas.
Estados
| Estado | Qué significa |
|---|---|
| Sin desplegar | Existe, pero nunca ha corrido |
| Desplegando | Hay un despliegue en curso |
| En marcha | Un contenedor sirviendo |
| Parado | Dormido; la siguiente petición lo despierta |
| Con problemas | Responde, pero no del todo bien |
| Ha caído | El contenedor se murió solo |
| Ha fallado | El último despliegue no llegó a servir y no queda nada corriendo |
Si se cae
La política de Arranque decide qué pasa cuando el proceso termina: Reiniciar siempre, Reiniciar solo si falló o Dejarlo parado. Podman reinicia el contenedor en el acto; si aun así queda Ha caído, la plataforma lo intenta levantar gastando los reintentos del servicio (diez por defecto): lo arranca si el contenedor sigue ahí, o despliega otra vez la misma imagen si ya no existe. Cada intento espera el doble que el anterior, desde treinta segundos hasta un máximo de diez minutos, y el contador vuelve a cero en cuanto el servicio está en marcha.
No se reintenta un servicio que Ha fallado (casi siempre, código que no compila), uno dormido, ni uno con Dejarlo parado. Cada intento queda en el historial del proyecto a nombre de Fundaia.
Si la versión que se cae acaba de salir, el rollback automático vuelve a la anterior en vez de insistir con ella; mientras lo hace, no se reintenta la que se cayó.
Para enterarte antes de la caída, mira los avisos por correo.
Consola
La pestaña Consola de un servicio ejecuta un comando dentro del contenedor que está sirviendo y enseña lo que imprime, con su código de salida y lo que tardó: para mirar ficheros, el disco, los procesos o lanzar a mano una migración o el cliente de una base de datos (psql, redis-cli…).
- Cada comando va por
sh -c, como mucho 60 segundos, y devuelve hasta 64 KB de salida por flujo. ↑ y ↓ recorren los que ya escribiste. - Los secretos del servicio salen tapados (
[redacted]), como en los registros de despliegue. - Cada comando queda en el historial del proyecto con su autor.
- El servicio tiene que estar en marcha: uno dormido se despierta antes (abriendo su dirección o con Despertar). En un servicio adoptado no se ejecuta nada.
fundaia exec tienda api -- ls -la /app
fundaia exec tienda postgres -- psql -U postgres -c 'select count(*) from pedidos'
Con MCP, run_command. Se anota como destructiva: un cliente debería pedir confirmación antes, porque un comando puede borrar datos.
Tareas programadas
La pestaña Tareas guarda comandos que el servicio ejecuta solo, a la hora que digas: limpiar sesiones viejas cada noche, mandar un informe los lunes, vaciar una cola cada cinco minutos. Corren dentro del contenedor que está sirviendo, como en la consola, así que tienen el código, las variables y la red del servicio sin montar nada aparte.
fundaia tasks add tienda api limpiar "0 3 * * *" -- node scripts/cleanup.js
fundaia tasks list tienda api
fundaia tasks run tienda api limpiar # ahora, esperando a que acabe
fundaia tasks runs tienda api limpiar # las últimas veinte y la salida de la última
fundaia tasks edit tienda api limpiar --pause
fundaia tasks rm tienda api limpiar
- El horario es cron de cinco campos, en UTC, el mismo que los workflows:
*/5 * * * *cada cinco minutos,0 3 * * *cada día a las 03:00 UTC,0 9 * * 1los lunes. La pestaña lo escribe en palabras y ofrece los más habituales; la lista dice cuándo toca la siguiente. - Un servicio dormido se despierta para su tarea, y cada ejecución cuenta como una visita: una tarea cada cinco minutos no le deja dormirse.
- Cada ejecución dura 15 minutos como mucho. Se guardan las veinte últimas, con el código de salida y el final de lo que imprimió (16 KB), los secretos tapados.
- Una tarea no se solapa consigo misma: si a su hora sigue en marcha la anterior, esa vuelta se apunta como saltada.
- Si la plataforma estuvo parada a la hora de una tarea, la ejecuta al volver solo si llega con menos de una hora de retraso. Al cambiar el horario o reanudarla no se ejecuta nada atrasado.
- Una ejecución fallida llega a los avisos del proyecto como Tarea programada fallida. Crear, cambiar, quitar o ejecutar a mano una tarea queda en el historial; las ejecuciones programadas, no.
Con MCP, list_scheduled_tasks, create_scheduled_task, update_scheduled_task, delete_scheduled_task y run_scheduled_task (que espera hasta un minuto a que termine). Las que crean, cambian o ejecutan una tarea se anotan como destructivas, como run_command.
Renombrar y borrar
Renombrar cambia el contrato del servicio: la plataforma reescribe las referencias ${{ Nombre.CLAVE }} y las direcciones internas que lo nombran, y vuelve a desplegar el servicio y a quienes lo usan. Su dominio no cambia.
Borrar exige repetir el nombre y conserva sus volúmenes: los datos siguen en disco hasta que alguien los borre a propósito. Un servicio del que otro lee variables no se puede borrar hasta desconectarlos.
El nombre es del proyecto, así que renombrar y borrar alcanzan al servicio en todos sus entornos. Para quitar solo una copia, se elimina su entorno.
fundaia rename tienda api backend
fundaia delete tienda backend --confirm backend
Duplicar un servicio
Duplicar… (clic derecho sobre el servicio en el lienzo, o Ajustes → Duplicar) crea otro servicio en el mismo entorno, con otro nombre, a partir del que ya tienes: para probar una rama con la misma base de datos, levantar un segundo worker o tener una réplica con otra configuración.
- Lo que se lleva: el mismo origen (repositorio, rama y directorio; imagen; o componente del catálogo), los mismos ajustes, el Containerfile si lo editaste y sus variables. Los secretos se cifran de nuevo para la copia y las referencias a otros servicios se mantienen, así que lee de la misma base de datos y el lienzo dibuja su flecha.
- Lo que no: los datos (sus volúmenes son nuevos y vacíos, en las mismas rutas y con el mismo tamaño) y los dominios (si la quieres pública, genérale uno en su pestaña Red). Un componente del catálogo duplicado nace con contraseñas nuevas, como al copiarlo a otro entorno.
- Se despliega al crearla, salvo que digas lo contrario (
--no-deploy). Queda debajo del original en el lienzo y en el historial.
fundaia duplicate tienda api api-canary
fundaia duplicate tienda worker worker-2 --no-deploy --env staging
Con MCP, duplicate_service. Para llevar un servicio a otro entorno se copia, no se duplica: fundaia env copy o copy_service_to_environment (Entornos).
Servicios adoptados
Un servicio adoptado es un contenedor que ya corría en la máquina antes que la plataforma. Se observa, pero no se gobierna: la plataforma informa de él y nunca lo para, lo recrea ni lo intenta levantar.