What Does dotnet build Do? A Guide for .NET Developers
If you work with .NET, you've probably run this command hundreds of times:
dotnet build
It looks simple, but what actually happens when you run it?
Does dotnet build compile your C# code? Does it restore NuGet packages? Does it create an executable? How is it different from dotnet run or dotnet publish?
Understanding these commands is important if you're working with .NET, ASP.NET Core, CI/CD pipelines, Docker, or cloud deployments.
In this guide, we'll take a practical look at what dotnet build does and how it fits into the .NET development workflow.
What Is dotnet build?
dotnet build is a .NET CLI command that builds a .NET project or solution.
At a high level, it:
Restores dependencies when necessary
Reads the project configuration
Compiles source code
Runs applicable build-time analyzers and generators
Produces build output
Reports compilation errors and warnings
The basic syntax is:
dotnet build
For a specific project:
dotnet build MyApp.csproj
For a solution:
dotnet build MySolution.sln
What Happens When You Run dotnet build?
Suppose you have this project:
MyApp/
│
├── MyApp.csproj
├── Program.cs
├── Services/
│ └── UserService.cs
└── Models/
└── User.cs
You execute:
dotnet build
The .NET SDK performs several steps.
1. MSBuild Evaluates the Project
The first important thing to understand is that dotnet build uses MSBuild underneath.
Your .csproj file contains project configuration such as:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
MSBuild evaluates this configuration and determines things such as:
Target framework
Project references
NuGet dependencies
Compiler settings
Output paths
Build configuration
Platform settings
Analyzers
Source generators
2. NuGet Dependencies Are Restored
If required dependencies haven't been restored, dotnet build normally performs a restore as part of the build.
For example:
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
The SDK needs to resolve this package and its dependencies before compilation.
You can also explicitly restore packages:
dotnet restore
Then build without restoring:
dotnet build --no-restore
This is particularly useful in CI/CD pipelines where restore is performed as a separate step.
3. Your C# Code Is Compiled
This is the part most developers associate with dotnet build.
Consider:
Console.WriteLine("Hello .NET");
The C# compiler processes your source code and generates Intermediate Language (IL).
Conceptually:
C# source code
↓
C# compiler
↓
Intermediate Language (IL)
↓
.NET assembly
The output is usually an assembly such as:
MyApp.dll
For applications that produce executables, you'll also see supporting files in the output directory.
4. Project References Are Built
Suppose your solution contains:
MySolution
│
├── MyApp
└── MyLibrary
And MyApp.csproj contains:
<ProjectReference Include="..\MyLibrary\MyLibrary.csproj" />
When you build the solution, MSBuild understands this dependency.
Conceptually:
MyLibrary
↓
MyApp
The referenced project is built first when required.
This is one reason dotnet build is more than simply invoking the C# compiler.
5. Build Analyzers and Source Generators Can Run
Modern .NET projects can use:
Roslyn analyzers
Code generators
Source generators
Compiler warnings
Static analysis rules
For example, nullable reference types can generate compiler warnings:
string name = null;
Depending on your project configuration, the compiler may report a warning such as a possible null assignment.
You may also have analyzer packages that enforce coding standards.
For example:
CA1822
CA2000
IDE0055
These checks can run during the build.
Where Does dotnet build Put the Output?
By default, build output is placed under:
bin/
For example:
MyApp/
├── bin/
│ └── Debug/
│ └── net10.0/
└── obj/
You may see files such as:
MyApp.dll
MyApp.deps.json
MyApp.pdb
MyApp.runtimeconfig.json
The exact files depend on the project type and target framework.
What Is the obj Folder?
You will usually see another directory:
obj/
Developers sometimes delete it when troubleshooting build problems.
The obj directory contains intermediate build information.
It can include:
Generated files
MSBuild metadata
NuGet restore information
Compiler-generated artifacts
Intermediate assembly information
Project assets information
For example:
obj/
└── project.assets.json
This file contains information about resolved NuGet dependencies.
A simplified view is:
Source code
↓
obj/
↓
bin/
Think of obj as intermediate build workspace and bin as the resulting build output.
Debug vs Release
By default, dotnet build typically uses the Debug configuration.
You can explicitly specify it:
dotnet build --configuration Debug
Or:
dotnet build --configuration Release
Short form:
dotnet build -c Release
This matters because your project can define different settings for different configurations.
For example:
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<Optimize>true</Optimize>
</PropertyGroup>
A Release build is commonly used for deployment.
Does dotnet build Create an Executable?
This depends on the project type.
For example, a console application might produce:
MyApp.dll
MyApp.exe
depending on the target framework and platform.
However, don't confuse build output with a complete deployment package.
If you want to prepare an application for deployment, you generally use:
dotnet publish
dotnet build vs dotnet run
These commands have different purposes.
dotnet build
Builds the application:
dotnet build
It does not exist primarily to launch the application.
dotnet run
Builds and runs the application:
dotnet run
Conceptually:
dotnet run
↓
Restore if needed
↓
Build
↓
Run
For everyday development:
dotnet run
is convenient.
When you only want to verify compilation:
dotnet build
is more appropriate.
dotnet build vs dotnet publish
This distinction is extremely important.
dotnet build
Creates build artifacts for development and further build steps.
dotnet build -c Release
dotnet publish
Creates a deployment-ready output.
dotnet publish -c Release
A simplified workflow looks like this:
dotnet restore
↓
dotnet build
↓
dotnet test
↓
dotnet publish
↓
Deploy
What Does dotnet build --no-restore Do?
If you already restored your dependencies:
dotnet restore
you can prevent another restore:
dotnet build --no-restore
This is particularly useful in automated builds.
For example:
dotnet restore
dotnet build --no-restore
dotnet test --no-build
Separating these operations can make CI/CD workflows easier to understand and control.
What Does dotnet build --no-incremental Do?
.NET builds can avoid recompiling things that haven't changed.
You can force a non-incremental build:
dotnet build --no-incremental
This can be useful when diagnosing unusual build behavior.
However, don't use it automatically for every build. Incremental builds are designed to avoid unnecessary work.
Building a Specific Project
If your current directory contains multiple projects, specify the project:
dotnet build MyApp/MyApp.csproj
You can also build a solution:
dotnet build MySolution.sln
For newer .NET versions, solution files may also use the .slnx format.
Building for a Specific Framework
A project can target multiple frameworks:
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
You can build a specific target framework:
dotnet build --framework net10.0
or:
dotnet build -f net10.0
This is useful for multi-targeted libraries.
Building for a Specific Runtime
Runtime identifiers can be useful when building applications for a particular platform.
For example:
dotnet build -r win-x64
or:
dotnet build -r linux-x64
However, runtime-specific build behavior should not be confused with producing a self-contained deployment. Deployment requirements are better handled explicitly with dotnet publish.
Common dotnet build Options
Here are some useful options:
| Command | Purpose |
|---|---|
dotnet build | Build the project |
dotnet build -c Release | Release build |
dotnet build --no-restore | Skip package restore |
dotnet build --no-incremental | Disable incremental compilation |
dotnet build -f net10.0 | Build a specific framework |
dotnet build -r win-x64 | Specify runtime |
dotnet build -v detailed | Show detailed build information |
dotnet build -v diag | Show diagnostic-level information |
dotnet build -o ./output | Specify output directory |
What Does dotnet build -o Do?
You can specify a custom output directory:
dotnet build -o ./output
Instead of the usual:
bin/Debug/net10.0/
you can get the build output under:
output/
Be careful when using custom output paths in complex solutions because output collisions can occur if multiple projects write into the same directory.
What Happens When the Build Fails?
Suppose you introduce a syntax error:
Console.WriteLine("Hello"
Running:
dotnet build
will produce compiler errors.
A simplified example might look like:
error CS1026: ) expected
The build exits with a non-zero status.
This is important for CI/CD systems.
For example:
Developer
↓
git push
↓
CI pipeline
↓
dotnet build
↓
❌ Compilation error
↓
Pipeline fails
This prevents broken code from progressing to later stages.
Build Warnings vs Errors
Not every build message means the build failed.
You may see:
warning CS8600
or:
warning CA1822
A warning can still allow the build to succeed.
An error such as:
error CS0103
normally causes the build to fail.
You can configure projects and CI pipelines to treat certain warnings as errors.
For example:
<PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
This can help enforce code quality.
dotnet clean and dotnet build
When you encounter strange build problems, you may see advice to run:
dotnet clean
followed by:
dotnet build
dotnet clean removes build outputs from previous builds.
A common troubleshooting sequence is:
dotnet clean
dotnet restore
dotnet build
For more persistent problems, developers sometimes also remove:
bin/
obj/
and rebuild.
dotnet build in CI/CD
A typical CI pipeline might look like:
dotnet restore
dotnet build --configuration Release --no-restore
dotnet test --configuration Release --no-build
dotnet publish --configuration Release --no-build
The exact pipeline depends on your application and CI platform.
The important idea is to separate:
Restore
↓
Build
↓
Test
↓
Publish
↓
Deploy
This makes failures easier to identify.
A Simple Mental Model
If you're new to the .NET CLI, remember this:
dotnet restore
↓
Download/resolve dependencies
↓
dotnet build
↓
Compile source code
↓
Create build artifacts
↓
dotnet test
↓
Run tests
↓
dotnet publish
↓
Prepare deployment output
And:
dotnet run
↓
Build
↓
Run
Common Mistakes Developers Make
Mistake 1: Assuming Build Means Publish
This is one of the most common misunderstandings.
dotnet build
doesn't mean:
"Create everything I need to deploy this application."
For deployment, use:
dotnet publish
Mistake 2: Always Deleting bin and obj
Deleting bin and obj can solve some stale-build problems, but it shouldn't be the first solution to every build failure.
First understand the error.
Mistake 3: Ignoring Warnings
A project can successfully compile while producing hundreds of warnings.
For example:
Build succeeded.
147 Warning(s)
0 Error(s)
Technically successful doesn't necessarily mean healthy.
Treat important warnings as part of your code-quality process.
Mistake 4: Using dotnet build as a Deployment Step
Build and deployment are different concerns.
A better conceptual separation is:
Build → Test → Publish → Deploy
Frequently Asked Questions
Does dotnet build restore NuGet packages?
Normally, yes, if a restore is required. You can explicitly control this with:
dotnet build --no-restore
Does dotnet build run my application?
No.
Use:
dotnet run
to run a project.
Does dotnet build run tests?
No.
Use:
dotnet test
to execute tests.
Does dotnet build create a Docker image?
No.
A Docker image requires a separate containerization process, although modern .NET tooling can integrate application publishing with container workflows.
Is dotnet build required before dotnet publish?
In a normal workflow, you don't necessarily need to run them as two separate commands. dotnet publish performs the build work it needs unless instructed otherwise.
For example:
dotnet publish -c Release
is often sufficient.
Final Takeaway
dotnet build is much more than a command that simply compiles C#.
It coordinates the MSBuild-based build process, evaluates project configuration, resolves dependencies when required, compiles source code, processes analyzers and generators, builds project references, and produces build artifacts.
The most useful distinction to remember is:
dotnet restore
↓
Restore dependencies
dotnet build
↓
Compile and build
dotnet test
↓
Run tests
dotnet publish
↓
Prepare deployment output
dotnet run
↓
Build and run
Once you understand these differences, .NET CLI commands become much easier to use in local development, CI/CD pipelines, Docker workflows, and production deployments.
If you're learning modern .NET, understanding dotnet build is one of those fundamentals that pays off across almost every .NET project.

Comments
Post a Comment