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:

  1. Restores dependencies when necessary

  2. Reads the project configuration

  3. Compiles source code

  4. Runs applicable build-time analyzers and generators

  5. Produces build output

  6. 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:

CommandPurpose
dotnet buildBuild the project
dotnet build -c ReleaseRelease build
dotnet build --no-restoreSkip package restore
dotnet build --no-incrementalDisable incremental compilation
dotnet build -f net10.0Build a specific framework
dotnet build -r win-x64Specify runtime
dotnet build -v detailedShow detailed build information
dotnet build -v diagShow diagnostic-level information
dotnet build -o ./outputSpecify 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.

Home

Looking for 1:1

Comments

Popular posts from this blog

How to Install .NET 10 SDK on Windows, Linux, and macOS

Microsoft Agent Framework Explained: A .NET Developer’s Architecture Guide

Why Is Your ASP.NET Core API Using So Much Memory? 7 Causes and Fixes