//JorgenHoc
← Todos os artigos
.NET HostingPor Jorge CalderónAtualizado 19 min read

.NET no Render.com — Prós, Contras e Benchmark Real

Hospede sua app .NET 10 no Render.com: guia de configuração, render.yaml, o problema de cold start, detalhamento de preços e comparativo de desempenho com Railway e Fly.io.

#dotnet#cloud#devops

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:

Formulário New Web Service do Render para o repositório jorgenhoc-org/dotnet-samples: nome jorgenhoc-hosting-sample, Language em Docker, branch main, região Oregon US West e Root Directory samples/dotnet-hosting para o monorepo.
Toda a configuração do deploy no Render em um formulário: repo, Docker, branch e Root Directory para o monorepo.

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:

Dashboard do serviço jorgenhoc-hosting-sample no Render mostrando o serviço Live na instância Free, a URL onrender.com, logs de deploy com o logging de requisições do ASP.NET Core e um banner avisando que instâncias gratuitas são suspensas por inatividade, o que pode atrasar requisições em 50 segundos ou mais.
Live no plano Free — repare no banner: o próprio Render avisa que uma instância gratuita suspensa pode atrasar requisições em 50+ segundos. É a seção de cold start abaixo.

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.
A resposta do exemplo em produção: .NET 10, plataforma detectada como Render e escutando na 10000 — a PORT que o Render injetou em tempo de execução, prova de que a abordagem do Program.cs funciona.

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: oregon

Pontos-chave:

  • fromDatabase.property: connectionString injeta 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 healthCheckPath informa 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ávelValor
RENDERtrue
RENDER_SERVICE_NAMENome do seu serviço
RENDER_GIT_COMMITSHA completo do commit deployado
RENDER_GIT_BRANCHNome da branch
PORTPorta 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

  1. Dashboard → New → PostgreSQL
  2. Escolha a região (faça coincidir com a região do seu serviço web para evitar latência entre regiões)
  3. Copie a Internal Database URL — esta é a URL da rede privada, sem custo de egress
  4. 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

  1. Dashboard → seu serviço → Settings → Custom Domains → Add Custom Domain
  2. Adicione o registro CNAME que seu provedor DNS requer (o Render mostra o valor exato)
  3. O Render provisiona um certificado Let's Encrypt automaticamente — geralmente em 60 segundos
  4. 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: Staging

O 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:

  1. Obter a imagem Docker (em cache, rápido)
  2. Iniciar o contêiner
  3. 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

PlanoPreço/mêsRAMCPUCold StartsIdeal Para
Free$0512 MBCompartidaSim (15 min ocioso)Demos, projetos paralelos
Starter$7512 MBCompartidaNãoProjetos pessoais, baixo tráfego
Standard$252 GB1 vCPUNãoAPIs em produção
Pro$854 GB2 vCPUNãoServiços de alto tráfego
Pro Plus$1758 GB4 vCPUNãoCargas de trabalho com uso intensivo de CPU
Seletor de Instance Type do Render com o plano Free selecionado a $0 por mês com 512 MB de RAM e 0.1 CPU, ao lado dos planos pagos: Starter $7 com 512 MB e 0.5 CPU, Standard $25 com 2 GB e 1 CPU, Pro $85 com 4 GB e 2 CPU, até Pro Ultra $450. Uma nota avisa que instâncias gratuitas são suspensas após períodos de inatividade.
O seletor de instâncias durante a configuração — os mesmos preços da tabela acima, direto do dashboard, com o aviso de suspensão anexado ao plano Free.

Preços do Postgres (separados do serviço web):

PlanoPreço/mêsArmazenamentoRAM
Free$0256 MB256 MB
Basic-256$7256 MB1 GB
Basic-512$14512 MB1 GB
Basic-1$281 GB2 GB
Pro$65100 GB4 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

ProvedorPlanoRegiãop50p95p99Requisições com falha
RenderFree ($0)Oregon152 ms206 ms318 ms0 de 1.800
RailwayHobby ($5)US East181 ms220 ms312 ms0 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)

Provedor1ª requisição2ª requisição3ª requisição
Render Free12,3 s0,63 s0,45 s
Railway Hobby0,69 s0,45 s0,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.toml dele 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árioVeredicto
Projeto paralelo / demoPlano gratuito está bom — aceite os cold starts
Ferramenta interna, baixo tráfegoStarter ($7) — melhor custo-benefício
API em produção, tráfego moderadoStandard ($25) — escolha sólida
Precisa de latência mais próxima do usuárioConsidere o Fly.io
Sem cartão de crédito / orçamento de $0 estritoO Render Free é a única opção real das três
Infra-as-code completa em um arquivoRender vence com o render.yaml
Postgres gerenciado com backupsRender é excelente
Ambientes de preview de PRRender é 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.

Leituras adicionais

Sobre o autor

Jorge Calderón

Engenheiro de software com mais de uma década construindo e operando aplicações .NET em produção — camadas de dados com EF Core, serviços intensivos em async e implantações em Azure e contêineres. Todos os benchmarks e projetos de exemplo destes guias estão publicados em um repositório público no GitHub para que você possa reproduzi-los.

Perfil no GitHubLinkedIn ↗Benchmarks e código de exemplo

Artigos relacionados