Ir al contenido

Buscar en la documentación

Busca en la documentación de Skiffly

Servicios

Un servicio es una unidad desplegable, construida desde un repositorio de GitHub o descargada como imagen Docker. Esta página cubre builders, comandos de inicio, health checks, recursos, réplicas, tareas cron y App Sleeping.

Orígenes#

OrigenCómo se construyeSe despliega con
Repositorio de GitHub (owner/repo, rama, directorio raíz opcional)Railpack, o el Dockerfile del repositoriocada push a la rama seguida, skiffly up, volver a desplegar
Imagen Docker (ghcr.io/org/app:1.2.3, postgres:17)no se construye, el nodo la descargadespliegue manual, cambio de tag de la imagen

Los repositorios privados necesitan la GitHub App de Skiffly (Configuración → Desarrollador → Conectar GitHub). Los registros privados todavía no son compatibles: haz push a un registro público o construye desde un repositorio.

Nuevo servicio en el lienzo del proyecto → Repositorio de GitHub, Imagen Docker o Plantilla. Para un repositorio, elige la rama. El directorio raíz, el builder, la ruta del Dockerfile y el comando de build están en Configuración → Origen del servicio; por ahora, el puerto se configura a través de la CLI, la API, update-service de MCP o configuración como código.

Desplegar un proyecto docker-compose#

Si el repositorio tiene un docker-compose.yml, la pestaña Repositorio de GitHub muestra sus servicios y ofrece Desplegar los N servicios. Un clic crea todos los servicios en el entorno actual: una image: ya construida se usa tal cual; si no, el servicio se compila desde su build.context con el Dockerfile de compose. Los servicios con ports: reciben un dominio público y el resto queda en la red privada. Las variables se trasladan con sus valores por defecto, el nombre de otro servicio usado como host pasa a ${{<servicio>.SKIFFLY_PRIVATE_DOMAIN}}, el DNS de Docker 127.0.0.11 pasa a ${{SKIFFLY_DNS_RESOLVER}} y claves como ORIGIN o APP_URL apuntan al dominio del servicio público. Los volúmenes se convierten en volúmenes de Skiffly; las bases de datos sin volumen reciben uno en su directorio de datos.

El archivo compose se busca en la raíz y en las carpetas habituales (docker/, deploy/, docker/production/, scripts/), y las variables de ejemplo en .env.example, .env.sample, env.example, example.env y similares. Cada servicio tiene una casilla: los auxiliares de desarrollo y pruebas (mailhog, adminer, e2e…) empiezan desmarcados y los servicios con profiles se omiten, como hace docker compose up. Las bases de datos quedan en la red privada aunque compose publique su puerto. Si el catálogo tiene una plantilla para el proyecto (Umami, Plausible…), el diálogo la sugiere: las plantillas traen volúmenes ajustados y secretos generados.

No se trasladan: tareas únicas (restart: "no", o un servicio que otros esperan con service_completed_successfully), volúmenes compartidos entre servicios o de solo lectura, montajes del código fuente de servicios compilados desde el repositorio, privileged, devices ni redes propias. Tras el despliegue, una nota indica qué revisar: valores tomados de .env.example y variables sin valor. Por ahora, solo repositorios públicos.

Builds#

Railpack (predeterminado) inspecciona el repositorio y produce una imagen para Node, Bun, Python, Go, Rust, PHP, Ruby, Java, Elixir, sitios estáticos y más. Lee los archivos habituales (scripts de package.json, requirements.txt, go.mod, …), así que la mayoría de los proyectos no necesita configuración.

Dockerfile: cuando el repositorio (o el directorio raíz) contiene un Dockerfile, se usa automáticamente. Define otra ruta con dockerfilePath y fuerza un builder con builder: RAILPACK | DOCKERFILE (serviceUpdate, skiffly config, update-service de MCP).

Otras opciones del build: comando de build (reemplaza el paso de build de Railpack) y directorio raíz (monorepos: el build se ejecuta dentro de él).

Rutas de vigilancia (watchPatterns, Configuración → Origen → Rutas de vigilancia, un glob por línea) limitan los despliegues automáticos: un push a la rama seguida despliega el servicio solo cuando al menos un archivo cambiado coincide con un patrón (apps/web/**, packages/shared/**) y con ninguna exclusión ! (!**/*.md). Los patrones son relativos a la raíz del repositorio; una lista vacía significa cada push. Los archivos cambiados vienen del webhook de push, así que un push con más de 20 commits o un force-push sin commits siempre despliega. Las rutas de vigilancia son por entorno y valen desde el siguiente push: guardarlas no redespliega.

Los builds corren en una microVM Kata con BuildKit y caché de capas; la imagen queda en un registro en el nodo. Como máximo corren dos builds a la vez por nodo; un build en cola muestra waiting for a build slot en su log.

Comando de inicio y puerto#

Railpack deduce el comando de inicio (npm start, gunicorn …, el binario compilado). Reemplázalo en Configuración → Despliegue → Comando de inicio del servicio o con startCommand; para una imagen Docker, el comando de inicio reemplaza a CMD.

