← back to knowledge-hub

Hosting a .NET Website on Render: Free Postgres, Deployment, and the Fine Print

Render has become a genuinely comfortable home for ASP.NET Core apps. It builds straight from a Git repository, runs your app on managed infrastructure, and — importantly for anyone shipping side projects — it has a free tier that includes both a web service and a PostgreSQL database. This guide walks through what actually happens when you host a .NET website there, how to deploy it, how to connect a free Postgres database, and where the free tier’s limits will bite.

Everything below is deliberately provider-agnostic. It applies to any ASP.NET Core app — a Web API, an MVC/Razor site, a Blazor Server app, or an API paired with a Vite/React SPA.

What You’re Actually Deploying

An ASP.NET Core app serves HTTP through Kestrel, its built-in cross-platform web server. On Render, your app runs as a Web Service: a long-running process that Render keeps alive, routes public traffic to, and puts behind its edge (you’ll typically see Cloudflare in front and Kestrel as the origin server). There’s no IIS and no Windows involved — your app runs on Linux, exactly as it would in a container.

That means two things matter before anything else:

  1. Your app must listen on the port Render tells it to.
  2. Your app must bind to 0.0.0.0, not localhost, so traffic from outside the container reaches it.

Step 1: Make Kestrel Bind to Render’s Port

Render assigns each web service a port through the PORT environment variable (the default is 10000). Your job is to make Kestrel listen there, on all interfaces. The most reliable, assumption-free way is to read PORT explicitly in Program.cs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var builder = WebApplication.CreateBuilder(args);

// Render provides PORT; default to 10000 for local parity.
var port = Environment.GetEnvironmentVariable("PORT") ?? "10000";
builder.WebHost.UseUrls($"http://0.0.0.0:{port}");

// ... rest of your service registration

var app = builder.Build();
app.Run();

This is more dependable than trying to interpolate $PORT inside an environment variable, because Render does not expand shell variables inside env-var values. Reading the variable in code sidesteps that entirely.

If you’re on .NET 8 or later and prefer configuration over code, you can instead set the environment variable ASPNETCORE_HTTP_PORTS=10000 in the Render dashboard and let Kestrel bind to that fixed port — Render’s default expected port is 10000, so the two line up. Either approach works; pick one and be consistent.

Step 2: Deploy the App

Push your code to GitHub or GitLab, then in the Render dashboard create a New → Web Service and connect the repository. You then choose between two runtimes.

Option A — Native .NET runtime

Render can build and run .NET directly, no Dockerfile required. You supply two commands:

  • Build command:
    1
    
    dotnet publish -c Release -o out
    
  • Start command:
    1
    
    dotnet out/YourApp.dll
    

Replace YourApp.dll with your project’s output assembly name. Render runs the build on every push, publishes the output, and executes the start command.

Option B — Docker

If you need full control over the base image or the app has native dependencies, add a Dockerfile and Render will build and run that instead. A minimal multi-stage build:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .
# Bind Kestrel to Render's port
ENV ASPNETCORE_HTTP_PORTS=10000
ENTRYPOINT ["dotnet", "YourApp.dll"]

With Docker, Render ignores the build/start command fields and uses the image’s ENTRYPOINT.

Infrastructure as code (optional)

Rather than clicking through the dashboard, you can commit a render.yaml Blueprint to your repo and let Render provision everything — the web service and the database — from that file. It’s the cleanest way to keep deployment reproducible:

1
2
3
4
5
6
7
8
services:
  - type: web
    name: my-dotnet-app
    runtime: docker      # or 'dotnet' for the native runtime
    plan: free
    envVars:
      - key: ASPNETCORE_ENVIRONMENT
        value: Production

Step 3: Add a Free PostgreSQL Database

Render offers a managed PostgreSQL database, and the free plan is the natural companion to a free web service. PostgreSQL is a strong default for .NET: it’s mature, open-source, and has first-class support through the Npgsql provider for Entity Framework Core.

Create the database and wire it up

  1. In Render, create New → PostgreSQL and pick the free plan.
  2. Once provisioned, copy the Internal Connection String (use the internal URL — it keeps traffic inside Render’s network and is faster and more secure than the external one).
  3. Add it to your web service as an environment variable. A convenient name is ConnectionStrings__DefaultConnection, because the double underscore maps to Configuration["ConnectionStrings:DefaultConnection"] in .NET automatically.

One wrinkle worth knowing: Render hands you a URL in the form postgres://user:password@host/dbname, but Npgsql expects a keyword-style connection string, not a URI. Convert it to:

Host=<host>;Port=5432;Database=<dbname>;Username=<user>;Password=<password>;SSL Mode=Require;Trust Server Certificate=true

The SSL Mode=Require part matters — Render’s Postgres requires TLS.

Connect from EF Core

1
2
3
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseNpgsql(
        builder.Configuration.GetConnectionString("DefaultConnection")));

Add the package first:

1
dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL

Run migrations on a platform with no shell

On the free tier you don’t get an easy interactive shell to run dotnet ef database update against production. The pragmatic pattern is to apply migrations automatically at startup:

1
2
3
4
5
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
    db.Database.Migrate();
}

This runs any pending migrations every time the app boots, so a fresh deploy brings the schema up to date on its own.

The Fine Print: Free-Tier Limitations

This is where honesty saves you a debugging session later. The free tier is excellent for demos, portfolios, and low-traffic side projects — but it has firm edges.

Web service:

  • It spins down after inactivity. A free web service that goes 15 minutes without inbound traffic is stopped. The next request wakes it, and that cold start takes roughly a minute, during which visitors see a loading page (and API clients may get a 503 until it’s up). If you deploy an app and the first request seems to hang, this is almost always why — it’s expected behaviour, not a broken build.
  • 750 free instance hours per month, shared across your workspace. Exceed it and services are suspended until the next cycle.
  • Small, shared compute. The free instance is modest (around 512 MB RAM). Memory-hungry .NET workloads — large in-memory caches, heavy PDF generation, image processing — can hit the ceiling.
  • Ephemeral filesystem. Free web services have no persistent disk. Anything you write to local disk (uploaded files, generated documents, SQLite files) is lost on every redeploy and every spin-down. Store uploads in the database or in external object storage, never on the container’s disk.

PostgreSQL (free plan):

  • 1 GB of storage.
  • The free database expires 30 days after creation, with a 14-day grace period to upgrade before it’s deleted. This is the single biggest gotcha: a free Postgres database is not a permanent home for data you care about. Budget for a paid plan, or export regularly.
  • One free Postgres database per workspace.
  • No managed connection pooling. ASP.NET Core opens a connection per request by default, so on a constrained free database you should keep pool sizes sane and lean on EF Core’s built-in pooling (AddDbContextPool) rather than assuming Render will pool for you.

Is It Worth It?

For shipping an ASP.NET Core app without touching a server, Render’s free tier is hard to beat: Git-push deploys, a native .NET runtime, and a real PostgreSQL database, all at no cost. The trade-offs are equally clear — cold starts on idle, an ephemeral disk, and a Postgres instance that expires in a month.

The sensible pattern is to build and validate on free, then move the pieces that need to be reliable to a paid plan — a persistent database first, then an always-on web service if cold starts hurt your users. Knowing exactly where those lines fall, before you deploy, is what turns Render from a surprise into a solid choice.

graph cloud