Skip to content
Happy Programming Guide
Start learning
C#

C# Deployment

How to publish a .NET application: framework-dependent versus self-contained builds, single-file output, trimming, Docker and configuration for production.

Servers mounted in a data centre rack

Deploying a C# application means running dotnet publish and copying the output somewhere. The decisions that matter are made through the flags on that command: whether the target machine needs .NET installed, whether you ship one file or many, and how configuration and secrets reach the running program.

The basic command#

Terminal
dotnet publish -c Release -o ./publish

-c Release is not optional in practice. A Debug build is larger, slower, and includes debugging information you do not want on a server.

The output folder contains your DLLs, the dependencies, and a runtime configuration file. Copy it to the target machine and run:

Terminal
dotnet MyApp.dll

Framework-dependent or self-contained#

Framework-dependent Self-contained
Target needs .NET installed Yes No
Output size a few MB 60 MB or more
Security patches via the shared runtime you must rebuild
Good for servers you control desktop distribution
Terminal
# Framework-dependent (default)
dotnet publish -c Release

# Self-contained, for 64-bit Linux
dotnet publish -c Release -r linux-x64 --self-contained true

The trade-off is maintenance. A framework-dependent app picks up runtime security fixes when the server updates .NET. A self-contained app carries its own copy, so every runtime fix requires you to rebuild and redeploy.

Runtime identifiers#

Terminal
win-x64      linux-x64      osx-x64
win-arm64    linux-arm64    osx-arm64
linux-musl-x64             # Alpine

You can publish for any of these from any machine, which makes building a Linux deployment on a Windows laptop straightforward.

Single file#

Terminal
dotnet publish -c Release -r win-x64 --self-contained true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true

One executable, no folder of DLLs. Convenient for tools people download and run.

Two things to know. The file extracts itself to a temporary folder on first run, so start-up is slightly slower the first time. And any code that assumes DLLs sit next to the executable — some plugin loaders, some reflection — will need adjusting.

Trimming#

Terminal
dotnet publish -c Release -r linux-x64 --self-contained true \
  -p:PublishTrimmed=true

Trimming removes framework code your application does not reference, which can cut a 60 MB output to under 20 MB.

Ahead-of-time compilation#

Terminal
dotnet publish -c Release -r linux-x64 -p:PublishAot=true

Native AOT compiles to machine code with no runtime, giving very fast start-up and a small footprint — useful for command-line tools and serverless functions. It comes with real restrictions: no runtime code generation, limited reflection, and some libraries will not work. Suitable for new projects designed with it in mind, awkward to retrofit.

Configuration#

JSON
{
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=shop;"
  },
  "Logging": {
    "LogLevel": { "Default": "Information" }
  }
}

Settings are layered, and later sources win: appsettings.json, then appsettings.{Environment}.json, then environment variables, then command-line arguments.

Terminal
export ASPNETCORE_ENVIRONMENT=Production
export ConnectionStrings__Default="Server=db;Database=shop;"

The double underscore is how environment variables express nesting. That is the standard way to supply secrets in containers and on servers.

Terminal
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;..."

Docker#

Output
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src

COPY *.csproj ./
RUN dotnet restore

COPY . ./
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:8.0
WORKDIR /app
COPY --from=build /app .

EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
ENTRYPOINT ["dotnet", "MyApp.dll"]

Two details make this efficient. The build happens in an image containing the SDK, while the final image contains only the smaller runtime. And copying the project file and restoring before copying the source means Docker caches the dependency layer, so ordinary code changes rebuild in seconds.

Running as a service on Linux#

Output
[Unit]
Description=My App
After=network.target

[Service]
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/dotnet /var/www/myapp/MyApp.dll
Restart=always
RestartSec=10
User=www-data
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target
Terminal
sudo systemctl enable myapp
sudo systemctl start myapp
sudo journalctl -u myapp -f

Put nginx or another reverse proxy in front for TLS, compression and static files.

A pre-deployment checklist#

  • Built with -c Release
  • No secrets in the repository or in appsettings.json
  • ASPNETCORE_ENVIRONMENT set to Production
  • Detailed error pages disabled for visitors
  • Logging goes somewhere you can read after the fact
  • A health-check endpoint the platform can poll
  • The app runs as a non-root user
  • You have tested the exact build you are shipping, not a Debug one

Questions people ask#

Do I need Visual Studio to publish?

No. The dotnet command-line tool does everything, which is also what your continuous integration pipeline will use.

Why is my self-contained build so large?

It includes the whole runtime. Add -p:PublishTrimmed=true to remove unused parts, and test the result carefully afterwards.

Can I publish for Linux from Windows?

Yes. Pass -r linux-x64. Cross-platform publishing is fully supported in both directions.

What is the difference between build and publish?

build compiles for local running. publish produces a self-contained output folder with dependencies and configuration, ready to copy to another machine.

Where to go next#

Deployment and packaging, whatever the languageRead next

Keep reading

C#

C# Basics

The core of C# in one page: types, variables, conditions, loops, methods, collections and classes, with the compiler errors beginners meet.

5 min read

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *