O Railway se tornou a plataforma preferida dos desenvolvedores que querem a simplicidade do Heroku com infraestrutura moderna. Para desenvolvedores .NET, é o caminho mais rápido de um repositório no GitHub até uma URL de produção em funcionamento. Este guia percorre um deploy completo e real — a app, o Dockerfile e o railway.json usados aqui vivem em
samples/dotnet-hosting, uma minimal API em .NET 10 que você mesmo pode fazer deploy — incluindo a falha que você verá se sua app não estiver na raiz do repositório, e as duas configurações que a corrigem.
O que Você Precisa
- Um projeto ASP.NET Core no GitHub (qualquer versão suportada — o exemplo usa .NET 10)
- Uma conta no Railway (teste gratuito em railway.app)
- Railway CLI (opcional, mas recomendado)
Configurando o Dockerfile
O Railway pode detectar automaticamente projetos .NET com seu builder Railpack, mas um Dockerfile oferece controle total sobre o processo de build. Este é o que o exemplo usa no deploy:
# Dockerfile — multi-stage: a imagem SDK faz o build, a imagem ASP.NET (menor) executa
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY ["JorgenHoc.DotnetHosting.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY --from=build /app/publish .
EXPOSE 8080
ENTRYPOINT ["dotnet", "JorgenHoc.DotnetHosting.dll"]O Railway injeta a porta de escuta como variável de ambiente PORT em tempo de execução. A tentadora linha de Dockerfile ENV ASPNETCORE_URLS=http://+:${PORT:-8080} NÃO funciona — o Docker resolve ${...} quando a imagem é construída, então 8080 fica fixado e o valor injetado pelo Railway é silenciosamente ignorado. Leia PORT no Program.cs em vez disso.
// Program.cs — ler o PORT que o Railway injeta em tempo de execução
var port = Environment.GetEnvironmentVariable("PORT");
if (!string.IsNullOrEmpty(port))
{
builder.WebHost.UseUrls($"http://+:{port}");
}
// Sem PORT, vale a porta padrão da imagem base aspnet (8080).O Arquivo de Configuração railway.json
O Railway é totalmente configurável pelo dashboard, mas um arquivo de configuração versionado mantém os ajustes junto do código. Este é o deploy/railway.json do exemplo:
{
"$schema": "https://railway.app/railway.schema.json",
"build": {
"builder": "DOCKERFILE",
"dockerfilePath": "Dockerfile"
},
"deploy": {
"healthcheckPath": "/health",
"healthcheckTimeout": 300,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}Não é preciso startCommand — o ENTRYPOINT do Dockerfile já cobre isso. O caminho do health check precisa de um endpoint correspondente na sua app:
// Program.cs
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));O Railway só detecta automaticamente o railway.json na raiz do serviço. O exemplo o mantém em deploy/, então o passo a passo abaixo indica o caminho ao Railway explicitamente — um único campo de configuração.
Fazendo Deploy a Partir do GitHub (Sem CLI)
- Acesse railway.app e faça login (o login com GitHub também cuida da autorização do repositório)
- Clique em New Project → Deploy from GitHub repo
- Selecione seu repositório e o Railway inicia o build imediatamente
Se o seu repositório é uma única app com o Dockerfile na raiz, é isso — em ~2 minutos você tem uma URL ativa e pode pular para a geração do domínio.
Quando a App Não Está na Raiz do Repositório
O exemplo vive em um monorepo (samples/dotnet-hosting dentro de dotnet-samples), e o primeiro build falha exatamente como o seu vai falhar: o Railpack escaneia a raiz do repositório, encontra arquivos de solução mas nenhuma app construível, e desiste.

Dois ajustes corrigem isso. Na aba Settings do serviço, defina Root Directory como a pasta que contém sua app:

E como o railway.json do exemplo fica em uma subpasta deploy/ em vez da raiz do serviço, aponte o Config-as-code para ele:

