dotnet-artifacts-output
Reference guide for the .NET SDK artifacts output layout, which centralizes build outputs (bin/, obj/, publish/,
package/) into a single artifacts/ directory at the repo root. Available since .NET 8 as an opt-in feature.
Recommended for new projects; evaluate tradeoffs before migrating existing projects.
Prerequisites: Run [skill:dotnet-version-detection] first to confirm .NET 8+ SDK -- artifacts output layout is not
available in earlier SDK versions.
Scope
- UseArtifactsOutput opt-in and ArtifactsPath configuration
- Centralized build output layout (artifacts/bin/, artifacts/obj/, artifacts/publish/)
- Impact on CI artifact upload paths and Docker builds
- Migration tradeoffs for existing projects
Out of scope
- Source tree organization (.sln, .csproj, src/, tests/) -- see [skill:dotnet-project-structure]
Cross-references: [skill:dotnet-project-structure] for solution layout, [skill:dotnet-containers] for Dockerfile path
adjustments, [skill:dotnet-gha-build-test] for CI artifact upload paths, [skill:dotnet-scaffold-project] for generating
new projects with artifacts output enabled.
Why Use Artifacts Output
Traditional .NET build output scatters bin/ and obj/ directories throughout the source tree, one per project. The
artifacts output layout consolidates all build outputs under a single artifacts/ directory next to
Directory.Build.props.
Benefits:
- Simpler
.gitignore -- one artifacts/ entry replaces per-project bin/ and obj/ entries
- Easier clean builds -- delete one directory instead of hunting for scattered
bin//obj/ folders
- Predictable output paths -- tooling can anticipate where to find build outputs without traversing the source tree
- Cleaner source tree -- no build artifacts mixed into project directories
Tradeoffs:
- Breaking path assumptions -- existing CI pipelines, Dockerfiles, and tooling that reference
bin/Debug/net10.0/
paths must be updated
- IDE/tool compatibility -- some older tools may not resolve the new output paths correctly
- Migration effort -- existing projects require updating all hardcoded output path references
Enabling Artifacts Output
Add UseArtifactsOutput to your Directory.Build.props at the repo root:
<Project>
<PropertyGroup>
<UseArtifactsOutput>true</UseArtifactsOutput>
</PropertyGroup>
</Project>
```text
Alternatively, generate a new `Directory.Build.props` with artifacts output pre-configured:
```bash
dotnet new buildprops --use-artifacts
```bash
This creates:
```xml
<Project>
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>
</PropertyGroup>
</Project>
```text
Setting `ArtifactsPath` directly is equivalent to `UseArtifactsOutput=true` and additionally lets you customize the root
directory location.
---
## Output Path Structure
All build outputs are organized under `artifacts/` with three levels: output type, project name, and pivot
(configuration/TFM/RID).
```text
artifacts/
bin/
MyApp/
debug/ # Single-targeted project
debug_net10.0/ # Multi-targeted project
release_linux-x64/ # RID-specific build
MyApp.Core/
debug/
obj/
MyApp/
debug/
publish/
MyApp/
release/ # dotnet publish output
release_linux-x64/ # RID-specific publish
package/
release/ # NuGet .nupkg files (no project subfolder)
```text
### Output Type Directories
| Directory | Contents | Traditional equivalent |
| -------------------- | ---------------------------------------- | --------------------------------------- |
| `artifacts/bin/` | Compiled assemblies and dependencies | `<project>/bin/` |
| `artifacts/obj/` | Intermediate build files, generated code | `<project>/obj/` |
| `artifacts/publish/` | Published application output | `<project>/bin/<config>/<tfm>/publish/` |
| `artifacts/package/` | NuGet packages (`.nupkg`, `.snupkg`) | `<project>/bin/<config>/` |
### Pivot Naming
The pivot subfolder combines configuration, TFM, and RID joined by underscores. Components that are not present are
omitted:
| Scenario | Pivot | Full path example |
| ---------------------- | ------------------- | ---------------------------------------- |
| Single-targeted, debug | `debug` | `artifacts/bin/MyApp/debug/` |
| Multi-targeted, debug | `debug_net10.0` | `artifacts/bin/MyApp/debug_net10.0/` |
| Release, RID-specific | `release_linux-x64` | `artifacts/bin/MyApp/release_linux-x64/` |
| Package output | `release` | `artifacts/package/release/` |
Note: `artifacts/package/` omits the project name subfolder. The pivot includes only the configuration.
---
## Customizing the Artifacts Path
### Custom Root Directory
Set `ArtifactsPath` to change the root location:
```xml
<PropertyGroup>
<ArtifactsPath>$(MSBuildThisFileDirectory).output</ArtifactsPath>
</PropertyGroup>
```xml
This places all build outputs under `.output/` instead of `artifacts/`.
### Custom Pivot
Customize the pivot subfolder naming with `ArtifactsPivots`:
```xml
<PropertyGroup>
<ArtifactsPivots>$(ArtifactsPivots)_MyCustomPivot</ArtifactsPivots>
</PropertyGroup>
```xml
---
## Impact on .gitignore
With artifacts output enabled, simplify `.gitignore`:
```gitignore
# Artifacts output layout (replaces per-project bin/ and obj/ entries)
artifacts/
```text
This single entry replaces the traditional pattern:
```gitignore
# Traditional layout (no longer needed with artifacts output)
[Bb]in/
[Oo]bj/
```text
If using a custom `ArtifactsPath`, update the `.gitignore` entry to match.
---
## Impact on Dockerfiles
Multi-stage Dockerfiles that copy build output must reference the new path structure. See [skill:dotnet-containers] for
full Dockerfile patterns.
**Traditional paths:**
```dockerfile
COPY --from=build /app/src/MyApp/bin/Release/net10.0/publish/ .
```dockerfile
**Artifacts output paths:**
```dockerfile
COPY --from=build /app/artifacts/publish/MyApp/release/ .
```dockerfile
Key differences in Dockerfile paths:
- Output is under `artifacts/publish/` not `bin/Release/<tfm>/publish/`
- Project name becomes a subdirectory under the output type
- Configuration pivot is lowercase (`release` not `Release`)
- TFM is omitted from single-targeted project pivots
---
## Impact on CI Pipelines
CI workflows that upload build artifacts or reference output paths must be updated. See [skill:dotnet-gha-build-test]
for full CI workflow patterns.
**GitHub Actions -- upload build output:**
```yaml
- name: Publish
run: dotnet publish src/MyApp/MyApp.csproj -c Release
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: app
path: artifacts/publish/MyApp/release/
```text
**GitHub Actions -- upload NuGet packages:**
```yaml
- name: Pack
run: dotnet pack -c Release
- name: Upload packages
uses: actions/upload-artifact@v4
with:
name: packages
path: artifacts/package/release/*.nupkg
```text
**Azure DevOps -- publish artifacts:**
```yaml
- script: dotnet publish src/MyApp/MyApp.csproj -c Release
displayName: 'Publish'
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'artifacts/publish/MyApp/release/'
artifact: 'app'
```text
---
## Migration Checklist
When enabling artifacts output on an existing project:
1. **Add `UseArtifactsOutput`** to `Directory.Build.props`
2. **Update `.gitignore`** -- replace `[Bb]in/` and `[Oo]bj/` with `artifacts/`
3. **Update Dockerfiles** -- change all `COPY --from=build` paths to use `artifacts/` structure
4. **Update CI pipelines** -- fix artifact upload paths, test result paths, and publish paths
5. **Update local scripts** -- fix any shell scripts that reference `bin/` or `obj/` paths
6. **Clean old output** -- delete existing `bin/` and `obj/` directories from all projects
7. **Verify builds** -- run `dotnet build` and `dotnet publish` to confirm output appears under `artifacts/`
8. **Verify tests** -- run `dotnet test` to confirm test execution with new paths
---
## Agent Gotchas
1. **Do not hardcode TFM in artifacts output paths for single-targeted projects.** The pivot for single-targeted
projects is just the configuration (e.g., `debug`), not `debug_net10.0`. Multi-targeted projects include the TFM in
the pivot.
2. **Do not use capitalized configuration names in artifacts paths.** The artifacts layout uses lowercase pivots
(`debug`, `release`), not the traditional capitalized names (`Debug`, `Release`).
3. **Do not assume `artifacts/package/` has a project name subfolder.** Unlike `bin/`, `obj/`, and `publish/`, the
`package/` output type omits the project name level. Packages appear directly under `artifacts/package/<config>/`.
4. **Do not enable artifacts output without updating Dockerfiles and CI pipelines first.** Path changes will break
`COPY --from=build` directives and artifact upload steps that reference traditional `bin/` paths.
5. **Do not present artifacts output as the default .NET layout.** It is opt-in since .NET 8 and remains opt-in.
Recommend it for new projects; for existing projects, evaluate the migration effort against the benefits.
---
## Code Navigation (Serena MCP)
**Primary approach:** Use Serena symbol operations for efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References
- [Artifacts output layout](https://learn.microsoft.com/en-us/dotnet/core/sdk/artifacts-output)
- [Customize your build (Directory.Build.props)](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build)
- [dotnet new buildprops](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new-sdk-templates#buildprops)
1---2name: dotnet-artifacts-output3description: Configures artifacts output layout. UseArtifactsOutput, ArtifactsPath, impact on CI and Docker.4license: MIT5targets: ['*']6tags: [foundation, dotnet, skill]7version: '0.0.1'8author: 'dotnet-agent-harness'9invocable: true10claudecode:11 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']12codexcli:13 short-description: '.NET skill guidance for foundation tasks'14opencode:15 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']16copilot: {}17geminicli: {}18antigravity: {}19category: fundamentals20subcategory: coding-standards21---2223# dotnet-artifacts-output2425Reference guide for the .NET SDK artifacts output layout, which centralizes build outputs (`bin/`, `obj/`, `publish/`,26`package/`) into a single `artifacts/` directory at the repo root. Available since .NET 8 as an opt-in feature.27Recommended for new projects; evaluate tradeoffs before migrating existing projects.2829**Prerequisites:** Run [skill:dotnet-version-detection] first to confirm .NET 8+ SDK -- artifacts output layout is not30available in earlier SDK versions.3132## Scope3334- UseArtifactsOutput opt-in and ArtifactsPath configuration35- Centralized build output layout (artifacts/bin/, artifacts/obj/, artifacts/publish/)36- Impact on CI artifact upload paths and Docker builds37- Migration tradeoffs for existing projects3839## Out of scope4041- Source tree organization (.sln, .csproj, src/, tests/) -- see [skill:dotnet-project-structure]4243Cross-references: [skill:dotnet-project-structure] for solution layout, [skill:dotnet-containers] for Dockerfile path44adjustments, [skill:dotnet-gha-build-test] for CI artifact upload paths, [skill:dotnet-scaffold-project] for generating45new projects with artifacts output enabled.4647---4849## Why Use Artifacts Output5051Traditional .NET build output scatters `bin/` and `obj/` directories throughout the source tree, one per project. The52artifacts output layout consolidates all build outputs under a single `artifacts/` directory next to53`Directory.Build.props`.5455Benefits:5657- **Simpler `.gitignore`** -- one `artifacts/` entry replaces per-project `bin/` and `obj/` entries58- **Easier clean builds** -- delete one directory instead of hunting for scattered `bin/`/`obj/` folders59- **Predictable output paths** -- tooling can anticipate where to find build outputs without traversing the source tree60- **Cleaner source tree** -- no build artifacts mixed into project directories6162Tradeoffs:6364- **Breaking path assumptions** -- existing CI pipelines, Dockerfiles, and tooling that reference `bin/Debug/net10.0/`65 paths must be updated66- **IDE/tool compatibility** -- some older tools may not resolve the new output paths correctly67- **Migration effort** -- existing projects require updating all hardcoded output path references6869---7071## Enabling Artifacts Output7273Add `UseArtifactsOutput` to your `Directory.Build.props` at the repo root:7475````xml7677<Project>78 <PropertyGroup>79 <UseArtifactsOutput>true</UseArtifactsOutput>80 </PropertyGroup>81</Project>8283```text8485Alternatively, generate a new `Directory.Build.props` with artifacts output pre-configured:8687```bash8889dotnet new buildprops --use-artifacts9091```bash9293This creates:9495```xml9697<Project>98 <PropertyGroup>99 <ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath>100 </PropertyGroup>101</Project>102103```text104105Setting `ArtifactsPath` directly is equivalent to `UseArtifactsOutput=true` and additionally lets you customize the root106directory location.107108---109110## Output Path Structure111112All build outputs are organized under `artifacts/` with three levels: output type, project name, and pivot113(configuration/TFM/RID).114115```text116117artifacts/118 bin/119 MyApp/120 debug/ # Single-targeted project121 debug_net10.0/ # Multi-targeted project122 release_linux-x64/ # RID-specific build123 MyApp.Core/124 debug/125 obj/126 MyApp/127 debug/128 publish/129 MyApp/130 release/ # dotnet publish output131 release_linux-x64/ # RID-specific publish132 package/133 release/ # NuGet .nupkg files (no project subfolder)134135```text136137### Output Type Directories138139| Directory | Contents | Traditional equivalent |140| -------------------- | ---------------------------------------- | --------------------------------------- |141| `artifacts/bin/` | Compiled assemblies and dependencies | `<project>/bin/` |142| `artifacts/obj/` | Intermediate build files, generated code | `<project>/obj/` |143| `artifacts/publish/` | Published application output | `<project>/bin/<config>/<tfm>/publish/` |144| `artifacts/package/` | NuGet packages (`.nupkg`, `.snupkg`) | `<project>/bin/<config>/` |145146### Pivot Naming147148The pivot subfolder combines configuration, TFM, and RID joined by underscores. Components that are not present are149omitted:150151| Scenario | Pivot | Full path example |152| ---------------------- | ------------------- | ---------------------------------------- |153| Single-targeted, debug | `debug` | `artifacts/bin/MyApp/debug/` |154| Multi-targeted, debug | `debug_net10.0` | `artifacts/bin/MyApp/debug_net10.0/` |155| Release, RID-specific | `release_linux-x64` | `artifacts/bin/MyApp/release_linux-x64/` |156| Package output | `release` | `artifacts/package/release/` |157158Note: `artifacts/package/` omits the project name subfolder. The pivot includes only the configuration.159160---161162## Customizing the Artifacts Path163164### Custom Root Directory165166Set `ArtifactsPath` to change the root location:167168```xml169170<PropertyGroup>171 <ArtifactsPath>$(MSBuildThisFileDirectory).output</ArtifactsPath>172</PropertyGroup>173174```xml175176This places all build outputs under `.output/` instead of `artifacts/`.177178### Custom Pivot179180Customize the pivot subfolder naming with `ArtifactsPivots`:181182```xml183184<PropertyGroup>185 <ArtifactsPivots>$(ArtifactsPivots)_MyCustomPivot</ArtifactsPivots>186</PropertyGroup>187188```xml189190---191192## Impact on .gitignore193194With artifacts output enabled, simplify `.gitignore`:195196```gitignore197198# Artifacts output layout (replaces per-project bin/ and obj/ entries)199artifacts/200201```text202203This single entry replaces the traditional pattern:204205```gitignore206207# Traditional layout (no longer needed with artifacts output)208[Bb]in/209[Oo]bj/210211```text212213If using a custom `ArtifactsPath`, update the `.gitignore` entry to match.214215---216217## Impact on Dockerfiles218219Multi-stage Dockerfiles that copy build output must reference the new path structure. See [skill:dotnet-containers] for220full Dockerfile patterns.221222**Traditional paths:**223224```dockerfile225226COPY --from=build /app/src/MyApp/bin/Release/net10.0/publish/ .227228```dockerfile229230**Artifacts output paths:**231232```dockerfile233234COPY --from=build /app/artifacts/publish/MyApp/release/ .235236```dockerfile237238Key differences in Dockerfile paths:239240- Output is under `artifacts/publish/` not `bin/Release/<tfm>/publish/`241- Project name becomes a subdirectory under the output type242- Configuration pivot is lowercase (`release` not `Release`)243- TFM is omitted from single-targeted project pivots244245---246247## Impact on CI Pipelines248249CI workflows that upload build artifacts or reference output paths must be updated. See [skill:dotnet-gha-build-test]250for full CI workflow patterns.251252**GitHub Actions -- upload build output:**253254```yaml255256- name: Publish257 run: dotnet publish src/MyApp/MyApp.csproj -c Release258259- name: Upload artifact260 uses: actions/upload-artifact@v4261 with:262 name: app263 path: artifacts/publish/MyApp/release/264265```text266267**GitHub Actions -- upload NuGet packages:**268269```yaml270271- name: Pack272 run: dotnet pack -c Release273274- name: Upload packages275 uses: actions/upload-artifact@v4276 with:277 name: packages278 path: artifacts/package/release/*.nupkg279280```text281282**Azure DevOps -- publish artifacts:**283284```yaml285286- script: dotnet publish src/MyApp/MyApp.csproj -c Release287 displayName: 'Publish'288289- task: PublishPipelineArtifact@1290 inputs:291 targetPath: 'artifacts/publish/MyApp/release/'292 artifact: 'app'293294```text295296---297298## Migration Checklist299300When enabling artifacts output on an existing project:3013021. **Add `UseArtifactsOutput`** to `Directory.Build.props`3032. **Update `.gitignore`** -- replace `[Bb]in/` and `[Oo]bj/` with `artifacts/`3043. **Update Dockerfiles** -- change all `COPY --from=build` paths to use `artifacts/` structure3054. **Update CI pipelines** -- fix artifact upload paths, test result paths, and publish paths3065. **Update local scripts** -- fix any shell scripts that reference `bin/` or `obj/` paths3076. **Clean old output** -- delete existing `bin/` and `obj/` directories from all projects3087. **Verify builds** -- run `dotnet build` and `dotnet publish` to confirm output appears under `artifacts/`3098. **Verify tests** -- run `dotnet test` to confirm test execution with new paths310311---312313## Agent Gotchas3143151. **Do not hardcode TFM in artifacts output paths for single-targeted projects.** The pivot for single-targeted316 projects is just the configuration (e.g., `debug`), not `debug_net10.0`. Multi-targeted projects include the TFM in317 the pivot.3182. **Do not use capitalized configuration names in artifacts paths.** The artifacts layout uses lowercase pivots319 (`debug`, `release`), not the traditional capitalized names (`Debug`, `Release`).3203. **Do not assume `artifacts/package/` has a project name subfolder.** Unlike `bin/`, `obj/`, and `publish/`, the321 `package/` output type omits the project name level. Packages appear directly under `artifacts/package/<config>/`.3224. **Do not enable artifacts output without updating Dockerfiles and CI pipelines first.** Path changes will break323 `COPY --from=build` directives and artifact upload steps that reference traditional `bin/` paths.3245. **Do not present artifacts output as the default .NET layout.** It is opt-in since .NET 8 and remains opt-in.325 Recommend it for new projects; for existing projects, evaluate the migration effort against the benefits.326327---328329330331## Code Navigation (Serena MCP)332333**Primary approach:** Use Serena symbol operations for efficient code navigation:3343351. **Find definitions**: `serena_find_symbol` instead of text search3362. **Understand structure**: `serena_get_symbols_overview` for file organization3373. **Track references**: `serena_find_referencing_symbols` for impact analysis3384. **Precise edits**: `serena_replace_symbol_body` for clean modifications339340**When to use Serena vs traditional tools:**341- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits342- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations343- ✅ **Fallback**: If Serena unavailable, traditional tools work fine344345**Example workflow:**346```text347# Instead of:348Read: src/Services/OrderService.cs349Grep: "public void ProcessOrder"350351# Use:352serena_find_symbol: "OrderService/ProcessOrder"353serena_get_symbols_overview: "src/Services/OrderService.cs"354```355## References356357- [Artifacts output layout](https://learn.microsoft.com/en-us/dotnet/core/sdk/artifacts-output)358- [Customize your build (Directory.Build.props)](https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build)359- [dotnet new buildprops](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-new-sdk-templates#buildprops)360````