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:
- Your app must listen on the port Render tells it to.
- Your app must bind to
0.0.0.0, notlocalhost, 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:
| |
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:
1dotnet publish -c Release -o out - Start command:
1dotnet 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:
| |
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:
| |
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
- In Render, create New → PostgreSQL and pick the free plan.
- 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).
- Add it to your web service as an environment variable. A convenient name is
ConnectionStrings__DefaultConnection, because the double underscore maps toConfiguration["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
| |
Add the package first:
| |
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:
| |
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
503until 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.