Faça o redeploy, e o builder muda do Railpack para o Dockerfile. Os logs de deploy mostram a app iniciando, escutando na porta injetada pelo Railway e respondendo com 200 ao health check /health do railway.json antes de o deploy entrar em produção:

Gerando uma URL Pública
Deploy feito não significa público — os serviços do Railway não recebem URL externa por padrão. Em Settings → Networking, clique em Generate Domain (aceite a porta 8080):

O exemplo informa em qual plataforma aterrissou detectando variáveis de ambiente específicas de cada plataforma (RAILWAY_ENVIRONMENT, neste caso):
![Navegador mostrando a resposta JSON de dotnet-samples-production.up.railway.app: service JorgenHoc hosting sample, runtime 10.0.11, platform Railway, listeningOn http://[::]:8080.](/images/deploy-dotnet-railway/deploy-dotnet-railway-deployed-app-response.png)
Railway CLI
A CLI oferece desenvolvimento local, streaming de logs e gerenciamento de ambientes:
# Instalar
npm install -g @railway/cli
# Autenticar
railway login
# Vincular a um projeto existente (a partir do diretório do seu projeto)
railway link
# Ou criar um novo projeto
railway init
# Fazer deploy do diretório atual
railway up
# Acompanhar logs em tempo real
railway logs
# Abrir a app em execução no navegador
railway openFazendo Deploy com a CLI
# Deploy com um único comando
railway up
# Fazer deploy de um serviço específico
railway up --service my-api
# Fazer deploy e acompanhar os logs
railway up && railway logsVariáveis de Ambiente
As variáveis de ambiente do Railway são definidas por serviço e por ambiente (Production, Staging, etc.):
Via Dashboard
No dashboard do Railway: Service → Variables → Add variable
Via CLI
# Definir variáveis
railway variables --set "ASPNETCORE_ENVIRONMENT=Production" --set "JWT_SECRET=your-secret-here"
# Listar todas as variáveis
railway variablesAcessando na Sua App .NET
As variáveis do Railway são simples variáveis de ambiente — integram-se perfeitamente com a configuração do ASP.NET Core:
// Os valores de appsettings.json são sobrescritos por variáveis de ambiente
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
// Isso lê da variável de ambiente ConnectionStrings__DefaultConnection (note o duplo underscore)
// Ou ler diretamente
var jwtSecret = builder.Configuration["JWT_SECRET"]
?? throw new InvalidOperationException("JWT_SECRET not configured");Adicionando um Banco de Dados PostgreSQL
O Railway tem um serviço nativo de Postgres que se conecta automaticamente à sua app:
# Adicionar Postgres ao seu projeto
railway add --database postgres
# O Railway define DATABASE_URL automaticamente no ambiente do seu serviçoInstale o provedor PostgreSQL para EF Core:
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL// Program.cs — ler a DATABASE_URL que o Railway define
var databaseUrl = builder.Configuration["DATABASE_URL"]
?? throw new InvalidOperationException("DATABASE_URL not set");
// Analisar o formato de URL postgres:// do Railway
var databaseUri = new Uri(databaseUrl);
var userInfo = databaseUri.UserInfo.Split(':');
var connectionString = $"Host={databaseUri.Host};Port={databaseUri.Port};" +
$"Database={databaseUri.AbsolutePath.TrimStart('/')};" +
$"Username={userInfo[0]};Password={userInfo[1]};" +
$"SSL Mode=Require;Trust Server Certificate=true";
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(connectionString));Ou use o construtor de string de conexão do Npgsql:
# O Railway também fornece variáveis de conexão individuais:
PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORDvar connectionString = new NpgsqlConnectionStringBuilder
{
Host = builder.Configuration["PGHOST"],
Port = int.Parse(builder.Configuration["PGPORT"] ?? "5432"),
Database = builder.Configuration["PGDATABASE"],
Username = builder.Configuration["PGUSER"],
Password = builder.Configuration["PGPASSWORD"],
SslMode = SslMode.Require
}.ConnectionString;Domínios Personalizados
# Adicionar um domínio personalizado via CLI
railway domain www.yourdomain.com
# O Railway fornece o CNAME para apontar no seu registrador de domínioOu via dashboard: Service → Settings → Networking → Custom Domain (ao lado do botão Generate Domain mostrado antes).
O Railway fornece certificados TLS automaticamente para todos os domínios personalizados.
Ambientes (Staging / Production)
O Railway suporta múltiplos ambientes por projeto:
# Criar um ambiente de staging
railway environment new staging
# Mudar o contexto da CLI para staging (railway up passará a apontar para ele)
railway environment stagingCada ambiente tem seu próprio conjunto de variáveis, bancos de dados e deploys. Seu ambiente de staging pode espelhar a produção com um banco de dados separado.
Preços
O Railway utiliza um modelo de preços baseado em uso:
| Plano | Base Mensal | Computação | Memória |
|---|---|---|---|
| Trial | $5 de crédito (uma vez) | $0,000463/vCPU-minuto | $0,000231/GB-minuto |
| Hobby | $5/mês incluídos | Mesmas tarifas | Mesmas tarifas |
| Pro | $20/mês incluídos | Mesmas tarifas | Mesmas tarifas |
Custos típicos para uma pequena API .NET:
Uma API .NET mínima usando ~0,1 vCPU e 256 MB de RAM continuamente:
- Computação: 0,1 × 43.800 min × $0,000463 ≈ $2,03/mês
- Memória: 0,25 × 43.800 min × $0,000231 ≈ $2,53/mês
- Total: ~$4,56/mês (dentro do crédito de $5 do plano Hobby)
Adicionar Postgres: $0,000231/GB-minuto por armazenamento + computação para a instância do banco.
A maioria das APIs .NET pequenas com um banco de dados Postgres funciona confortavelmente dentro do plano Hobby de $5/mês do Railway. Estime seus custos em railway.app/pricing antes de escalar.
Deploys Automáticos
Por padrão, cada push na sua branch principal aciona um deploy. Configure deploys baseados em branches:
No dashboard: Service → Settings → Deployments:
- Watch Branch:
main(ou qualquer branch) - Root Directory:
/ou um subdiretório se seu projeto .NET não estiver na raiz do repositório
Prós e Contras
Prós
- Zero configuração para o básico — suba o código, obtenha a URL, sem manifestos YAML
- Integração nativa com Git — deploy automático a cada push
- Excelente DX — o dashboard é limpo e intuitivo
- Postgres, Redis, MySQL nativos — um clique para adicionar um banco de dados
- Preços justos — baseados em uso, fáceis de estimar
- Domínios personalizados com TLS automático — incluído em todos os planos
- Ramificação de ambientes — separação staging/produção integrada
Contras
- Sem SLA no plano Hobby — plano Pro necessário para garantias de disponibilidade
- Regiões centradas nos EUA — menos regiões que AWS/Azure (mas em expansão)
- Recursos empresariais limitados — sem VPCs nem rede privada nos planos inferiores
- Minutos de build podem se acumular — soluções .NET grandes com muitos projetos levam tempo
- Sem auto-scaling — apenas escalonamento vertical (atualizar o tamanho da instância); escalonamento horizontal é manual
Quando Escolher o Railway
O Railway é a escolha certa quando:
- Você é um desenvolvedor solo ou uma equipe pequena
- Quer fazer deploy rapidamente sem experiência em infraestrutura
- Sua app é uma combinação padrão de web API + banco de dados
- O orçamento é uma preocupação e você quer custos baixos e previsíveis
- Você está prototipando ou gerenciando um projeto paralelo
Migre para fora do Railway quando:
- Precisar de garantias de conformidade (SOC2, HIPAA com BAA, etc.)
- Precisar de redes complexas (VPCs, endpoints privados)
- Precisar de deploys ativos-ativos em múltiplas regiões
- Seus padrões de tráfego exigirem auto-scaling sofisticado