Docker
Despliega desde un Dockerfile o una imagen ya construida en Skiffly — cuándo se usa el Dockerfile, el contexto de build y `dockerfilePath`, build args y secretos de BuildKit, `PORT`, `CMD` frente al comando de inicio, health checks, usuarios, volúmenes y los runtimes gVisor/Kata.
Hay dos formas de ejecutar una imagen de contenedor en Skiffly:
| Origen | Cómo se construye | Se despliega con |
|---|---|---|
Repositorio con un Dockerfile | Skiffly lo construye con BuildKit en una microVM de Kata y sube la imagen al registro del nodo | cada push, skiffly up, volver a desplegar |
Imagen (ghcr.io/acme/api:1.4, nginx:1.27) | no se construye; el nodo la descarga | skiffly deploy --image, un cambio de tag, volver a desplegar |
Los registros privados todavía no están soportados: sube la imagen a un registro público (paquetes públicos de GHCR, Docker Hub) o construye desde el repositorio.
Cuándo se usa el Dockerfile#
Un Dockerfile en la raíz del repositorio — o en el directorio raíz del servicio — cambia automáticamente el builder de Railpack a Dockerfile. Para forzar uno u otro, define builder: RAILPACK | DOCKERFILE; para usar un archivo con otro nombre o ubicación, define dockerfilePath (docker/Dockerfile.prod, que se resuelve primero dentro del directorio raíz y luego desde la raíz del repositorio). El contexto de build es el directorio raíz, así que las rutas de COPY son relativas a él; un Dockerfile de monorepo que necesite todo el repositorio mantiene el directorio raíz en / y usa dockerfilePath: apps/api/Dockerfile.
skiffly api 'mutation { serviceUpdate(id: "…", input: { builder: DOCKERFILE, dockerfilePath: "docker/Dockerfile.prod" }) { id } }'o en configuración como código: build: { builder: "DOCKERFILE", dockerfilePath: "docker/Dockerfile.prod" }. El panel muestra el builder en Configuración → Build; update-service de MCP acepta los mismos campos. Se respeta .dockerignore.
Variables y secretos en tiempo de build#
Por defecto, el build se ejecuta sin ninguna de tus variables en su entorno. Hay dos mecanismos:
- Build args — enumera los nombres de las variables en una variable del servicio
SKIFFLY_BUILD_ARGS=NODE_ENV,NEXT_PUBLIC_API_URL; esas se pasan como--build-argy deben declararse conARGen el Dockerfile. Los build args quedan en el historial de la imagen, así que no incluyas secretos en esta lista. - Secretos — cada variable del servicio está disponible como secreto de BuildKit con el nombre de la variable:
RUN --mount=type=secret,id=NPM_TOKEN \
NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN) npm ciEl log del build indica qué build args se pasaron ([build] build-args: …). Los builds tienen acceso a internet pero no pueden llegar a la red privada de tu proyecto: nada en el Dockerfile puede comunicarse con tu base de datos.
Puerto#
Skiffly define PORT en el contenedor y enruta el tráfico al puerto del servicio (8080 cuando el servicio no define uno). EXPOSE es solo informativo. Haz que el proceso lea PORT o define el puerto del servicio como el puerto en el que escucha la imagen:
ENV PORT=8080
CMD ["sh", "-c", "./server --port $PORT"] # forma shell para que $PORT se expandaPara una imagen que escucha en un puerto fijo (nginx en el 80, ghost en el 2368): skiffly deploy --image ghost:5 --port 2368, o Configuración → Red → Puerto. Escucha en 0.0.0.0, no en 127.0.0.1.
CMD, ENTRYPOINT y el comando de inicio#
El ENTRYPOINT/CMD de la imagen se ejecutan como de costumbre. Un comando de inicio en el servicio reemplaza el CMD (el ENTRYPOINT se conserva), así que sh -c "npm run migrate && node server.js" funciona con una imagen cuyo entrypoint es docker-entrypoint.sh. No hay fase de release; consulta la sección de migraciones de la página de tu lenguaje, o ejecuta comandos puntuales con skiffly ssh -- <cmd>.
Health check#
El HEALTHCHECK del Dockerfile se ignora. En su lugar, define una ruta del health check en el servicio; sin ella, el nodo solo comprueba que el puerto acepte conexiones TCP. Las imágenes distroless y scratch funcionan (el sondeo no necesita shell, es HTTP/TCP desde el nodo), pero entonces skiffly ssh no tiene nada que ejecutar y un comando de inicio con sh -c falla — conserva sh en la imagen si necesitas cualquiera de los dos.
Usuarios, sistema de archivos y volúmenes#
- Los contenedores se ejecutan con el usuario que declara la imagen; correr como root funciona, pero es mejor no hacerlo (
USER node). El sistema de archivos raíz es escribible y efímero. - Los volúmenes se montan en una ruta (
skiffly volume add --mount-path /data) y pertenecen a root en el primer montaje; un proceso que no es root necesita unchowndel directorio — hazlo en un script de entrypoint, o corre como root en el primer arranque. - Las imágenes que esperan funciones específicas de Docker (
--privileged,/var/run/docker.sock, red del host) no se ejecutan.
Runtime: gVisor o Kata#
Los servicios se ejecutan con gVisor por defecto. Las imágenes que necesitan un kernel completo — bases de datos que dependen de io_uring, FUSE, trucos con SO_REUSEPORT, algunos runtimes con JIT — deberían usar Kata (runtimeClass: kata, Configuración → Runtime). Los síntomas de un runtime equivocado son errores operation not permitted/function not implemented al arrancar. Más detalles en Seguridad.
Ejemplo multi-stage#
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=NPM_TOKEN \
NPM_TOKEN=$(cat /run/secrets/NPM_TOKEN 2>/dev/null || true) npm ci
COPY . .
RUN npm run build
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package.json .
USER node
CMD ["node", "dist/server.js"]La imagen escucha en PORT en dist/server.js; define /healthz como ruta del health check. La caché de capas se conserva por servicio entre builds, así que pon COPY package*.json antes de COPY . ..
Problemas comunes#
| Síntoma | Solución |
|---|---|
| Railpack se ejecuta aunque hay un Dockerfile | el archivo está fuera del directorio raíz del servicio, o builder está fijado en RAILPACK |
failed to solve: … "/app/dist": not found | el contexto de build es el directorio raíz; revisa las rutas de COPY y .dockerignore |
ARG está vacío durante el build | agrega el nombre de la variable a SKIFFLY_BUILD_ARGS |
ImagePullBackOff en el mensaje del despliegue | error de tipeo en el nombre/tag de la imagen, o un registro privado (no soportado) |
FAILED a los 5 minutos | el proceso escucha en un puerto distinto de PORT/el puerto del servicio, o en 127.0.0.1 |
permission denied al escribir en un volumen | el montaje pertenece a root: haz chown en un entrypoint o corre como root |
Errores function not implemented, io_uring_setup | cambia el servicio al runtime Kata |
exec: "sh": not found con un comando de inicio | imagen distroless: define el comando de inicio como el ejecutable mismo, sin sh -c |
Landing: /deploy/docker