Runtime de backend administrado
Una aplicación de backend declarada de esta manera en rebase.json se ejecuta en el runtime administrado:
{ "apps": { "backend": { "type": "backend", "runtime": "managed" } }}Administrado significa que envías un bundle, no una imagen. rebase cloud deploy compila tus colecciones, backend/functions/ y crons en dist-bundle, lo sube y Rebase Cloud lo inicia sobre la imagen publicada rebasepro/server. Nunca escribes un Dockerfile y tampoco puedes cambiar lo que hay en la imagen. Esta página detalla lo que esto permite, para que puedas planificar una funcionalidad antes de construirla.
Los números aquí provienen del runtime y de la plataforma tal como son hoy. Algunos son parámetros que puedes cambiar, y la página indica cuáles. Las pocas cosas que aún no están garantizadas se recopilan al final.
De un vistazo
Sección titulada «De un vistazo»| Pregunta | Respuesta |
|---|---|
| Versión de Node | Node 22, en Debian slim, linux/x64 |
| Paquetes npm | Sí, desde el registro público o uno privado. Se instalan en el bundle cuando despliegas |
Módulos nativos (.node, node-gyp) |
No. rebase cloud deploy rechaza el bundle |
| WebAssembly | Sí |
| Generar un proceso secundario (child process) | Sí, para un binario linux/x64 que incluya un paquete npm. Consulta Binarios |
| Memoria y CPU | Se configuran por proyecto con rebase cloud compute. Consulta Cómputo |
| Instancias | 1 por defecto, siempre activa. Puedes fijar hasta 5, o dejar que el escalador automático suba hasta 16 |
| Tiempo de espera de funciones | 30 segundos, luego 504 FUNCTION_TIMEOUT. Configurable hasta 10 minutos |
| Cuerpo de la solicitud | 10 MiB, luego 413. Configurable, hasta los 64 MiB que acepta el edge |
| Sistema de archivos con permisos de escritura | Sí, pero es temporal y pertenece a una sola instancia |
Estado a nivel de módulo (una caché Map) |
Sobrevive entre solicitudes en una misma instancia. Se pierde en cada despliegue y reinicio, y no se comparte entre instancias |
| Variables de entorno y secretos | rebase cloud env set, aplicadas en el siguiente despliegue |
| Almacenamiento de archivos | Un bucket por proyecto, servido por tu backend en /api/storage/file/… |
| HTTP de salida | Sí, en los puertos 80 y 443 (y 465, 587, 993 para correo). Otros puertos están bloqueados. Una IP de salida fija: 207.175.138.15 |
Código y paquetes
Sección titulada «Código y paquetes»Node y la imagen. La imagen del runtime está construida sobre node:22-slim (Debian), y tu bundle se ejecuta en linux/x64 como un usuario no root. Tu tsconfig y la versión de Node en tu máquina no cambian esto.
Dependencias. rebase cloud deploy instala en el bundle las dependencias que declaran tus archivos package.json, para linux/x64, y las sube con él, de modo que una nueva instancia no tiene nada que instalar. Un bundle que con ellas superaría el límite de subida de 1 GB se sube sin ellas, y el despliegue lo indica: cada instancia las instala entonces al iniciar, con npm ci (o npm install si no hay lockfile). De cómo se instalan se derivan cuatro cosas:
-
Los scripts de instalación nunca se ejecutan. La instalación utiliza
--ignore-scripts. Un paquete que descarga un binario o compila algo enpostinstallse instala sin ello. -
Solo se instalan versiones del registro. Una dependencia
file:,link:,git+…ogithub:hace que la compilación falle, porque el runtime no tiene contra qué resolverla. -
Algunos paquetes provienen de la imagen, no de ti.
@rebasepro/server,@rebasepro/types,@rebasepro/client,@rebasepro/common,@rebasepro/utils,hono,@hono/node-serveryzodse eliminan de las dependencias del bundle y se vinculan las copias propias de la imagen, por lo que tu versión de ellos no aplica. Los demás paquetes@rebasepro/*, como el driver de base de datos, se instalan desde tupackage.jsoncomo cualquier dependencia.typescriptytsxtambién se eliminan; son herramientas de compilación. -
Los paquetes privados necesitan un
.npmrc.rebase buildcopia el.npmrcdel proyecto (ybackend/.npmrc) en el bundle, para que la instalación use la misma configuración de registro que tu máquina. Cada credencial dentro de él debe ser una referencia a una variable de entorno, nunca el token en sí; un token literal hace que la compilación falle, ya que el bundle se sube con tu código:@acme:registry=https://npm.pkg.github.com//npm.pkg.github.com/:_authToken=${NPM_TOKEN}rebase cloud env set NPM_TOKEN=ghp_… --secret
La subida del bundle está limitada a 1 GB, comprimido.
Los módulos nativos son rechazados. Un paquete que incluye un binario compilado .node no se puede cargar en el runtime administrado. rebase build advierte sobre esto, y rebase cloud deploy se detiene antes de subirlo:
This bundle depends on native modules (sharp), which the managed runtime cannot runLa verificación busca paquetes nativos conocidos (sharp, canvas, bcrypt, argon2, sqlite3, better-sqlite3, re2, sodium-native, pg-native, …), la presencia de binding.gyp o gypfile, scripts de instalación que llamen a node-gyp o prebuild, y archivos .node. La guía de despliegue enumera las alternativas habituales. La versión corta: realiza el trabajo en tiempo de compilación, llama a un servicio o despliega con runtime: "custom".
WebAssembly funciona. Un paquete que incluye un archivo .wasm y lo carga desde JavaScript no es un módulo nativo, y Node 22 lo ejecuta. Verificado en la imagen base del runtime con la configuración de seguridad del pod, utilizando esbuild-wasm. Recuerda el límite de memoria si el módulo es grande, como un modelo de voz o de ML.
Binarios y procesos secundarios
Sección titulada «Binarios y procesos secundarios»child_process funciona. La imagen en sí tiene muy poco para ejecutar: node, npm, sh, tar y las herramientas cliente de PostgreSQL (pg_dump, pg_restore). No hay ffmpeg, ni ImageMagick, ni Python ni compilador, y no puedes añadir paquetes del sistema porque no hay Dockerfile y el proceso no es root.
Un binario que un paquete npm incluye dentro de su propio tarball se instala con el paquete y se ejecuta. Debe estar compilado para linux/x64 contra glibc (Debian), y se ejecuta como un usuario sin privilegios y sin capacidades de Linux. Verificado con @esbuild/linux-x64 en la imagen base del runtime. Un binario que un paquete descarga en postinstall nunca llega, porque los scripts de instalación no se ejecutan; paquetes como ffmpeg-static funcionan de esa manera.
Si una funcionalidad necesita un binario, utiliza una API alojada para ese trabajo o traslada el backend a runtime: "custom", donde compilas tu propia imagen.
Cómputo e instancias
Sección titulada «Cómputo e instancias»Cada proyecto se ejecuta en sus propios pods. La memoria y la CPU son por instancia, y las configuras con los parámetros de cómputo:
rebase cloud compute # what the project reserves nowrebase cloud compute set --cpu 500m --memory 1GiUn proyecto que nunca configuró un parámetro se ejecuta como un pod con capacidad de ráfaga (burstable): reserva 100m de CPU y 384Mi de memoria, y puede alcanzar ráfagas de hasta 2 CPU y 2 GiB. Una solicitud configurada mantiene esa proporción entre la reserva y el límite. Ejecuta rebase cloud compute para ver qué tiene tu proyecto.
Instancias:
- Una por defecto, siempre en ejecución.
--replicasfija entre 1 y 5. Nada escala un backend a cero. - El escalado automático es opcional.
--autoscale-maxestablece un tope de hasta 16, y--autoscale-cpu-target(por defecto 70) el uso de CPU al que apunta el escalador automático. - Los despliegues son progresivos. Una nueva instancia se inicia y pasa a estar lista antes de que la anterior se detenga, por lo que durante un breve periodo dos versiones sirven tráfico.
--spot está desactivado por defecto; las instancias spot son más económicas y se reinician sin previo aviso.
Memoria y estado a nivel de módulo
Sección titulada «Memoria y estado a nivel de módulo»Un valor a nivel de módulo, como const cache = new Map() al principio de un archivo de función, vive en el proceso de Node. Sobrevive de una solicitud a la siguiente en la misma instancia. Se pierde cada vez que ese proceso termina:
- en cada despliegue, porque cada despliegue inicia nuevas instancias;
- cuando una variable de entorno cambia y se despliega;
- cuando una instancia se reinicia, se reprograma o (con
--spot) es reclamada.
Con más de una instancia, cada una tiene su propia copia, por lo que dos solicitudes pueden ver cachés diferentes.
Utiliza un Map a nivel de módulo como una caché que puedas permitirte perder: una que haga que la siguiente solicitud sea más rápida pero que no sea la única copia de nada. Guarda cualquier cosa que haya costado producir, como audio generado o una respuesta costosa de una API, en la base de datos o en el almacenamiento. Para clientes que construyes una sola vez, como el SDK de una API, usa lazyResource para que el cliente se construya en su primer uso a partir del entorno de la solicitud. Consulta Configuración y secretos.
Ejecutar más de una instancia enumera el estado que el propio Rebase mantiene por proceso, como los contadores de límites de tasa y los canales de tiempo real.
El sistema de archivos
Sección titulada «El sistema de archivos»El sistema de archivos del contenedor es de lectura y escritura, y /tmp funciona. Pero todo lo que se escribe allí:
- pertenece a una instancia, y las demás instancias no lo ven;
- se elimina cuando la instancia se detiene, a más tardar en el siguiente despliegue;
- comparte un límite de almacenamiento efímero de 6 GiB con el bundle y sus
node_modulesinstalados. Una instancia que lo sobrepase es desalojada.
Úsalo para archivos temporales durante una solicitud. No lo uses como caché entre despliegues ni como almacenamiento para subidas. Por esa razón también STORAGE_TYPE=local está desactivado en producción.
Solicitudes
Sección titulada «Solicitudes»| Límite | Por defecto | Modifícalo con |
|---|---|---|
| Tiempo de espera de funciones | 30 s, luego 504 con código FUNCTION_TIMEOUT |
REBASE_FUNCTIONS_TIMEOUT_MS (0 lo desactiva) |
| Espera en tu instancia, en el edge | 10 minutos entre lecturas o escrituras | Fijo |
Cuerpo de la solicitud, rutas /api/* sin un límite propio |
10 MiB, luego 413 PAYLOAD_TOO_LARGE |
REBASE_MAX_BODY_SIZE, en bytes |
| Cuerpo de la solicitud en el edge | 64 MiB | Fijo |
Un archivo mediante POST /api/storage/upload |
50 MB | STORAGE_MAX_FILE_SIZE |
Llamadas anónimas a /api/functions/* |
3000 por IP cada 15 minutos | rateLimit.anonymousFunctions |
Algunos detalles:
- El tiempo de espera libera al emisor, no al trabajo. A los 30 segundos, el emisor recibe un
504ygetSignal(c)se cancela. Pasa esa señal afetchy a cualquier otra función que la acepte, para que el trabajo también se detenga. Consulta Tiempos de espera y límites de tasa. - El edge espera más tiempo que el runtime. El ingress delante de tus instancias espera hasta 10 minutos, por lo que el límite con el que choca una solicitud es el del runtime, que responde en JSON y cancela la señal. Aumenta
REBASE_FUNCTIONS_TIMEOUT_MShasta ese valor. Para tareas que tarden más, o que deban sobrevivir a un despliegue (una instancia drena conexiones durante 15 segundos), responde de inmediato y realiza el trabajo en un job. - Las subidas de más de 64 MiB necesitan la subida reanudable (TUS), que envía el archivo en partes.
client.storage.putObject()cambia a este método automáticamente a partir de 8 MiB. - Tamaño de respuesta. Ni el runtime ni el edge establecen un límite en el tamaño de respuesta. Aun así, una respuesta grande se mantiene en memoria si tu controlador la construye en una sola pieza, por lo que conviene transmitirla o servirla desde el almacenamiento.
Variables de entorno y secretos
Sección titulada «Variables de entorno y secretos»Configúralas con la CLI o en la consola. No hay ningún campo para ellas en rebase.json.
rebase cloud env set TTS_API_URL=https://api.example.com/v1/speechrebase cloud env set TTS_API_KEY=sk-… --secret # write-only: never shown againrebase cloud env list # names only, never valuesrebase cloud deploy # applies the change- Un cambio se aplica en el siguiente despliegue. Las instancias en ejecución mantienen los valores con los que iniciaron.
setyunsette recuerdan esto (Pending redeploy). - Límites: hasta 100 variables por proyecto, 32 KiB por valor y 256 KiB en total. Los nombres siguen el patrón
[A-Za-z_][A-Za-z0-9_]*. - Algunos nombres están reservados. La plataforma establece por sí misma
DATABASE_URL,DATABASE_DIRECT_URL,DATABASE_READ_URL,JWT_SECRET,REBASE_SERVICE_KEY,PORTyNODE_ENV, y fija cómo se ejecuta el proceso (REBASE_ROLE,TRUSTED_PROXY_HOPS, …). Si intentas configurar uno de ellos, será rechazado y el error los enumerará todos. CORS_ORIGINSparte de tus dominios. La plataforma lo establece con el host del proyecto y sus dominios personalizados verificados. UnCORS_ORIGINSque configures se añade a esa lista, para un frontend servido desde otro lugar:rebase cloud env set CORS_ORIGINS=https://app.example.org.- Las variables de almacenamiento son valores por defecto. La plataforma escribe las variables
STORAGE_*yS3_*para su bucket, y cualquier valor que configures tiene prioridad. - Los nombres de tiempo de compilación se rechazan. Las variables
VITE_,NEXT_PUBLIC_,PUBLIC_yREACT_APP_son para frontends en tiempo de compilación, y el backend nunca las ve a tiempo.--forcealmacena una de todos modos.
En una función, léelas dentro del controlador, nunca al principio del archivo:
import { defineFunction, requireEnv } from "@rebasepro/server/functions";
export default defineFunction((app) => { app.get("/status", async (c) => { const endpoint = requireEnv(c, "TTS_API_URL"); // throws a clear error if unset return c.json({ configured: Boolean(endpoint) }); });});Una lectura de process.env en el ámbito del módulo que resulte vacía provoca que todo el archivo falle al cargarse y que sus rutas respondan 404. Consulta Configuración y secretos.
Almacenamiento para archivos generados
Sección titulada «Almacenamiento para archivos generados»Un proyecto de Cloud obtiene almacenamiento de archivos una vez que lo creas:
rebase cloud storage create # a managed bucket and its credentialsrebase cloud deploy # the running backend gets the credentials at deployO apunta el proyecto a un bucket que ya poseas con rebase cloud storage attach (S3, R2, MinIO, cualquier bucket compatible con S3). Sin ninguno de los dos, las subidas responderán 501 STORAGE_NOT_CONFIGURED. Se declaran más buckets en config/resources.ts; consulta Varios buckets.
Un despliegue con almacenamiento se rechaza hasta que el proyecto defina quién puede leer y escribir archivos. El archivo config/storage.ts del scaffold exporta un hook storageAuthorize para esto. Consulta Autorización por objeto.
Cómo se sirven los archivos. Tu backend sirve cada archivo en GET /api/storage/file/<key>. Lee el objeto desde el bucket y lo transmite, con Content-Type, solicitudes de rango y cabeceras de caché. No hay una URL directa del bucket para compartir.
- Una clave bajo
public/se sirve a cualquiera, sin token, en una URL que nunca cambia. Úsala para archivos que cualquier visitante pueda cargar, como audio generado para un juego público. - Cualquier otra clave es privada. Un cliente solicita a
/api/storage/metadata/<key>una URL de descarga que contiene un token, válido por defecto durante 300 segundos (STORAGE_DOWNLOAD_TOKEN_TTL, hasta una semana).
Desde una función, escribe con rebase.storage (el bucket predeterminado) o rebase.bucket(…) (uno con nombre). Tu hook storageAuthorize sigue decidiendo si la escritura está permitida.
Las transformaciones de imagen (?width=, ?format=) necesitan sharp, que es un módulo nativo y no está en la imagen administrada. En el runtime administrado, una solicitud de transformación recibe la imagen original.
Ejemplo: almacenar en caché audio generado
Sección titulada «Ejemplo: almacenar en caché audio generado»Una función que convierte texto a voz con una API alojada, guarda cada resultado una sola vez y lo sirve desde el almacenamiento en adelante. El Map solo ahorra una consulta al almacenamiento en una instancia activa; el bucket es la verdadera caché, por lo que un despliegue no pierde nada.
import { defineFunction, getSignal, requireEnv } from "@rebasepro/server/functions";
const known = new Map<string, true>(); // per instance, lost on deploy: that is fine
export default defineFunction((app, { rebase }) => { app.get("/voice/:id", async (c) => { const id = c.req.param("id").replace(/[^a-z0-9-]/gi, ""); const key = `public/voices/${id}.mp3`; const url = `/api/storage/file/${key}`;
if (known.has(key)) return c.redirect(url); const storage = rebase.storage; if (!storage) return c.json({ error: "Storage is not configured" }, 501);
const listed = await storage.listObjects(key, { maxResults: 1 }); if (listed.items.some(item => item.fullPath === key)) { known.set(key, true); return c.redirect(url); }
const text = c.req.query("text") ?? ""; const response = await fetch(requireEnv(c, "TTS_API_URL"), { method: "POST", headers: { authorization: `Bearer ${requireEnv(c, "TTS_API_KEY")}`, "content-type": "application/json" }, body: JSON.stringify({ text }), signal: getSignal(c) }); if (!response.ok) return c.json({ error: "Speech service failed" }, 502);
const file = new File([await response.arrayBuffer()], `${id}.mp3`, { type: "audio/mpeg" }); await storage.putObject({ file, key }); known.set(key, true); return c.redirect(url); });});La ruta es pública, como cualquier ruta en backend/functions/ hasta que la protejas. Una real verificaría quién puede crear nuevo audio, de modo que un desconocido no pueda consumir el presupuesto de tu API de voz.
Red de salida
Sección titulada «Red de salida»Las funciones pueden llamar a servicios externos. Las conexiones salientes desde tus instancias solo están permitidas hacia cualquier dirección de internet en estos puertos TCP:
| Puerto | Para |
|---|---|
| 80, 443 | HTTP y HTTPS |
| 465, 587 | Envío de correo mediante SMTP con TLS o STARTTLS |
| 993 | IMAP sobre TLS |
Todo lo demás está bloqueado. Esto incluye el puerto 25, IMAP sin cifrar y bases de datos o cachés en sus propios puertos: no se puede acceder a Redis en el 6379 o MongoDB en el 27017 fuera de Rebase Cloud. La dirección de metadatos de la nube (169.254.169.254) también está bloqueada.
La única excepción es tu propia base de datos. Si el proyecto trae su propio PostgreSQL (BYODB), cada despliegue permite además las conexiones al host de esa base de datos en el puerto de su cadena de conexión, y a nada más en ese puerto. El host se resuelve a sus direcciones IP cuando despliegas, así que si tu proveedor mueve la base de datos a otra dirección, vuelve a desplegar. La base de datos debe aceptar conexiones desde internet: la app se conecta directamente a la cadena de conexión. Los ajustes del bastión SSH solo los usa la prueba de conexión, no la app desplegada.
La plataforma no añade ningún proxy ni límite de tasa saliente; los servicios a los que llamas tienen los suyos propios. Pasa getSignal(c) a fetch para que una llamada se detenga cuando se agote el tiempo de espera de su solicitud.
Todo el tráfico de salida sale desde una única dirección IP fija, 207.175.138.15. Añádela a una lista de permitidos de terceros cuando un servicio te la pida. La dirección es compartida por todos los proyectos de Rebase Cloud, así que una lista de permitidos demuestra que quien llama se ejecuta en Rebase Cloud, no que sea tu proyecto: sigue autenticándote también ante el servicio con una clave.
Inicio y apagado
Sección titulada «Inicio y apagado»- Arranque en frío (cold start). Una nueva instancia descarga y desempaqueta el bundle antes de servir tráfico, por lo que un árbol de dependencias grande hace más lento cada escalado. Un bundle subido sin sus dependencias también las instala, lo que añade de 40 a 60 segundos. La plataforma espera hasta 300 segundos a que una instancia arranque.
- Salud (health). La plataforma comprueba
/livez(el proceso está en ejecución) y/health(puede comunicarse con la base de datos). Ambos endpoints son servidos por el runtime, por lo que no necesitas escribirlos. - Apagado (shutdown). En un despliegue o reinicio, la instancia deja de aceptar nuevas solicitudes, espera 5 segundos a que el tráfico se desvíe y luego recibe
SIGTERM. Las solicitudes en curso tienen hasta 15 segundos para completarse. Las promesas pasadas awaitUntilreciben hasta 5 segundos de ese tiempo. Cualquier promesa que no hayas entregado awaitUntilse descarta. Consulta Trabajo en segundo plano.
Cuándo el modo administrado no es el adecuado
Sección titulada «Cuándo el modo administrado no es el adecuado»Mueve el backend a "runtime": "custom" cuando necesite:
- un módulo nativo o un binario del sistema que ningún paquete npm incluya, como
sharp, ImageMagick o un motor de voz local; - paquetes del sistema o una imagen base diferente;
- una conexión de larga duración a algo fuera de los puertos permitidos.
Un runtime personalizado compila tu propio Dockerfile, y a partir de ese momento la imagen es tuya. rebase cloud deploy --eject realiza el cambio. Consulta Rebase Cloud.
Aún no garantizado
Sección titulada «Aún no garantizado»- Historial de registros (logs).
rebase cloud logs --runtimelee desde las instancias que están en ejecución, yrebase cloud debug logs --previousdesde la última ejecución de una que se haya reiniciado. La salida de una instancia reemplazada por un despliegue desaparece. Escribe cualquier información que necesites conservar en la base de datos o en tu propio servicio de logs. - Escalado a cero no está disponible. Cada backend ejecuta al menos una instancia en todo momento.
Páginas relacionadas
Sección titulada «Páginas relacionadas»- Rebase Cloud: vinculación, despliegue, parámetros de cómputo y lo que la versión beta no incluye
- Funciones personalizadas: escribir las rutas en
backend/functions/ - Runtime y bundles: qué es un bundle y cómo lo inicia el runtime
- Almacenamiento: buckets, endpoints, límites de tamaño y reglas de acceso
- Ejecutar más de una instancia: el estado que Rebase mantiene por proceso