Objetivos de aprendizado
Depois de ler este artigo, você vai conseguir:
- Entender por que um monorepo não sobe como um projeto de pacote único
- Gerar uma pasta autossuficiente para um app com pnpm deploy ou turbo prune
- Automatizar o build e o deploy de cada app a cada push
Um monorepo guarda vários projetos num único repositório: uma API, um site, um bot e o código compartilhado entre eles. Isso é prático enquanto você desenvolve, porque uma única mudança pode mexer numa biblioteca e em todos os apps que a usam. Na hora do deploy, porém, a coisa complica: o servidor quer um app, não a árvore inteira.
O problema central é que o seu app não vive sozinho. Ele importa packages/shared, que só existe dentro do repositório. Se você zipar apps/api sozinho, o import quebra. Se zipar tudo, você envia e instala muito mais do que o app precisa. Fazer deploy de um monorepo é recortar exatamente a fatia certa.
O que muda no deploy de um monorepo?#
Num projeto de pacote único, a pasta que você publica é o próprio repositório. Num monorepo, três coisas mudam:
- Dependências locais. Os pacotes do workspace são ligados com o protocolo
workspace:, que o pnpm resolve para a sua pasta local e nunca para o registro do npm. A pasta publicada precisa contê-los. - Um lockfile só, na raiz. As versões são fixadas para o repositório inteiro, não por app.
- Vários apps, um repositório. Um push pode mudar um app, vários ou nenhum, e normalmente você quer refazer o deploy só dos afetados.
O objetivo é ter uma pasta autossuficiente por app: o código do app, as dependências do workspace já compiladas e o node_modules de produção. Se a ideia de mandar código para um servidor ainda é nova para você, leia antes o que é um deploy.
Como é o repositório?#
Este guia usa um layout comum com workspaces do pnpm:
my-repo/
apps/
api/ # o app que vamos publicar
package.json
src/index.ts
packages/
shared/ # importado pela api
package.json
src/index.ts
package.json
pnpm-workspace.yaml
pnpm-lock.yamlO arquivo do workspace diz ao pnpm onde ficam os pacotes:
packages:
- apps/*
- packages/*E o apps/api/package.json depende do pacote compartilhado pelo protocolo do workspace:
{
"name": "api",
"private": true,
"files": ["dist"],
"scripts": {
"build": "tsc -p .",
"start": "node dist/index.js"
},
"dependencies": {
"shared": "workspace:*"
}
}Como compilar só um app?#
Use a flag --filter do pnpm. Acrescentar ... ao nome seleciona o pacote e tudo de que ele depende, direta ou indiretamente:
pnpm install --frozen-lockfile
pnpm --filter "api..." buildO pnpm compila shared primeiro e api depois, na ordem das dependências. Pacotes que a API nunca importa ficam de fora. Com o Turborepo, o equivalente é turbo run build --filter=api..., que ainda guarda os resultados em cache entre execuções.
Para isso funcionar, shared precisa de um script build próprio e de um campo main ou exports que aponte para a saída compilada. Um pacote do workspace que exporta TypeScript cru costuma funcionar em desenvolvimento, mas pode falhar num runtime Node.js comum.
Como gerar uma pasta pronta para deploy com pnpm deploy?#
O pnpm deploy copia um pacote e as dependências de produção dele para uma pasta de destino, resolvendo os pacotes do workspace no caminho:
pnpm --filter api --prod deploy out/apiO --prod deixa de fora as devDependencies, que é o que você quer depois do build. Por padrão o comando copia todos os arquivos do pacote, mas respeita primeiro o campo files, depois o .npmignore e depois o .gitignore. Se dist está no .gitignore e não há campo files, a saída do build fica para trás, e é por isso que api e shared devem declarar "files": ["dist"]. A pasta de destino precisa estar vazia, senão o pnpm falha com ERR_PNPM_DEPLOY_DIR_NOT_EMPTY, a menos que você passe --force. Desde o pnpm v12.2.0 o comando não exige mais a configuração injectWorkspacePackages. Se o deploy falhar com ERR_PNPM_DEPLOY_AMBIGUOUS_PEER, ativar essa configuração ou fixar a peer dependency com overrides resolve.
Confira o resultado antes de enviar:
cd out/api
node dist/index.jsSe o app sobe a partir dessa pasta sozinha, a pasta é autossuficiente.
Como podar com o Turborepo?#
O turbo prune foi feito para Docker e CI. Ele copia o app, os pacotes do workspace de que ele precisa e um lockfile correspondente:
npx turbo prune api --dockerCom --docker, a saída em out/ vem dividida em duas partes, para a instalação ficar em cache separada das mudanças de código:
out/json/guarda só os arquivospackage.json.out/full/guarda os arquivos de código.- O lockfile podado fica na raiz de
out/.
Um build típico copia out/json/ primeiro, instala as dependências, depois copia out/full/ e compila. Como a camada de instalação só muda quando um package.json muda, builds repetidos continuam rápidos. O mesmo padrão vale se você quiser colocar o app no Docker.
Como publicar só os apps que mudaram?#
Recompilar todos os apps a cada push desperdiça tempo. Duas soluções comuns:
- Filtros de caminho no CI. Um workflow do GitHub Actions pode disparar só quando arquivos em
apps/api/**oupackages/shared/**mudam. - Filtro pelo grafo de tarefas. O Turborepo consegue compilar só o que uma mudança afeta, por exemplo
turbo run build --filter=...[HEAD^1](pacotes alterados desde o commit anterior, mais os que dependem deles). O checkout precisa de histórico do Git suficiente para essa comparação.
Este workflow publica a API quando o código dela ou o pacote compartilhado muda. Ele compila, empacota o app com pnpm deploy e deixa o envio para o seu 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 # lê a versão do "packageManager" no 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
# Coloque aqui o seu comando de deploy, rodando a partir de out/apiLembre-se de listar o lockfile e os pacotes compartilhados em paths. Senão, uma mudança numa biblioteca não refaz o deploy dos apps que a usam. Para o padrão completo com secrets e deploys condicionados, veja deploy automático com GitHub Actions.
Quais são os erros mais comuns?#
- Pacote do workspace faltando. O app quebra com
Cannot find module 'shared'porque o pacote não foi compilado ou não foi copiado. Compile com o filtro...e publique compnpm deploy. - Rodar a partir da raiz do repositório. O comando de início precisa rodar dentro da pasta empacotada, não na raiz, senão os caminhos relativos quebram.
- Dependências de desenvolvimento em produção. Esquecer o
--prodincha o upload e a memória que o app usa. - Lockfile desatualizado. Use
--frozen-lockfileno CI para que uma divergência faça o build falhar em vez de instalar outras versões. - Segredos compartilhados. Mantenha as variáveis de ambiente por app. Um segredo colocado na raiz do repositório vaza fácil para apps que não precisam dele. Veja o que são variáveis de ambiente.
Como saber se o deploy funcionou?#
Confira três níveis: o comando de início funciona dentro da pasta empacotada, o log do build mostra shared e api compilados e o app em execução registra a linha de inicialização sem erro de módulo. Depois do primeiro deploy bem-sucedido, mude uma linha em packages/shared, faça push e confirme que o app pegou a mudança. Isso prova que o grafo de dependências está ligado certo, não só o app.
Como fazer isso na Square Cloud#
Na Square Cloud, você faz o deploy por upload de ZIP no painel, pela CLI, pelo GitHub (com a GitHub Action oficial ou por webhook) ou pela extensão do VS Code. Não existe opção de diretório raiz: seja qual for a forma, trate cada app do monorepo como uma aplicação própria e envie a pasta autossuficiente gerada acima, depois de conferir que o app sobe a partir dela. A plataforma não tem etapa de build e só instala as dependências no start, então rode o build no CI ou no comando de início.
O arquivo de configuração squarecloud.app (nome, arquivo principal, memória, versão) é opcional: o painel o gera no deploy, ou você o escreve à mão na raiz do projeto. As variáveis de ambiente ficam por aplicação no painel, na API ou na CLI, o que combina com a dica de segredos por app. A RAM do plano é um pool compartilhado entre as aplicações, com mínimo de 256 MB para uma aplicação de fundo, como um bot, e 512 MB para uma aplicação web.
Para automatizar, a GitHub Action oficial roda um comando da CLI a cada push, release ou execução manual, e só precisa de uma API key guardada como secret. Comece pela CLI da Square Cloud.
Deploy
Envie pelo GitHub, pela CLI ou em ZIP e publique em segundos
Perguntas frequentes
Preciso fazer deploy do monorepo inteiro?
Não. Você publica um app por vez, e cada app só precisa do próprio código mais os pacotes do workspace que ele importa. Podar o repositório para esse subconjunto deixa o upload pequeno e a instalação rápida.
O que faz o protocolo workspace:?
Ele manda o pnpm resolver a dependência a partir do workspace local, nunca do registro do npm. Como o pacote só existe dentro do seu repositório, a pasta publicada precisa incluí-lo.
Qual a diferença entre pnpm deploy e turbo prune?
O pnpm deploy copia um pacote e as dependências de produção dele para uma pasta de destino. O turbo prune copia o pacote, os pacotes do workspace de que ele precisa e um lockfile correspondente, para você instalar e compilar em outro lugar.
Dá para publicar dois apps do mesmo repositório?
Sim. Rode as etapas de build e empacotamento uma vez por app e envie cada resultado para a própria aplicação no servidor. Filtros de caminho no CI evitam que uma mudança num app refaça o deploy do outro.
Mais em Deploy
Ver todos os 33 artigos- O que são variáveis de ambiente (e por que usar)Variáveis de ambiente são pares chave-valor que o sistema entrega ao processo. Veja como ler no Node.js e Python e por que tokens não ficam no código.
- O que é deploy (e o que acontece quando você faz um)Deploy é o processo de levar o código de uma aplicação para um servidor, prepará-lo e colocá-lo no ar. Veja as etapas, os métodos e como voltar atrás.
- Quanta RAM e CPU minha aplicação precisa?Dimensione RAM e CPU medindo o pico real da aplicação e somando uma folga de 30%. Veja como medir em Node.js, Python e Java e o que fazer quando faltar.
- Deploy automático com GitHub ActionsDeploy automático com GitHub Actions envia seu código para o servidor a cada push. Veja o workflow passo a passo, secrets, testes antes do deploy e erros comuns.