$npx -y skills add wshaddix/dotnet-skills --skill dotnet-cli-distributionChoosing CLI output format. AOT vs framework-dependent, RID matrix, single-file, dotnet tool.
| 1 | # dotnet-cli-distribution |
| 2 | |
| 3 | CLI distribution strategy for .NET tools: choosing between Native AOT single-file publish, framework-dependent deployment, and `dotnet tool` packaging. Runtime Identifier (RID) matrix planning for cross-platform targets (linux-x64, osx-arm64, win-x64, linux-arm64), single-file publish configuration, and binary size optimization techniques for CLI applications. |
| 4 | |
| 5 | **Version assumptions:** .NET 8.0+ baseline. Native AOT for console apps is fully supported since .NET 8. Single-file publish has been mature since .NET 6. |
| 6 | |
| 7 | **Out of scope:** Native AOT MSBuild configuration (PublishAot, ILLink descriptors, EnableAotAnalyzer, trimming) -- see [skill:dotnet-native-aot]. AOT-first application design patterns (source gen over reflection, DI choices) -- see [skill:dotnet-aot-architecture]. Multi-platform packaging formats (Homebrew, apt/deb, winget, Scoop) -- see [skill:dotnet-cli-packaging]. Release CI/CD pipeline -- see [skill:dotnet-cli-release-pipeline]. Container-based distribution -- see [skill:dotnet-containers]. General CI/CD patterns -- see [skill:dotnet-gha-patterns] and [skill:dotnet-ado-patterns]. |
| 8 | |
| 9 | Cross-references: [skill:dotnet-native-aot] for AOT compilation pipeline, [skill:dotnet-aot-architecture] for AOT-safe design patterns, [skill:dotnet-cli-architecture] for CLI layered architecture, [skill:dotnet-cli-packaging] for platform-specific package formats, [skill:dotnet-cli-release-pipeline] for automated release workflows, [skill:dotnet-containers] for container-based distribution, [skill:dotnet-tool-management] for consumer-side tool installation and manifest management. |
| 10 | |
| 11 | --- |
| 12 | |
| 13 | ## Distribution Strategy Decision Matrix |
| 14 | |
| 15 | Choose the distribution model based on target audience and deployment constraints. |
| 16 | |
| 17 | | Strategy | Startup Time | Binary Size | Runtime Required | Best For | |
| 18 | |----------|-------------|-------------|-----------------|----------| |
| 19 | | Native AOT single-file | ~10ms | 10-30 MB | None | Performance-critical CLI tools, broad distribution | |
| 20 | | Framework-dependent single-file | ~100ms | 1-5 MB | .NET runtime | Internal tools where runtime is guaranteed | |
| 21 | | Self-contained single-file | ~100ms | 60-80 MB | None | Simple distribution without AOT complexity | |
| 22 | | `dotnet tool` (global/local) | ~200ms | < 1 MB (NuGet) | .NET SDK | Developer tools, .NET ecosystem users | |
| 23 | |
| 24 | ### When to Choose Each Strategy |
| 25 | |
| 26 | **Native AOT single-file** -- the gold standard for CLI distribution: |
| 27 | - Zero dependencies on target machine (no .NET runtime needed) |
| 28 | - Fastest startup (~10ms vs ~100ms+ for JIT) |
| 29 | - Smallest binary when combined with trimming |
| 30 | - Trade-off: longer build times, no reflection unless preserved |
| 31 | - See [skill:dotnet-native-aot] for PublishAot MSBuild configuration |
| 32 | |
| 33 | **Framework-dependent deployment:** |
| 34 | - Smallest artifact size (only app code, no runtime) |
| 35 | - Users must have .NET runtime installed |
| 36 | - Best for internal/enterprise tools where runtime is managed |
| 37 | - Can still use single-file publish for convenience |
| 38 | |
| 39 | **Self-contained (non-AOT):** |
| 40 | - Includes .NET runtime in the artifact |
| 41 | - Larger binary than AOT but simpler build process |
| 42 | - Full reflection and dynamic code support |
| 43 | - Good compromise when AOT compat is difficult |
| 44 | |
| 45 | **`dotnet tool` packaging:** |
| 46 | - Distributed via NuGet -- simplest publishing workflow |
| 47 | - Users install with `dotnet tool install -g mytool` |
| 48 | - Requires .NET SDK on target (not just runtime) |
| 49 | - Best for developer-facing tools in the .NET ecosystem |
| 50 | - See [skill:dotnet-cli-packaging] for NuGet distribution details |
| 51 | |
| 52 | --- |
| 53 | |
| 54 | ## Runtime Identifier (RID) Matrix |
| 55 | |
| 56 | ### Standard CLI RID Targets |
| 57 | |
| 58 | Target the four primary RIDs for broad coverage: |
| 59 | |
| 60 | | RID | Platform | Notes | |
| 61 | |-----|----------|-------| |
| 62 | | `linux-x64` | Linux x86_64 | Most Linux servers, CI runners, WSL | |
| 63 | | `linux-arm64` | Linux ARM64 | AWS Graviton, Raspberry Pi 4+, Apple Silicon VMs | |
| 64 | | `osx-arm64` | macOS Apple Silicon | M1/M2/M3+ Macs (primary macOS target) | |
| 65 | | `win-x64` | Windows x86_64 | Windows 10+, Windows Server | |
| 66 | |
| 67 | ### Optional Extended Targets |
| 68 | |
| 69 | | RID | When to Include | |
| 70 | |-----|----------------| |
| 71 | | `osx-x64` | Legacy Intel Mac support (declining market share) | |
| 72 | | `linux-musl-x64` | Alpine Linux / Docker scratch images | |
| 73 | | `linux-musl-arm64` | Alpine on ARM64 | |
| 74 | | `win-arm64` | Windows on ARM (Surface Pro X, Snapdragon laptops) | |
| 75 | |
| 76 | ### RID Configuration in .csproj |
| 77 | |
| 78 | ```xml |
| 79 | <!-- Set per publish, not in csproj (avoids accidental RID lock-in) --> |
| 80 | <!-- Use dotnet publish -r <rid> instead --> |
| 81 | |
| 82 | <!-- If you must set a default for local development --> |
| 83 | <PropertyGroup Condition="'$(RuntimeIdentifier)' == ''"> |
| 84 | <RuntimeIdentifier>osx-arm64</RuntimeIdentifier> |
| 85 | </PropertyGroup> |
| 86 | ``` |
| 87 | |
| 88 | Publish per RID from the command line: |
| 89 | |
| 90 | ```bash |
| 91 | # Publish for each target RID |
| 92 | dotnet publish -c Release -r linux-x64 |
| 93 | dotnet publish -c Release -r linux-arm64 |
| 94 | dotnet publish -c Release -r osx-arm64 |
| 95 | dotnet publish -c Release -r |