Resposta curta#
Quase sempre é um problema de paridade de ambientes: sua máquina e o servidor de build não são idênticos. As causas mais frequentes são variáveis de ambiente que existem no seu computador, mas não no servidor, ferramentas instaladas globalmente ou como devDependencies, versões diferentes de Node ou Python, arquivos bloqueados pelo .gitignore e a diferenciação de maiúsculas e minúsculas do Linux: macOS e Windows perdoam erros de maiúsculas nos imports; o servidor normalmente não.
A solução é reproduzir localmente o ambiente de build de produção e eliminar as suposições implícitas. Abaixo estão as dez causas, como diagnosticar cada uma e um checklist para percorrê-las em ordem.
Documentação: The Twelve-Factor App · Dev/prod parity ↗ · npm · npm ci ↗
A causa raiz: “na minha máquina funciona”#
O build que passa no seu notebook e falha no servidor é um dos problemas mais antigos do desenvolvimento. O motivo de fundo é sempre o mesmo: seu ambiente local e o de build não são iguais. Sua máquina acumula configurações, variáveis exportadas no shell, dependências globais e caminhos que você deu como certos; o servidor começa do zero, a partir do repositório e nada mais.
Diagnosticar é um processo de eliminar diferenças uma a uma. Estas dez explicam a grande maioria dos casos.
1. Variáveis de ambiente que só existem na sua máquina#
Você tem uma variável no seu .env local ou exportada no shell, e o código a usa durante o build. No servidor ela não está definida, e o build falha ou gera um artefato quebrado.
Como diagnosticar: procure toda leitura de process.env (Node) ou os.environ (Python) que aconteça em tempo de build, incluindo arquivos de configuração do bundler.
Como resolver: declare explicitamente cada variável na configuração do ambiente de deploy. Nunca suba o .env para o repositório; documente as variáveis obrigatórias num .env.example versionado.
# .env.example (este SIM é versionado; o .env real não)
DATABASE_URL=
API_KEY=
NODE_ENV=productionPara se aprofundar: Variáveis de ambiente e DATABASE_URL em produção: guia completo
Documentação: The Twelve-Factor App · Dev/prod parity ↗
2. NODE_ENV=production muda o comportamento#
Muitos apps e ferramentas de Node se comportam de forma diferente conforme o NODE_ENV. Um bug pode aparecer só quando o valor é production, por exemplo ao minificar ou otimizar assets, e nunca se manifestar em desenvolvimento. Além disso, o próprio npm muda de comportamento: com NODE_ENV=production, por padrão ele ignora as devDependencies na instalação.
Como resolver: reproduza o bug localmente forçando o mesmo valor antes de compilar, com NODE_ENV=production npm run build. Se falhar do mesmo jeito que no servidor, você já isolou o caso na sua máquina.
Documentação: Node.js · Development vs production ↗ · npm · config: omit / include ↗
3. A ferramenta de build está em devDependencies#
Seu build precisa de um pacote, como um bundler ou o compilador do TypeScript, mas ele está em devDependencies. Se o servidor instala em modo produção (com NODE_ENV=production ou --omit=dev), esse pacote nunca chega e o build falha com command not found ou module not found.
Como resolver: há dois caminhos válidos. O mais limpo é instalar também as devDependencies na etapa de build e deixá-las de fora só na imagem final. Se a sua plataforma não deixa você controlar a instalação, mova para dependencies o que o build precisa.
# Opção A: o servidor instala também as devDependencies para o build
npm ci --include=dev
npm run build
# Opção B: a ferramenta de build passa para dependencies
npm install --save typescriptDocumentação: npm · package.json devDependencies ↗ · npm · config: omit / include ↗ · npm · npm ci ↗
4. Maiúsculas e minúsculas: o Linux não perdoa#
É o clássico silencioso. macOS e Windows usam por padrão sistemas de arquivos que não diferenciam maiúsculas de minúsculas, então import Button from './components/button' funciona mesmo que o arquivo se chame Button.tsx. O servidor de build costuma rodar Linux, que diferencia, e o mesmo import falha com module not found.
Existe uma variante mais traiçoeira: você renomeou button.tsx para Button.tsx no seu Mac, mas o Git não registrou a mudança porque core.ignoreCase está ativo nesse sistema. No seu disco o arquivo tem um nome, e no repositório, outro.
# Qual é o nome real do arquivo no repositório?
git ls-files | grep -i 'components/button'
# Renomear mudando só maiúsculas/minúsculas (no macOS/Windows é preciso usar git mv)
git mv src/components/button.tsx src/components/Button.tsxPara prevenir: corrija cada import para que bata caractere por caractere com o arquivo, ative forceConsistentCasingInFileNames no TypeScript e rode o build no CI sobre Linux, onde o erro aparece antes de chegar ao deploy.
Documentação: Git · core.ignoreCase ↗ · TypeScript · forceConsistentCasingInFileNames ↗ · Git · git check-ignore ↗
5. Versão diferente de Node, Python ou do runtime#
Você roda uma versão localmente e o servidor usa outra por padrão. Uma API que existe na sua versão pode não existir na do build, ou vice-versa, e uma dependência pode exigir uma versão mínima.
Como resolver: fixe a versão no projeto para que sua máquina, o CI e o servidor leiam a mesma.
# .nvmrc (lido pelo nvm e pelo actions/setup-node)
22
// package.json
{
"engines": { "node": ">=22 <23" }
}
# .python-version (pyenv e várias plataformas)
3.12Confira sua versão local com node --version ou python --version e confirme na documentação da sua plataforma qual desses arquivos ela lê.
Para se aprofundar: Dockerfile: deploys consistentes em qualquer nuvem
Documentação: npm · package.json engines ↗ · nvm · .nvmrc ↗ · GitHub · actions/setup-node ↗
6. Um arquivo necessário está bloqueado pelo .gitignore#
O arquivo existe no seu disco, mas uma regra ampla demais do .gitignore impede que ele entre no repositório. Como o build parte do repositório, no servidor esse arquivo simplesmente não existe.
Exemplo clássico: uma regra lib, pensada para ignorar a pasta de saída da raiz, ignora também qualquer pasta chamada lib em qualquer nível, como src/lib com código de que seu app precisa. Um padrão sem barra casa em qualquer nível; com barra no início, só no diretório do .gitignore.
# Antes: ignora qualquer pasta "lib" em qualquer nível
lib
# Depois: ignora só /lib na raiz do projeto
/lib
# Qual regra está ignorando este arquivo?
git check-ignore -v src/lib/format.tsConfirme o que está realmente versionado com git ls-files.
Documentação: Git · gitignore ↗ · Git · git check-ignore ↗
7. Lockfile desatualizado ou fora do commit#
Se o seu package-lock.json, yarn.lock ou poetry.lock não está no repositório ou está dessincronizado do package.json, o servidor pode resolver versões diferentes das suas e quebrar o build.
Como resolver: versione sempre o lockfile e instale com o comando que respeita exatamente o lock.
npm ci # instala exatamente o que está no lockfile e falha se não bater com o package.json
# em vez de:
npm install # pode resolver versões novas e reescrever o lockfileDocumentação: npm · npm ci ↗ · npm · package-lock.json ↗
8. Dependências nativas ou ferramentas de compilação ausentes#
Alguns pacotes compilam código nativo (C ou C++) durante a instalação e precisam de ferramentas como gcc, make ou python, que sua máquina tem e a imagem do servidor não. Outros baixam binários pré-compilados para um sistema operacional e uma arquitetura específicos: o binário do seu Mac com Apple Silicon não serve num servidor Linux x86_64.
Como resolver: leia os logs do build para identificar o pacote que falha, garanta que a imagem base inclua as ferramentas de compilação necessárias ou use uma versão pré-compilada para a plataforma do servidor, e nunca copie o node_modules da sua máquina: instale sempre no servidor.
Documentação: npm · npm ci ↗
9. O build fica sem memória#
Seu notebook costuma ter bem mais RAM que o ambiente de build. Builds grandes, principalmente de frontend, podem morrer com um erro de memória que você nunca veria localmente.
Como diagnosticar: procure nos logs por JavaScript heap out of memory ou por um processo que termina sem explicação, típico de um OOM kill.
Como resolver: reduza o consumo do build (divida bundles, limite a concorrência) ou aumente o limite do heap do Node com NODE_OPTIONS=--max-old-space-size=4096 npm run build. O valor é em MiB e precisa caber na memória real do ambiente de build; se passar disso, o sistema mata o processo de qualquer jeito.
Documentação: Node.js · --max-old-space-size ↗
10. Variáveis de build-time que não chegam ao build#
É diferente do item 1: algumas plataformas injetam as variáveis só em runtime, quando o app está rodando, e não durante o build. Se a compilação precisa de uma variável, por exemplo a URL pública de uma API num frontend, e ela só existe em runtime, o build usa um valor vazio sem avisar.
No Next.js, as variáveis NEXT_PUBLIC_ são embutidas no código do navegador na compilação; no Vite acontece o mesmo com as VITE_. Alterá-las depois do build não tem efeito: é preciso compilar de novo. Confirme na documentação da sua plataforma quais variáveis estão disponíveis em build-time e declare ali as que a compilação precisa.
Documentação: Next.js · Environment variables ↗ · Vite · Env variables and modes ↗
Reproduza o build do zero e no CI#
A regra prática: se você não consegue reproduzir o build num diretório novo, sem node_modules anterior, sem variáveis exportadas no shell e com a mesma versão de runtime, o problema não é o servidor; é a sua configuração local.
# Reproduza o build como o servidor faria: clone limpo, sem node_modules nem .env
git clone --depth 1 <url-do-repo> /caminho/limpo && cd /caminho/limpo
node --version # compare com a do servidor
env -i PATH="$PATH" HOME="$HOME" NODE_ENV=production \
sh -c 'npm ci --include=dev && npm run build' Melhor ainda: rode o mesmo build no CI sobre Linux a cada push. Assim os erros de maiúsculas, versões e lockfile aparecem no pull request, e não no deploy.
# .github/workflows/build.yml
name: build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npm run build
env:
NODE_ENV: productionDocumentação: GitHub · actions/setup-node ↗ · npm · npm ci ↗
Checklist de diagnóstico rápido#
Quando um build passar local e falhar no deploy, percorra esta lista nesta ordem:
- Você reproduz a falha com NODE_ENV=production npm run build num clone limpo?
- Todas as variáveis de que o build precisa estão declaradas no servidor, na fase certa?
- As ferramentas de build são instaladas no servidor (dependencies ou --include=dev)?
- Os imports batem exatamente, em maiúsculas e minúsculas, com os nomes de arquivo do repositório?
- A versão de Node ou Python está fixada e é igual à local?
- Há arquivos necessários bloqueados pelo .gitignore?
- O lockfile está commitado e você usa npm ci?
- Os logs mencionam memória, ferramentas de compilação ou um pacote nativo?
Se chegar ao fim sem encontrar a causa, o mais provável é uma dependência global implícita ou um script de build diferente do que você usa localmente.
Perguntas frequentes#
Por que meu import funciona no Mac, mas falha no servidor? Porque macOS e Windows ignoram por padrão maiúsculas e minúsculas nos nomes de arquivo, e o Linux não. Um import de ./Button não encontra um arquivo chamado button.tsx no Linux. Corrija o caminho e, se você renomeou o arquivo, faça isso com git mv.
Qual a diferença entre npm install e npm ci num deploy? O npm install pode atualizar versões e modificar o lockfile; o npm ci instala exatamente o que diz o lockfile, apaga o node_modules antes de começar e falha se o lock não bater com o package.json. Para builds reproduzíveis, use npm ci.
Por que o build fica sem memória só no servidor? Porque o ambiente de build costuma ter menos RAM que a sua máquina. Reduza o tamanho do build ou ajuste o limite do heap dentro da memória disponível.
Devo subir meu .env para o repositório para o deploy funcionar? Não. Declare as variáveis na configuração da sua plataforma e versione só um .env.example sem valores.
Feche a lacuna entre local e produção#
Cada build que falha só no servidor é o custo de uma suposição não documentada: uma variável que só existia no seu shell, uma dependência global que você instalou meses atrás ou um import que seu sistema de arquivos perdoou. Paridade de ambientes não é luxo; é a base para deploys previsíveis.
O objetivo não é o servidor ficar parecido com a sua máquina, e sim os dois se comportarem igual a partir da mesma definição explícita: versões fixadas, variáveis declaradas, lockfile versionado e um build que roda no CI.
Documentação: The Twelve-Factor App · Dev/prod parity ↗
Fontes e escopo
Documentação consultada em 25 de setembro de 2026. Os exemplos e critérios de decisão são propostas editoriais; adapte-os ao contrato da sua aplicação e valide-os no seu ambiente de testes autorizado.
- The Twelve-Factor App · Dev/prod parity ↗
- npm · npm ci ↗
- Node.js · Development vs production ↗
- npm · config: omit / include ↗
- npm · package.json devDependencies ↗
- Git · core.ignoreCase ↗
- TypeScript · forceConsistentCasingInFileNames ↗
- Git · git check-ignore ↗
- npm · package.json engines ↗
- nvm · .nvmrc ↗
- GitHub · actions/setup-node ↗
- Git · gitignore ↗
- npm · package-lock.json ↗
- Node.js · --max-old-space-size ↗
- Next.js · Environment variables ↗
- Vite · Env variables and modes ↗
Compare opções de nuvem
Confira preços, limites, condições e fontes de cada opção.
Abrir comparador