dotnet publish
What dotnet publish puts in the output folder, why it builds Release, and what the runtimeconfig and deps files do.
dotnet publish [PROJECT] -c Release -o <directory>dotnet build produces files you can run on the machine that built them. dotnet publish produces a folder you can copy to a server. It is the only supported way to prepare an app for deployment.
What the folder holds
Your compiled code is written as app.dll. Beside it sits a small native launcher called the apphost: app.exe on Windows, app on Linux and macOS. It exists because the UseAppHost property defaults to true. Two generated files sit next to them. app.runtimeconfig.json tells the host which .NET framework and version the app wants. app.deps.json lists the assemblies the app expects to load. Every library you referenced is copied in too, plus a .pdb file, which holds the line numbers that make stack traces readable.
Release, not Debug
From the .NET 8 SDK onwards, dotnet publish uses the Release configuration for projects targeting net8.0 or later. Older SDKs used Debug. Release turns compiler optimisations on and stops defining the DEBUG symbol. You can still ask for Debug with -c Debug. -c is the short form of --configuration, and -o is the short form of --output; each pair is the same option.
Where it goes
With no -o, output lands in bin/Release/net10.0/publish/. Do not tidy up the two JSON files afterwards. Without them the host cannot start the app.
Example
// Program.cs from: dotnet new console
// Reports the configuration it was compiled in, then names the
// files that sit beside the app in a publish folder.
string configuration;
#if DEBUG
configuration = "Debug";
#else
configuration = "Release";
#endif
Console.WriteLine($"configuration: {configuration}");
string assemblyName = "shipping";
// A collection expression. The longhand is:
// new string[] { $"{assemblyName}.dll", ... }
string[] hostFiles =
[
$"{assemblyName}.dll",
$"{assemblyName}.runtimeconfig.json",
$"{assemblyName}.deps.json"
];
foreach (string file in hostFiles)
{
Console.WriteLine($"publish file: {file}");
}
// Output (from a Release build, which is what dotnet publish uses):
// configuration: Release
// publish file: shipping.dll
// publish file: shipping.runtimeconfig.json
// publish file: shipping.deps.jsonWhen to use it
- Hand a tester a folder they can run without installing your source or the SDK.
- Produce the application files that a Dockerfile or a release artefact copies in.
- Work out why an app that runs under dotnet run fails on the server with a missing framework error.
More examples
The project file behind the publish
AssemblyName decides the names of all four published files, so renaming the project folder does not rename the output. Set UseAppHost to false when something else starts the app, for example a container image whose entrypoint is already `dotnet shipping.dll`; keep the default when a person or a service manager runs the file directly.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>14.0</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- The published files are named after this, not the folder. -->
<AssemblyName>shipping</AssemblyName>
<!-- Default is true: publish also writes a native launcher.
false publishes shipping.dll on its own. -->
<UseAppHost>false</UseAppHost>
</PropertyGroup>
</Project>A framework-dependent runtimeconfig.json
This file is generated, not hand written, but reading it explains startup failures. The framework name is the shared runtime the app asks for: Microsoft.NETCore.App for a console app, Microsoft.AspNetCore.App for a web app. version plus rollForward decide which installed runtime is acceptable, which is why "You must install .NET" errors quote a version from here. configProperties carries runtime switches set in the project file.
{
"runtimeOptions": {
"tfm": "net10.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "10.0.0"
},
"rollForward": "latestPatch",
"configProperties": {
"System.GC.Server": true
}
}
}Publish, then run what you published
Always run the published folder once before handing it to a deployment. It catches the two most common mistakes at the cheapest moment: a file that the build copied but publish did not, and a configuration file that was only ever present in the project directory.
# Publish into a folder you choose rather than deep under bin/.
dotnet publish -c Release -o ./out
# Long form of the same command.
dotnet publish --configuration Release --output ./out
# Two ways to start a framework-dependent app.
dotnet ./out/shipping.dll
./out/shipping
Discussion