O Render.com se tornou silenciosamente uma das plataformas PaaS mais amigáveis para desenvolvedores que trabalham com cargas de trabalho em contêineres — mas a penalidade de cold start do plano gratuito e os saltos de preço pegam de surpresa desenvolvedores .NET que não sabem no que estão se metendo. Este guia cobre tudo, do Dockerfile ao render.yaml, um deploy real do exemplo .NET 10 de
samples/dotnet-hosting, e uma comparação honesta com Railway e Fly.io.
O que é o Render.com (e seu posicionamento no mercado)
Render é uma plataforma cloud totalmente gerenciada que executa contêineres Docker, sites estáticos, cron jobs e workers em segundo plano. Ocupa o espaço entre o Heroku (obsoleto ou caro demais) e o Kubernetes puro: você obtém deploys baseados em Git, TLS gerenciado e Postgres integrado sem escrever código de infraestrutura.
Seus principais diferenciais para desenvolvedores .NET:
- HTTPS sem configuração em cada serviço e domínio personalizado
render.yaml— infraestrutura como código versionada no seu repositório- Postgres nativo com bancos de dados gerenciados e backups automáticos
- Rede privada entre serviços da mesma conta (sem custos de egress público)
- Auto-deploy a cada push para qualquer branch que você configurar
O porém: o plano gratuito suspende serviços ociosos, e o salto do gratuito ($0) para o Starter ($7/mês) é a menor atualização que elimina os cold starts.
Dockerfile para .NET no Render
O Render executa qualquer imagem Docker. O build multi-estágio padrão funciona sem modificações — 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 Render injeta uma variável de ambiente PORT em tempo de execução (10000 por padrão). A tentadora linha de Dockerfile ENV ASPNETCORE_URLS=http://+:${PORT:-8080} NÃO a captura — o Docker resolve ${...} quando a imagem é construída, então o fallback fica fixado e o valor injetado pelo Render é silenciosamente ignorado. Leia PORT no Program.cs em vez disso.
// Program.cs — ler a PORT que o Render 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).Fazendo Deploy pelo Dashboard
O caminho mais rápido é o formulário do dashboard — sem YAML. New → Web Service, conecte o repositório do GitHub, e um único formulário cobre tudo. O único campo não óbvio em um monorepo é o Root Directory, que aponta o contexto de build do Docker para a pasta certa:

Escolha um tipo de instância (Free para este passo a passo), clique em Deploy Web Service, e o Render constrói o Dockerfile e roteia tráfego assim que o health check passa:

