aspnet-core-publish-deploy-produccion-iis-linux

Publishing ASP.NET Core on IIS or Linux with dotnet publish

  • 4 min

dotnet publish is the command that prepares a .NET application and its dependencies for deployment.

During development we usually press F5 or type dotnet run. That runs the application with a configuration designed for coding and debugging.

For production we normally use a Release build, with the optimizations and artifacts intended for deployment.

To go to production, we need to generate a Release version: optimized and packaged. The command for this is dotnet publish.

The basic command

The simplest command you can run in your terminal is:

dotnet publish -c Release -o ./publish
Copied!

Let’s break down what this does:

  1. -c Release: Compiles in Release mode. The C# compiler (Roslyn) will apply aggressive optimizations to make your code run faster.
  2. -o ./publish: Saves the output to the publish folder.

Inside that folder you’ll see the assemblies, dependencies, configuration files, and depending on the publication type, a native executable. That is the set of files we deploy.

Publication strategies

In modern .NET, we have two main ways to package the application. Choosing the right one depends on your server.

This is the default option and does not include the full runtime.

  • Requirement: The server MUST have the .NET Runtime of the target version installed (e.g., .NET 10 if publishing for .NET 10).
  • Advantage: The deployment takes up less space and several applications can use the same runtime installed on the server.

This option packs everything from the .NET Runtime into your publication folder.

  • Requirement: It does not require a .NET runtime installed, although the operating system must still meet the native dependencies of the chosen platform.
  • Disadvantage: The size increases significantly (100MB+ for a simple API).
  • Command:
# Publish for Linux 64-bit self-contained
dotnet publish -c Release -r linux-x64 --self-contained true
Copied!

Kestrel

When you run your compiled API (./MiApi.exe or dotnet MiApi.dll), the one listening for HTTP requests is a web server called Kestrel.

Kestrel is a lightweight, cross-platform, and extremely fast (open source) web server that comes built into ASP.NET Core.

Kestrel can be exposed directly to the Internet or placed behind a reverse proxy. IIS, Nginx, Apache, or a load balancer can facilitate TLS termination, load distribution, access logs, and integration with existing infrastructure.

This is why the reverse proxy pattern is common, although it is not mandatory in all deployments.

Architecture with reverse proxy

Instead of exposing the application directly to the public port 80/443, we put a server or load balancer in front that receives the request and passes it to Kestrel.

  1. Install the .NET Core Hosting Bundle on the Windows server. This installs the Runtime and a module for IIS (AspNetCoreModuleV2).
  2. Create a website in IIS and point it to your publish folder.
  3. The IIS module will start your .exe in the background and redirect traffic.

When you run dotnet publish, a web.config file is automatically generated. Do not delete it, it tells IIS how to start your application.

  1. Copy your files to the server (e.g., /var/www/miapi).
  2. Create a Systemd service (a .service file) so your app starts automatically if the server reboots.
[Unit]
Description=My .NET API

[Service]
WorkingDirectory=/var/www/miapi
ExecStart=/usr/bin/dotnet /var/www/miapi/MiApi.dll
Restart=always

[Install]
WantedBy=multi-user.target
Copied!
  1. Configure Nginx as a Reverse Proxy to listen on port 80 and redirect to localhost:5000 (where Kestrel listens).

Single-file publication

There is a very attractive option for console tools, workers, or specific deployments: packaging the application into a single .exe or Linux binary file.

dotnet publish -c Release -r win-x64 -p:PublishSingleFile=true --self-contained true
Copied!

The result is a single MiApi.exe file. For web APIs it can be useful, but it’s not always worth it: review size, startup, native file extraction, and compatibility with your deployment platform.

Configuration management in production

A classic mistake is publishing the appsettings.Development.json or having development connection strings in the base appsettings.json.

In production, the loading order is:

  1. appsettings.json (Base).
  2. appsettings.Production.json (Overrides).
  3. Environment Variables (Overrides everything above).

The best practice on servers (IIS/Linux/Docker) is NOT to touch the JSON files. Use environment variables on the server to define the connection string and secret keys.

  • Windows: set ASPNETCORE_ENVIRONMENT=Production
  • Linux: export ASPNETCORE_ENVIRONMENT=Production