Objetivos de aprendizaje
Después de leer este artículo podrás:
- Entender por qué un monorepo no se despliega como un proyecto de un solo paquete
- Generar una carpeta autosuficiente para una app con pnpm deploy o turbo prune
- Automatizar el build y el deploy de cada app en cada push
Un monorepo guarda varios proyectos en un solo repositorio: una API, un sitio web, un bot y el código compartido entre ellos. Es cómodo mientras desarrollas, porque un solo cambio puede tocar una biblioteca y todas las apps que la usan. Al desplegar, en cambio, se complica: un servidor quiere una app, no el árbol completo.
El problema de fondo es que tu app no se sostiene sola. Importa packages/shared, que solo existe dentro del repositorio. Si comprimes apps/api por separado, el import se rompe. Si comprimes todo, subes e instalas mucho más de lo que la app necesita. Desplegar un monorepo consiste en recortar exactamente la porción correcta.
¿Qué cambia al desplegar un monorepo?#
En un proyecto de un solo paquete, la carpeta que despliegas es el repositorio. En un monorepo cambian tres cosas:
- Dependencias locales. Los paquetes del workspace se enlazan con el protocolo
workspace:, que pnpm resuelve a tu carpeta local y nunca al registro de npm. La carpeta desplegada tiene que contenerlos. - Un solo lockfile en la raíz. Las versiones se fijan para todo el repositorio, no por app.
- Muchas apps, un repositorio. Un push puede cambiar una app, varias o ninguna, y normalmente quieres volver a desplegar solo las afectadas.
El objetivo es una carpeta autosuficiente por app: el código de la app, sus dependencias del workspace ya compiladas y su node_modules de producción. Si la idea de enviar código a un servidor es nueva para ti, lee primero qué es un deploy.
¿Cómo es el repositorio?#
Esta guía usa una estructura común con workspaces de pnpm:
my-repo/
apps/
api/ # la app que vamos a desplegar
package.json
src/index.ts
packages/
shared/ # importado por api
package.json
src/index.ts
package.json
pnpm-workspace.yaml
pnpm-lock.yamlEl archivo del workspace le dice a pnpm dónde están los paquetes:
packages:
- apps/*
- packages/*Y apps/api/package.json depende del paquete compartido mediante el protocolo del workspace:
{
"name": "api",
"private": true,
"files": ["dist"],
"scripts": {
"build": "tsc -p .",
"start": "node dist/index.js"
},
"dependencies": {
"shared": "workspace:*"
}
}¿Cómo compilas solo una app?#
Usa la opción --filter de pnpm. Añadir ... al nombre selecciona el paquete y todo aquello de lo que depende, directa o indirectamente:
pnpm install --frozen-lockfile
pnpm --filter "api..." buildpnpm compila shared primero y api después, en orden de dependencias. Los paquetes que la API nunca importa se omiten. Con Turborepo, el equivalente es turbo run build --filter=api..., que además guarda los resultados en caché entre ejecuciones.
Para que funcione, shared necesita su propio script build y un campo main o exports que apunte a su salida compilada. Un paquete del workspace que exporta TypeScript sin compilar suele funcionar en desarrollo, pero puede fallar en un runtime de Node.js normal.
¿Cómo generas una carpeta desplegable con pnpm deploy?#
pnpm deploy copia un paquete y sus dependencias de producción a un directorio de destino, resolviendo por el camino los paquetes del workspace:
pnpm --filter api --prod deploy out/api--prod omite las devDependencies, que es lo que quieres después del build. Por defecto el comando copia todos los archivos del paquete, pero respeta primero el campo files, luego .npmignore y luego .gitignore. Si dist está en .gitignore y no hay campo files, la salida del build se queda fuera, y por eso tanto api como shared deben declarar "files": ["dist"]. La carpeta de destino tiene que estar vacía; si no, pnpm falla con ERR_PNPM_DEPLOY_DIR_NOT_EMPTY, salvo que pases --force. Desde pnpm v12.2.0 el comando ya no exige el ajuste injectWorkspacePackages. Si el deploy falla con ERR_PNPM_DEPLOY_AMBIGUOUS_PEER, activar ese ajuste o fijar la peer dependency con overrides lo soluciona.
Comprueba el resultado antes de enviarlo:
cd out/api
node dist/index.jsSi la app arranca solo desde esa carpeta, la carpeta es autosuficiente.
¿Cómo podas con Turborepo?#
turbo prune está pensado para Docker y CI. Copia la app, los paquetes del workspace que necesita y un lockfile correspondiente:
npx turbo prune api --dockerCon --docker, la salida en out/ se divide en dos para que la instalación quede en caché aparte de los cambios de código:
out/json/contiene solo los archivospackage.json.out/full/contiene los archivos de código.- El lockfile podado queda en la raíz de
out/.
Un build típico copia primero out/json/, instala las dependencias, luego copia out/full/ y compila. Como la capa de instalación solo cambia cuando cambia un package.json, los builds repetidos siguen siendo rápidos. El mismo patrón sirve si quieres dockerizar la app.
¿Cómo despliegas solo las apps que cambiaron?#
Recompilar todas las apps en cada push es perder tiempo. Dos soluciones habituales:
- Filtros de ruta en CI. Un workflow de GitHub Actions puede dispararse solo cuando cambian archivos en
apps/api/**opackages/shared/**. - Filtrado por el grafo de tareas. Turborepo puede compilar solo lo que afecta un cambio, por ejemplo
turbo run build --filter=...[HEAD^1](paquetes cambiados desde el commit anterior, más los que dependen de ellos). El checkout necesita suficiente historial de Git para esa comparación.
Este workflow despliega la API cuando cambia su código o su paquete compartido. Compila, empaqueta la app con pnpm deploy y deja la subida a tu comando de deploy:
name: Deploy API
on:
push:
branches: [main]
paths:
- "apps/api/**"
- "packages/shared/**"
- "pnpm-lock.yaml"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: pnpm/action-setup@v6 # lee la versión de "packageManager" en package.json
- uses: actions/setup-node@v7
with:
node-version: lts/*
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm --filter "api..." build
- run: pnpm --filter api --prod deploy out/api
# Añade aquí tu comando de despliegue, ejecutado desde out/apiRecuerda incluir el lockfile y los paquetes compartidos en paths. Si no, un cambio en una biblioteca no volverá a desplegar las apps que la usan. Para el patrón completo con secretos y deploys condicionados, consulta deploys automáticos con GitHub Actions.
¿Cuáles son los errores más comunes?#
- Falta un paquete del workspace. La app falla con
Cannot find module 'shared'porque el paquete no se compiló o no se copió. Compila con el filtro...y despliega conpnpm deploy. - Ejecutar desde la raíz del repositorio. El comando de inicio tiene que ejecutarse desde la carpeta empaquetada, no desde la raíz, o las rutas relativas se rompen.
- Dependencias de desarrollo en producción. Olvidar
--prodinfla la subida y la memoria que usa la app. - Lockfile desactualizado. Usa
--frozen-lockfileen CI para que una discrepancia haga fallar el build en lugar de instalar otras versiones. - Secretos compartidos. Mantén las variables de entorno por app. Un secreto puesto en la raíz del repositorio se filtra con facilidad a apps que no lo necesitan. Consulta qué son las variables de entorno.
¿Cómo sabes que el deploy funcionó?#
Comprueba tres niveles: el comando de inicio funciona dentro de la carpeta empaquetada, el log del build muestra shared y api compilados y la app en ejecución registra su línea de arranque sin errores de módulo. Tras el primer deploy correcto, cambia una línea en packages/shared, haz push y confirma que la app recoge el cambio. Eso demuestra que el grafo de dependencias está bien conectado, no solo la app.
Cómo hacerlo en Square Cloud#
En Square Cloud, despliegas subiendo un ZIP en el panel, con la CLI, desde GitHub (con la GitHub Action oficial o un webhook) o con la extensión de VS Code. No hay opción de directorio raíz: elijas la forma que elijas, trata cada app de tu monorepo como una aplicación propia y envía la carpeta autosuficiente que generaste arriba, después de comprobar que la app arranca desde ella. La plataforma no tiene etapa de build y solo instala las dependencias al iniciar, así que ejecuta el build en CI o en el comando de inicio.
El archivo de configuración squarecloud.app (nombre, archivo principal, memoria, versión) es opcional: el panel lo genera al desplegar, o puedes escribirlo a mano en la raíz del proyecto. Las variables de entorno se definen por aplicación en el panel, la API o la CLI, lo que encaja con el consejo de secretos por app. La RAM de tu plan es un pool compartido entre aplicaciones, con un mínimo de 256 MB para una aplicación en segundo plano, como un bot, y 512 MB para una aplicación web.
Para automatizarlo, la GitHub Action oficial ejecuta un comando de la CLI en cada push, release o ejecución manual, y solo necesita una API key guardada como secreto. Empieza por la CLI de Square Cloud.
Deploy
Sube desde GitHub, la CLI o un ZIP y publica en segundos
Preguntas frecuentes
¿Tengo que desplegar todo el monorepo?
No. Despliegas una app a la vez, y cada app solo necesita su propio código más los paquetes del workspace que importa. Podar el repositorio a ese subconjunto mantiene la subida pequeña y la instalación rápida.
¿Qué hace el protocolo workspace:?
Le indica a pnpm que resuelva la dependencia desde el workspace local y nunca desde el registro de npm. Como el paquete solo existe dentro de tu repositorio, la carpeta desplegada tiene que incluirlo.
¿Qué diferencia hay entre pnpm deploy y turbo prune?
pnpm deploy copia un paquete y sus dependencias de producción a una carpeta de destino. turbo prune copia el paquete, los paquetes del workspace que necesita y un lockfile correspondiente, para que instales y compiles en otro lugar.
¿Puedo desplegar dos apps del mismo repositorio?
Sí. Ejecuta los pasos de build y empaquetado una vez por app y envía cada resultado a su propia aplicación en el servidor. Los filtros de ruta en tu CI evitan que un cambio en una app vuelva a desplegar la otra.
Más en Deploy
Ver los 33 artículos- Qué son las variables de entornoLas variables de entorno son pares clave-valor que el sistema operativo entrega a un proceso al iniciar. Aprende a leerlas en Node.js y Python sin exponer tokens.
- Qué es un deploy o despliegueUn deploy o despliegue lleva una versión de tu código a un servidor, la prepara y la pone en marcha. Conoce las etapas, los métodos y cómo hacer rollback.
- ¿Cuánta RAM y CPU necesita mi aplicación?Dimensiona RAM y CPU midiendo el pico real de la aplicación y sumando un margen del 30%. Mira cómo medir en Node.js, Python y Java y qué hacer cuando falte.
- Deploy automático con GitHub ActionsEl deploy automático con GitHub Actions envía tu código al servidor en cada push. Mira el workflow paso a paso, los secrets, las pruebas y los errores comunes.