Run, Build and Publish

How dotnet run, dotnet build and dotnet publish differ, and exactly which files each one leaves on disk.

Syntaxdotnet publish -c Release -o <directory>

Three commands compile your code. They differ in what they do afterwards and in what they leave behind.

dotnet run

Compiles, then immediately starts the program. This is the command for the edit-and-try loop. Compiled files land in bin/Debug/net10.0 and working files in obj. It resolves libraries straight out of the local package cache, which is why it is not the command to use in production.

dotnet build

Compiles and stops. In bin/Debug/net10.0 you get the assembly Report.dll holding your compiled code, a small launcher program named Report (Report.exe on Windows), Report.pdb with the information a debugger needs, Report.deps.json listing the libraries the program depends on, and Report.runtimeconfig.json naming the runtime version it expects. Any libraries it uses are copied in beside them.

dotnet publish

Compiles and then gathers everything needed to deploy into one folder, by default bin/Release/net10.0/publish. This is the only officially supported way to prepare an app for a server. Use -o to send the folder somewhere else.

What trips people up

The default configuration is not the same for all three. Since .NET 8, dotnet publish defaults to Release for projects targeting net8.0 or later, while dotnet build and dotnet run still default to Debug. Release turns on optimisations, so what you ship is not built the way you were testing it. Pass -c Release or -c Debug when it matters.

Example

Example · csharp
// Program.cs of a project named Report. The same file, three commands:
//
//   dotnet run
//       compiles into bin/Debug/net10.0, then prints the lines below
//   dotnet build
//       compiles into bin/Debug/net10.0 and stops
//   dotnet bin/Debug/net10.0/Report.dll
//       runs what the build produced, without compiling again
//   dotnet publish
//       fills bin/Release/net10.0/publish with a deployable copy
Console.WriteLine("report: ok");
Console.WriteLine("rows: 3");

// Output:
// report: ok
// rows: 3

When to use it

  • Iterating on a feature locally without thinking about output folders.
  • Producing the exact folder a deployment script copies to a server.
  • Diagnosing a program that runs locally but will not start from its published folder.
  • Shipping a command-line tool to a machine that has no .NET installed.

More examples

What a build leaves behind

Use dotnet build when you only want to know whether the code compiles, for example in a pre-commit check. Knowing these five files by name also makes a deployment that fails to start much easier to diagnose.

Example · bash
dotnet build
# Build succeeded

ls bin/Debug/net10.0
# Report                      a launcher program (Report.exe on Windows)
# Report.dll                  your compiled code
# Report.pdb                  debug information
# Report.deps.json            the list of libraries this program needs
# Report.runtimeconfig.json   which .NET runtime version to use

dotnet bin/Debug/net10.0/Report.dll   # start it without rebuilding

Publishing for a server that has .NET installed

This is a framework-dependent publish: the folder holds your code and its libraries but not .NET itself, so a matching runtime must already be on the machine. It is the normal choice when you control the server or the container image.

Example · bash
dotnet publish
# Release is the default configuration here, for net8.0 and later.
# Output written to bin/Release/net10.0/publish/

# Or choose the configuration and the destination yourself:
dotnet publish -c Release -o /srv/report

# On the server, with the .NET 10 runtime installed:
dotnet /srv/report/Report.dll

Publishing something that needs no .NET on the target

A self-contained publish produces a much larger folder that runs on a machine with no .NET installed at all, which suits a single-file tool you hand to someone. linux-x64 is a runtime identifier: the operating system and processor you are publishing for.

Example · bash
# Carry the runtime along with the app:
dotnet publish -c Release -r linux-x64 --self-contained
# Output lands in bin/Release/net10.0/linux-x64/publish/

# The opposite, stated explicitly:
dotnet publish -c Release -r linux-x64 --no-self-contained

Discussion

  • Be the first to comment on this lesson.