O exemplo em produção informa a plataforma que detectou (pela variável de ambiente RENDER) e a porta que o Render injetou:
![Navegador mostrando a resposta JSON de jorgenhoc-hosting-sample.onrender.com: service JorgenHoc hosting sample, runtime 10.0.11, platform Render, listeningOn http://[::]:10000.](/images/dotnet-hosting/render-app-response.png)
render.yaml — Infraestrutura como Código
O formulário do dashboard funciona, mas o render.yaml (um "Blueprint") declara serviços, bancos de dados e variáveis de ambiente em um arquivo versionado no seu repo — o Render o aplica quando você conecta o repositório como Blueprint. O exemplo mantém um mínimo em deploy/render.yaml; um exemplo mais completo com cara de produção:
# render.yaml — versionado no controle de código fonte
services:
- type: web
name: my-dotnet-api
runtime: docker
region: oregon # oregon | frankfurt | singapore | ohio
plan: starter # free | starter | standard | pro
branch: main # auto-deploy a cada push nesta branch
healthCheckPath: /health
# Overrides de build (raramente necessário com um Dockerfile)
dockerfilePath: ./MyApi/Dockerfile
envVars:
- key: ASPNETCORE_ENVIRONMENT
value: Production
- key: ConnectionStrings__DefaultConnection
fromDatabase:
name: my-postgres-db # referencia o bloco de banco de dados abaixo
property: connectionString
- key: JWT_SECRET
generateValue: true # Render gera um valor aleatório uma vez
autoDeploy: true
# Worker em segundo plano (sem porta pública, sem health check necessário)
- type: worker
name: my-background-worker
runtime: docker
plan: starter
branch: main
dockerfilePath: ./Worker/Dockerfile
envVars:
- key: ASPNETCORE_ENVIRONMENT
value: Production
databases:
- name: my-postgres-db
databaseName: myapp
user: myapp_user
plan: free # free | basic-256 | basic-512 | basic-1
region: oregonPontos-chave:
fromDatabase.property: connectionStringinjeta automaticamente a connection string completa do Postgres — sem copiar e colar credenciais.generateValue: trueé útil para segredos como chaves de assinatura JWT; o Render os gera uma vez e nunca os exibe novamente nos logs.- O
healthCheckPathinforma ao load balancer do Render qual endpoint consultar. Retorne HTTP 200 quando a app estiver pronta.
Endpoint de Health Check
Adicione um endpoint de health mínimo para que o Render saiba que sua app está funcionando:
// Program.cs
app.MapGet("/health", () => Results.Ok(new { status = "healthy", utc = DateTime.UtcNow }));Para produção, use Microsoft.Extensions.Diagnostics.HealthChecks para incluir a conectividade com o banco de dados:
builder.Services.AddHealthChecks()
.AddNpgsql(connectionString, name: "postgres");
// Mapear em /health
app.MapHealthChecks("/health");Variáveis de Ambiente
O Render oferece duas formas de configurar variáveis de ambiente:
1. render.yaml (versionado no código, valores não secretos)
envVars:
- key: ASPNETCORE_ENVIRONMENT
value: Production
- key: FEATURE_FLAG_NEW_CHECKOUT
value: "true"2. Dashboard (segredos, overrides por serviço)
Navegue até seu serviço → Environment → Add Environment Variable. Valores definidos no dashboard sobrescrevem o render.yaml para a mesma chave. Nunca coloque segredos no render.yaml — use o dashboard ou generateValue: true.
O Render também expõe variáveis internas que você pode ler na sua app:
| Variável | Valor |
|---|---|
RENDER | true |
RENDER_SERVICE_NAME | Nome do seu serviço |
RENDER_GIT_COMMIT | SHA completo do commit deployado |
RENDER_GIT_BRANCH | Nome da branch |
PORT | Porta na qual seu servidor deve fazer bind |
// Ler contexto específico do Render para logging/tracing
var isRender = Environment.GetEnvironmentVariable("RENDER") == "true";
var commitSha = Environment.GetEnvironmentVariable("RENDER_GIT_COMMIT") ?? "local";
var serviceName = Environment.GetEnvironmentVariable("RENDER_SERVICE_NAME") ?? "unknown";
logger.LogInformation("Iniciando {Service} @ {Commit}", serviceName, commitSha[..7]);Adicionando um Banco de Dados Postgres
Via render.yaml (recomendado)
O bloco databases mostrado acima cria uma instância Postgres gerenciada. O Render a vincula ao seu serviço via fromDatabase, que injeta automaticamente a variável de ambiente ConnectionStrings__DefaultConnection.
// appsettings.json — a chave deve corresponder à chave envVar no render.yaml
{
"ConnectionStrings": {
"DefaultConnection": "Host=localhost;Database=myapp;Username=dev;Password=dev"
}
}// Program.cs
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));Via Dashboard
- Dashboard → New → PostgreSQL
- Escolha a região (faça coincidir com a região do seu serviço web para evitar latência entre regiões)
- Copie a Internal Database URL — esta é a URL da rede privada, sem custo de egress
- Cole nas variáveis de ambiente do seu serviço como
ConnectionStrings__DefaultConnection
Sempre use a Internal Database URL (começa com postgres://...@dpg-...oregon-a:5432/...) em vez da URL externa. Conexões internas permanecem dentro da rede privada do Render — sem custos de egress e ~0.3ms a menos de latência vs o endpoint público.
Executando Migrations do EF Core no Deploy
O Render não possui um hook de migration integrado. Abordagens comuns:
Opção 1 — Migrar na inicialização (simples, adequado para equipes pequenas)
// Program.cs — executar migrations antes de a app começar a aceitar requisições
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// ApplyMigrations é idempotente — seguro executar a cada inicialização
await db.Database.MigrateAsync();
}Opção 2 — Job pré-deploy (mais seguro para produção)
Adicione um preDeployCommand no render.yaml (atualmente em beta para serviços Docker). Como alternativa, use o recurso de jobs avulsos do Render para executar migrations manualmente antes de promover.
Domínios Personalizados e TLS
- Dashboard → seu serviço → Settings → Custom Domains → Add Custom Domain
- Adicione o registro CNAME que seu provedor DNS requer (o Render mostra o valor exato)
- O Render provisiona um certificado Let's Encrypt automaticamente — geralmente em 60 segundos
- O redirect de HTTP para HTTPS está habilitado por padrão
Você pode adicionar múltiplos domínios personalizados por serviço. Certificados wildcard (*.seudominio.com) não são suportados no plano gratuito.
Auto-Deploy do GitHub
O render.yaml define autoDeploy: true e branch: main. Cada push para main dispara um novo build Docker e deploy progressivo.
Para fazer deploy de ambientes de preview por pull request, habilite Pull Request Previews nas configurações do seu serviço. Cada PR recebe sua própria URL (https://my-dotnet-api-pr-42.onrender.com) com variáveis de ambiente isoladas.
Para ambientes de staging baseados em branches, crie um segundo serviço no render.yaml:
services:
- type: web
name: my-dotnet-api-staging
runtime: docker
plan: starter
branch: develop # auto-deploy staging da branch develop
envVars:
- key: ASPNETCORE_ENVIRONMENT
value: StagingO Problema do Cold Start no Plano Gratuito
Esta é a reclamação mais comum sobre o Render e o que mais importa antes de escolher um plano.
O que acontece: Serviços web do plano gratuito são suspensos após 15 minutos de inatividade. A próxima requisição HTTP acorda o contêiner. Durante a ativação, o Render precisa:
- Obter a imagem Docker (em cache, rápido)
- Iniciar o contêiner
- Aguardar sua app passar no health check
Para uma API .NET típica, isso leva 25–60 segundos. A inicialização do ASP.NET não é rápida — ele escaneia assemblies, inicializa DI, aquece o EF Core e conecta ao Postgres antes de aceitar tráfego.
A primeira requisição após um cold start fica travada por 30–60 segundos ou retorna um 502 (se o timeout do health check for menor que o tempo de inicialização da sua app).
Medindo Seu Cold Start
Adicione timing de inicialização para entender onde o tempo é gasto:
// Program.cs
var sw = System.Diagnostics.Stopwatch.StartNew();
var builder = WebApplication.CreateBuilder(args);
// ... registro de serviços ...
var app = builder.Build();
// ... pipeline de middleware ...
app.Lifetime.ApplicationStarted.Register(() =>
{
sw.Stop();
// Log para stdout — visível no visualizador de logs do Render
Console.WriteLine($"[STARTUP] Pronto em {sw.ElapsedMilliseconds}ms");
});
await app.RunAsync();Soluções para o Problema de Cold Start
Opção 1 — Ping externo de health check (gratuito)
Use um monitor de uptime gratuito (UptimeRobot, Better Uptime, plano gratuito do Cronitor) para fazer ping no seu endpoint /health a cada 10 minutos. Isso mantém o contêiner ativo sem precisar de upgrade.
Limitações: o próprio monitor pode ter intervalos sem atividade, e os pings não contam como tráfego de usuários — o temporizador de inatividade do Render é baseado em requisições HTTP de clientes externos, não do seu próprio monitoramento.
Opção 2 — Atualizar para Starter ($7/mês)
O plano Starter elimina os cold starts completamente. O serviço fica em execução contínua, e você obtém 512 MB de RAM e uma CPU compartilhada. Para a maioria das APIs .NET, esta é a escolha certa no momento em que você tem usuários reais.
Opção 3 — Otimizar o tempo de inicialização
Reduza a duração do cold start adiando a inicialização custosa:
// Usar IHostedService para inicialização em segundo plano em vez de bloquear a inicialização
builder.Services.AddSingleton<IMyService, MyService>();
builder.Services.AddHostedService<MyServiceWarmup>();
// MyServiceWarmup.cs
public class MyServiceWarmup : IHostedService
{
private readonly IMyService _service;
public MyServiceWarmup(IMyService service) => _service = service;
public async Task StartAsync(CancellationToken ct)
{
// Executar init custoso em segundo plano — não bloqueia o health check
_ = Task.Run(() => _service.WarmUpAsync(ct), ct);
}
public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}Se sua app demorar mais que o timeout do health check do Render (padrão 180 segundos) para iniciar, o deploy falhará e o Render reverterá para a versão anterior. Aumente o timeout em Service Settings → Health Check Timeout, ou otimize a inicialização.
Preços
| Plano | Preço/mês | RAM | CPU | Cold Starts | Ideal Para |
|---|---|---|---|---|---|
| Free | $0 | 512 MB | Compartida | Sim (15 min ocioso) | Demos, projetos paralelos |
| Starter | $7 | 512 MB | Compartida | Não | Projetos pessoais, baixo tráfego |
| Standard | $25 | 2 GB | 1 vCPU | Não | APIs em produção |
| Pro | $85 | 4 GB | 2 vCPU | Não | Serviços de alto tráfego |
| Pro Plus | $175 | 8 GB | 4 vCPU | Não | Cargas de trabalho com uso intensivo de CPU |

Preços do Postgres (separados do serviço web):
| Plano | Preço/mês | Armazenamento | RAM |
|---|---|---|---|
| Free | $0 | 256 MB | 256 MB |
| Basic-256 | $7 | 256 MB | 1 GB |
| Basic-512 | $14 | 512 MB | 1 GB |
| Basic-1 | $28 | 1 GB | 2 GB |
| Pro | $65 | 100 GB | 4 GB |
O Postgres gratuito expira 30 dias após a criação (sem backups) — depois disso o Render exclui o banco de dados. Para dados gratuitos persistentes, use um provedor externo como o Neon; para produção, use pelo menos o Basic-256.
Largura de banda: 100 GB gratuitos por mês entre todos os serviços; $0.10/GB depois disso.
Custo mensal estimado para uma API .NET típica + Postgres:
- Desenvolvimento: Web gratuito + Postgres gratuito = $0 (cold starts, limite de 30 dias no DB)
- Produção: Web Starter + Postgres Basic-256 = $14/mês
- Produção escalada: Web Standard + Postgres Basic-1 = $53/mês
Benchmark: Render vs Railway (Números Reais)
Estes números foram medidos de verdade (agosto de 2026) contra os dois deploys ativos desta série de artigos — a API .NET 10 de exemplo no Render Free (Oregon) e no Railway Hobby (US East). O Fly.io estava planejado como terceira plataforma mas não foi medido: ele não tem mais tier gratuito e o cadastro exige cartão de crédito.
Configuração do teste:
- Ferramenta: k6, script em
benchmark/k6-latency.js— rode-o contra os seus próprios deploys - Carga: taxa de chegada fixa de 30 req/s por 60 segundos (~1.800 requisições por plataforma), serviços aquecidos antes
- Endpoint:
/, um payload JSON pequeno, sem banco de dados — isso mede o caminho HTTP da plataforma, não o desempenho de consultas - Cliente: uma única máquina em Bogotá, Colômbia. Com taxa de chegada fixa, a distribuição de latência é dominada pelo caminho de rede entre o cliente e a região do provedor — estes números dizem o que um usuário naquela localização experimenta, não uma velocidade absoluta da plataforma. Rode o script de onde estão os seus usuários.
Latência Aquecida
| Provedor | Plano | Região | p50 | p95 | p99 | Requisições com falha |
|---|---|---|---|---|---|---|
| Render | Free ($0) | Oregon | 152 ms | 206 ms | 318 ms | 0 de 1.800 |
| Railway | Hobby ($5) | US East | 181 ms | 220 ms | 312 ms | 0 de 1.801 |
A surpresa: o Render saiu mais rápido na mediana apesar de a origem estar mais longe (Oregon vs US East). A razão é a rede edge — o Render fica atrás da Cloudflare, que terminou o TLS em Bogotá, enquanto o edge mais próximo do Railway era Miami. No p99 eles são indistinguíveis. A conclusão honesta: a 30 req/s nos planos mais baratos, as duas plataformas dão conta com folga, e a geografia dos seus usuários importa mais que a computação da plataforma.
Cold Start (primeira requisição após 22 minutos ocioso)
| Provedor | 1ª requisição | 2ª requisição | 3ª requisição |
|---|---|---|---|
| Render Free | 12,3 s | 0,63 s | 0,45 s |
| Railway Hobby | 0,69 s | 0,45 s | 0,47 s |
O Render Free tinha sido suspenso e precisou de 12,3 segundos para subir o contêiner, iniciar o .NET e passar no health check. Atenção: esta minimal API é o melhor caso — sem grafo de DI, sem aquecimento de EF Core, sem conexão a banco. Uma aplicação real fica mais perto da faixa de 25–60 segundos descrita antes, e é por isso que o próprio banner do dashboard do Render avisa sobre atrasos de "50 segundos ou mais". O Railway Hobby nunca dorme, então seu número "frio" é só um handshake TLS novo.
Principais conclusões:
- Os dois planos mais baratos aguentaram 30 req/s com zero requisições com falha e p99 abaixo de 330 ms a partir da América do Sul.
- Redes edge vencem a proximidade da origem na mediana: Render + Cloudflare foi mais rápido de Bogotá que a região de origem mais próxima do Railway.
- A verdadeira diferença do tier gratuito é o cold start: o Render Free paga 12+ segundos após cada período ocioso; o Railway não tem tier web gratuito, então nada nunca dorme.
- O Fly.io não foi medido aqui — se você tem conta, faça o deploy do mesmo exemplo com o
fly.tomldele e rode o mesmo script.
Prós e Contras
Prós
- render.yaml é excelente — a história de infra-as-code mais limpa das três plataformas. Defina tudo em um arquivo, compare-o em PRs.
- Postgres gerenciado é de primeira classe — backups diários automáticos, recuperação point-in-time em planos superiores, injeção de connection string sem atrito.
- Ambientes de preview de PR — cada PR recebe uma URL ao vivo com configuração isolada. Enorme para fluxos de trabalho de QA.
- Sem configuração de rede — a descoberta de serviços internos simplesmente funciona. Workers podem chamar serviços web pelo nome sem configuração extra.
- Preços transparentes — sem surpresas na fatura de egress. 100 GB/mês de largura de banda gratuita cobre a maioria dos apps pequenos.
- A experiência de desenvolvimento é polida — logs de deploy em tempo real, rollbacks com um clique, diffs de variáveis de ambiente auditáveis.
Contras
- Cold starts do plano gratuito são brutais para .NET — ativações de 30–60 segundos são piores que a maioria das plataformas devido à sobrecarga de inicialização do .NET. Aceitável para demos, inutilizável para usuários reais.
- Os tempos de build dependem do seu Dockerfile — estruture-o para aproveitar o cache de camadas (restore antes do copy) ou você vai sentir isso a cada deploy.
- Sem edge/multi-região — o Render tem 4 regiões mas sem roteamento geo-automático ou distribuição em nível CDN. O Fly.io vence aqui.
- CPU compartilhada no Starter é limitada — apps .NET com tarefas intensivas em CPU (geração de PDF, processamento de imagens) serão throttleadas no Starter e precisarão do Standard ($25).
- Sem suporte a GPU — não é uma plataforma para cargas de trabalho de inferência de ML.
- Postgres gratuito expira após 30 dias — fácil de esquecer, doloroso quando seu banco de dados desaparece em produção.
Quando Escolher o Render
| Cenário | Veredicto |
|---|---|
| Projeto paralelo / demo | Plano gratuito está bom — aceite os cold starts |
| Ferramenta interna, baixo tráfego | Starter ($7) — melhor custo-benefício |
| API em produção, tráfego moderado | Standard ($25) — escolha sólida |
| Precisa de latência mais próxima do usuário | Considere o Fly.io |
| Sem cartão de crédito / orçamento de $0 estrito | O Render Free é a única opção real das três |
| Infra-as-code completa em um arquivo | Render vence com o render.yaml |
| Postgres gerenciado com backups | Render é excelente |
| Ambientes de preview de PR | Render é o melhor dos três |
Resumo
O Render.com é uma excelente escolha para desenvolvedores .NET que querem uma experiência PaaS limpa sem gerenciar infraestrutura. O formato render.yaml é a melhor história de infra-as-code de qualquer plataforma comparável, o Postgres gerenciado é genuinamente bom, e os previews de PR são um multiplicador de fluxo de trabalho.
O plano gratuito é adequado apenas para demos e desenvolvimento — o problema de cold start com .NET é grave demais para qualquer coisa voltada ao usuário. Orçe $14/mês (Starter + Basic Postgres) como mínimo para um app em produção. Nessa faixa de preço, o Render é difícil de superar pela combinação de experiência de desenvolvimento, confiabilidade e serviços gerenciados.
Se a latência bruta é sua principal restrição, rode o script k6 acima a partir da região dos seus usuários — nossas medições mostram que a geografia e as redes edge dominam nessa escala. Mas para a maioria das equipes .NET que fazem deploy de uma API web ou worker em segundo plano, o Render entrega tudo que você precisa em um único lugar.