- Por que Railway e não Render, Fly ou VPS?
- O que o Railway faz nos bastidores
- Antes de começar: o que seu projeto precisa ter
- Configurando o package.json para produção
- Preparando o app para o ambiente de nuvem
- Fazendo o deploy: GitHub ou CLI
- Variáveis de ambiente do jeito certo
- Adicionando banco de dados PostgreSQL
- Erros comuns e como resolver
- FAQ
- Próximos passos
Configurar VPS do zero para um projeto pessoal não faz sentido. Você vai gastar horas com nginx, certificado SSL, PM2 e ainda vai esquecer de renovar o cert em 90 dias. O Railway tira esse peso — e em menos de 10 minutos você tem uma URL pública rodando, com deploy automático a cada git push.
Por que Railway e não Render, Fly ou VPS?
O Heroku era a referência para isso. Encerrou os planos gratuitos em outubro de 2022. O mercado ficou com um buraco, e várias plataformas tentaram preenchê-lo — cada uma com trade-offs diferentes.
| Plataforma | Configuração | Banco incluído | Cold start | Free tier |
|---|---|---|---|---|
| Railway | Mínima | ✅ PostgreSQL, MySQL, Redis | Não | $5 USD/mês em créditos |
| Render | Baixa | ✅ PostgreSQL | Sim (plano gratuito) | Sim, com limitações |
| Fly.io | Média-alta | ✅ via extensão | Não | Sim (3 VMs compartilhadas) |
| VPS (EC2, DO) | Alta | ❌ provisionamento separado | Não | Não |
O diferencial do Railway não é só a ausência de cold start. É a DX: você conecta o GitHub, define as variáveis de ambiente na UI e o banco de dados é provisionado com um clique — sem copiar connection string, sem configurar segurança de rede, sem nada.
O plano Hobby dá $5 de crédito por mês sem precisar de cartão de crédito. Um app Node.js com PostgreSQL consome em torno de $3 a $4/mês nesse plano. Convertendo: com o dólar a R$5,80, estamos falando de R$17 a R$23/mês para manter um projeto pessoal no ar. Assine o Pro ($20/mês) só quando precisar de mais recursos ou de backups automáticos com SLA.
O que o Railway faz nos bastidores
Vale entender o mecanismo antes de sair clicando. O Railway usa o Nixpacks — um buildpack open source desenvolvido por eles — para detectar automaticamente o tipo de projeto e montar uma imagem Docker sem que você precise escrever um Dockerfile.
O fluxo é:
- Você faz
git push(ou usa a CLI comrailway up) - O Nixpacks analisa seu repositório: detecta Node.js pelo
package.json, identifica a versão do runtime pelo campoengines, instala as dependências comnpm install - Uma imagem Docker é construída e enviada ao registry interno
- O container sobe em um worker isolado. O Railway injeta automaticamente
PORT,RAILWAY_ENVIRONMENTe as variáveis que você configurou - Um proxy reverso roteia as requisições da URL pública para o container
Você não precisa escrever docker build ou configurar nada. Mas entender isso ajuda a debugar quando o build quebra — e vai quebrar alguma hora.
Antes de começar: o que seu projeto precisa ter
Não tem nada exótico aqui, mas checklist rápida antes de continuar:
- Node.js 18 ou superior (o Railway suporta até a versão LTS mais recente)
- Repositório no GitHub com pelo menos um commit (o Railway lê diretamente do repo)
- Conta no railway.app — entre com GitHub e autoriza em 30 segundos
- O campo
"start"definido nopackage.json(se não tiver, o Railway não sabe como iniciar seu app)
A CLI do Railway é opcional para o primeiro deploy, mas útil para debug e para rodar comandos remotos como migrations. Pode instalar depois se precisar.
Configurando o package.json para produção
Esse arquivo é o contrato entre o seu projeto e o Railway. Dois campos são críticos e ignorados pela maioria dos tutoriais:
{
"name": "meu-api",
"version": "1.0.0",
"engines": {
"node": ">=20.0.0"
},
"scripts": {
"build": "tsc --project tsconfig.json",
"start": "node dist/index.js"
}
}
O campo engines não é sugestão — sem ele, o Nixpacks pode subir seu app com uma versão de Node defasada. Já vi isso acontecer com optional chaining (?.) e nullish coalescing (??) quebrando silenciosamente em produção porque o ambiente estava rodando Node 14. O CI passava, o build local passava, só a produção explodia.
Se você usa TypeScript, o build roda antes do start. Confirme que o outDir no tsconfig.json bate com o caminho que você colocou no start:
{
"compilerOptions": {
"outDir": "dist",
"rootDir": "src",
"target": "ES2020",
"module": "commonjs"
}
}
⚠️ Atenção: Nunca commite
node_modulesnem.env. O Railway instala as dependências sozinho. O.envvai para a aba de variáveis de ambiente da plataforma — nunca para o repositório.
Preparando o app para o ambiente de nuvem
Porta dinâmica é obrigatória
Esse é o erro número um de quem faz o primeiro deploy e acha que o Railway está "com problema":
const PORT = 3000;
app.listen(PORT);
O Railway injeta a variável PORT dinamicamente no container. Se você hardcodar a porta, o proxy interno não consegue rotear as requisições — o deploy sobe, mas a URL retorna 502 e você fica sem entender o que aconteceu.
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Servidor rodando na porta ${PORT}`);
});
O || 3000 é o fallback para quando você roda localmente sem a variável definida.
Configurando CORS para produção
Outro erro frequente em APIs: deixar o CORS liberado para * em desenvolvimento e esquecer de restringir em produção.
import cors from 'cors';
const allowedOrigins = process.env.NODE_ENV === 'production'
? ['https://meu-frontend.vercel.app']
: ['http://localhost:3000'];
app.use(cors({ origin: allowedOrigins }));
Use a variável NODE_ENV que o Railway injeta automaticamente no ambiente de produção.
Rota de health check
Não é obrigatório, mas o Railway usa esse endpoint para confirmar que o app está responsivo após o deploy. Também é útil para monitoramento externo:
app.get('/health', (_req, res) => {
res.status(200).json({
status: 'ok',
uptime: process.uptime(),
timestamp: new Date().toISOString(),
});
});
Fazendo o deploy: GitHub ou CLI
Via GitHub (para a maioria dos projetos)
É o fluxo mais prático. Depois de conectar, cada git push na branch principal dispara um deploy automático.
- Entre em railway.app e faça login com GitHub
- Clique em New Project → Deploy from GitHub repo
- Autorize o Railway a acessar seus repositórios
- Selecione o repo e clique em Deploy Now
O Railway detecta que é Node.js, executa npm install e depois npm start (ou npm run build && npm start se tiver o script de build). Em 2 a 3 minutos você tem uma URL no formato https://seu-app.up.railway.app.
💡 Dica: Acompanhe o progresso do build em tempo real na aba Deployments. Os logs mostram cada etapa do Nixpacks — se algo quebrar, o erro aparece ali com o stack trace completo.
Via CLI (para automação e monorepos)
npm install -g @railway/cli
railway login
railway init
railway up
O railway up faz o deploy do diretório atual. É útil quando o projeto ainda não está no GitHub ou quando você quer testar um branch sem commitar. Para monorepos, use a flag --service para apontar para o serviço correto.
Variáveis de ambiente do jeito certo
Na aba Variables do serviço, você adiciona as variáveis no formato CHAVE=VALOR. O Railway injeta elas no container em tempo de execução — sem precisar de .env, sem precisar de biblioteca como dotenv em produção.
const config = {
jwtSecret: process.env.JWT_SECRET,
dbUrl: process.env.DATABASE_URL,
nodeEnv: process.env.NODE_ENV || 'development',
};
if (!config.jwtSecret) {
console.error('JWT_SECRET não definida. Encerrando.');
process.exit(1);
}
Validar variáveis obrigatórias na inicialização do app é uma boa prática que evita bugs silenciosos em produção — o app falha imediatamente com uma mensagem clara, em vez de quebrar em runtime quando a variável é usada.
Para ambientes diferentes, o Railway usa o conceito de Environments. Você cria um staging e um production, cada um com suas próprias variáveis e com deploys independentes. Sem branches mágicas, sem múltiplos repos.
railway variables set JWT_SECRET=seu-segredo-longo-e-aleatorio
railway variables set NODE_ENV=production
Adicionando banco de dados PostgreSQL
Um dos pontos fortes da plataforma. O banco é provisionado dentro do mesmo projeto, e a connection string é injetada automaticamente no serviço Node — sem configuração manual.
- No projeto, clique em New → Database → Add PostgreSQL
- Aguarde o provisionamento (menos de 1 minuto)
- A variável
DATABASE_URLaparece automaticamente nas variáveis do serviço Node
Você não precisa copiar nada. O Railway faz a linkagem entre os serviços internamente.
Com Prisma, é direto:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
Para rodar migrations após o deploy, use o CLI do Railway para executar comandos remotamente:
railway run npx prisma migrate deploy
Isso garante que as migrations rodem no mesmo ambiente de produção, com as mesmas variáveis de ambiente do container.
⚠️ Atenção: O plano Hobby não inclui backups automáticos do banco de dados. Para dados de usuário em produção real, configure backups manuais via
pg_dumpagendado ou use um banco externo com SLA (Supabase, Neon, PlanetScale). O Railway é ótimo para começar, mas não para dados críticos sem estratégia de backup.
Erros comuns e como resolver
Deploy sobe mas a URL retorna 502 ou não responde
Quase sempre é a porta hardcoded. Confirme que você está usando process.env.PORT. Se estiver usando, verifique nos logs se o app está falhando silenciosamente na inicialização — uma exceção não capturada no boot faz o container morrer antes de receber conexões.
npm install falha com erro de dependência nativa
Algumas bibliotecas com bindings nativos (como bcrypt, canvas, sharp) precisam de ferramentas de compilação que podem não estar disponíveis no container padrão do Railway. Para bcrypt, use bcryptjs — é implementação pura em JavaScript, sem binding nativo, com a mesma API. Para sharp, adicione um Dockerfile customizado especificando as dependências de sistema necessárias.
Variável de ambiente aparece como undefined em runtime
A variável existe na aba Variables, mas foi adicionada depois do último deploy. O Railway não reinicia o container automaticamente quando você altera variáveis — você precisa acionar um redeploy manualmente ou aguardar o próximo git push.
App reinicia em loop (crash loop)
railway logs --tail
Use o comando acima para ver os logs em tempo real. O problema mais comum é a conexão com o banco de dados falhando no startup — o app tenta conectar antes do banco estar pronto, ou a DATABASE_URL está incorreta. Adicione um retry com backoff exponencial na conexão inicial para casos onde o banco demora a responder após o deploy.
Build do TypeScript não encontra os arquivos compilados
O Railway executa npm run build antes do npm start. Se o outDir do tsconfig.json for dist mas o seu start apontar para build/index.js, o processo vai falhar. Mantenha consistência:
{
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
}
}
Nixpacks não detecta o projeto como Node.js
Acontece raramente, mas o Nixpacks pode confundir o tipo de projeto em monorepos. Crie um arquivo nixpacks.toml na raiz para forçar a configuração:
[phases.setup]
nixPkgs = ["nodejs_20"]
[start]
cmd = "node dist/index.js"
FAQ
O Railway é realmente gratuito? Tem limite escondido? O plano Hobby dá $5 de crédito por mês sem cartão de crédito. Um app Node.js sem banco consome em torno de $1 a $2/mês. Com PostgreSQL incluso, vai para $3 a $4/mês. O limite é financeiro, não técnico — quando o crédito acaba no mês, os serviços são suspensos até o próximo ciclo. Não tem surpresa de cobrança.
Posso usar domínio próprio? Sim. Na aba Settings do serviço, tem a opção de adicionar um domínio customizado. Você aponta um registro CNAME no seu provedor de DNS para o endereço que o Railway fornece, e o SSL via Let's Encrypt é configurado automaticamente em alguns minutos.
O app hiberna como no Render gratuito? Não. No Railway, o app fica em execução contínua enquanto houver crédito disponível — sem cold start, sem tempo de hibernação. Esse é um dos principais diferenciais em relação ao Render no plano gratuito.
Como faço rollback se o deploy quebrar a produção? Na aba Deployments do serviço, você vê o histórico completo. Clique em qualquer deploy anterior e selecione Redeploy — o Railway restaura aquele snapshot do container em menos de 2 minutos. Sem precisar reverter o Git, sem stress.
Funciona com monorepo? Sim. Nas configurações do serviço, defina o Root Directory apontando para a pasta do seu app dentro do monorepo. O Railway vai usar aquela pasta como raiz para o Nixpacks, ignorando o restante do repositório.
Próximos passos
Com o app no ar, o caminho natural é blindar o ambiente e preparar para crescer:
- Domínio próprio com SSL — configure o CNAME nas settings do serviço. Leva 5 minutos
- Monitoramento externo — o UptimeRobot monitora seu
/healthde 5 em 5 minutos e manda email se cair, no plano gratuito - Alertas de uso no Railway — configure na aba Billing para receber aviso quando se aproximar do limite de crédito
- Migrations automatizadas no CI — adicione
railway run npx prisma migrate deployno GitHub Actions, antes do step de deploy, para garantir que o schema está sempre atualizado - Logs estruturados — em vez de
console.log, usepinoouwinstoncom output JSON. O Railway captura stdout e você consegue filtrar por nível de log na UI
O Railway não vai ser a solução definitiva quando o app escalar para centenas de milhares de requisições. Mas para sair do localhost e ter algo real na internet, sem perder dois dias configurando infra, é a melhor opção disponível hoje.