Si Railpack no encuentra qué ejecutar (sin script start, main ni index.js en un proyecto Node, sin punto de entrada en otros lenguajes) y no hay comando de inicio configurado, la compilación falla en el paso del plan con No start command detected y una sugerencia para tu lenguaje, en lugar de desplegar una imagen que termina al instante. Añade un script de inicio o configura el comando de inicio y vuelve a desplegar.

El servicio debe escuchar en $PORT. Skiffly asigna a PORT el puerto del servicio (port en el servicio, 8080 si no está definido) y enruta hacia él el dominio público y el hostname privado. Los servicios creados desde plantillas ya vienen con el puerto correcto. Si defines tú una variable PORT, tu valor tiene prioridad: algunas imágenes usan PORT para otra cosa, como el upstream al que hace proxy un nginx.

Health checks#

Con una ruta de health check (/healthz), el nodo consulta GET <ruta> en el puerto del servicio: un despliegue nuevo recibe tráfico solo cuando la comprobación pasa, y un contenedor que falla la comprobación tres veces seguidas se reinicia. Sin ruta, el sondeo es una conexión TCP al puerto. El tiempo de espera del health check (healthcheckTimeout, segundos, 300 por defecto) es cuánto puede seguir fallando la comprobación un contenedor que arranca (un sondeo cada 5 s hasta agotar el tiempo) antes de que el despliegue se marque como FAILED; súbelo para migraciones lentas al arrancar. Cambiar la ruta o el tiempo de espera vuelve a desplegar el servicio.

Recursos y réplicas#

Cada réplica recibe un límite de CPU y memoria: 2 vCPU / 2 GiB de forma predeterminada, modificable en Configuración → Recursos dentro de los límites de tu plan. La memoria nunca se sobreasigna; la CPU puede subir hasta el límite. La facturación cuenta el límite reservado mientras la réplica corre.

Las réplicas (numReplicas) ejecutan copias idénticas detrás del mismo dominio; las solicitudes se balancean entre ellas. Los volúmenes no se pueden compartir, así que un servicio con volumen corre con una sola réplica.

skiffly api 'mutation { serviceInstanceUpdate(serviceId: "…", environmentId: "…", input: { numReplicas: 2, cpuMillis: 1000, memoryBytes: 1073741824 }) }'

Política de reinicio#

ON_FAILURE (predeterminada, restartPolicyMaxRetries = 10), ALWAYS o NEVER (Configuración → Despliegue → Política de reinicio). El clúster siempre reinicia los contenedores; la política decide cuándo Skiffly deja de reiniciar:

  • ALWAYS: reiniciar sin límite. Un contenedor en bucle de fallos (tres reinicios seguidos) muestra el despliegue como CRASHED mientras los reinicios continúan.
  • ON_FAILURE: cuando el contenedor ha fallado más de restartPolicyMaxRetries veces, el servicio se escala a cero y el despliegue se marca como CRASHED con stopped after N restarts y las últimas líneas del log. Durante una puesta en marcha, el despliegue anterior sigue sirviendo. Reiniciar o Volver a desplegar lo arranca de nuevo.
  • NEVER: lo mismo, pero tras el primer fallo.

El contador de reinicios es por contenedor y se reinicia cuando el contenedor se mantiene en pie. Cambiar la política vuelve a desplegar el servicio.

Tareas cron#

Define una programación cron (0 3 * * *, UTC) en un servicio y dejará de ser un proceso de larga duración: en cada tick se inicia un contenedor con el comando de inicio y se espera a que termine. Las ejecuciones no se solapan; nextCronRunAt muestra el siguiente tick. Los servicios cron no reciben dominio y no se ponen en reposo.

App Sleeping#

Activa App Sleeping (sleepApplication: true) para servicios HTTP que pasan la mayor parte del tiempo inactivos. Tras unos 10 minutos sin solicitudes a través del dominio público, el servicio se escala a cero y su despliegue muestra SLEEPING; la siguiente solicitud lo despierta (normalmente 2–10 s, quien hace la solicitud espera) y el tráfico se reanuda. Un servicio en reposo no se factura por CPU ni memoria; su volumen sí.

El reposo necesita un dominio público y aplica solo a servicios HTTP sin proxy TCP; los servicios cron y los workers que solo hacen solicitudes salientes no pueden despertarse y se dejan como están. La opción se aplica de inmediato, sin volver a desplegar.

Runtime de aislamiento#

Los servicios corren en gVisor (runtimeClass: gvisor) de forma predeterminada. Algunas imágenes necesitan un kernel completo (bases de datos que usan io_uring, cualquier cosa con multihilo SO_REUSEPORT, FUSE); cambia ese servicio a Kata (runtimeClass: kata), una microVM ligera. Consulta Seguridad.

Eliminar un servicio#

Configuración → Zona de peligro → Eliminar, skiffly service delete o serviceDelete(id). La eliminación quita la carga de trabajo en todos los entornos, los dominios y los volúmenes.