You clone a repository, run dotnet build, and get hit with a version mismatch. The project targets an SDK you don't have. You check the global.json, download the right version from the .NET website, run the installer, restart your terminal, and try again. Meanwhile, your colleague on macOS is doing the same dance with a shell script, and your CI pipeline has its own bespoke installation logic using dotnet-install.sh. Three platforms, three approaches, one shared frustration.
Every other major ecosystem solved this years ago. Rust has rustup. Node has nvm (and fnm, and volta). Python has pyenv. Go ships a single binary that just works. .NET developers, despite having one of the most mature toolchains in the industry, have been cobbling together SDK management from a patchwork of installers, package managers, and shell scripts.
That's changing. The .NET team is building dotnetup — an official, cross-platform .NET SDK and runtime version manager. It was soft-launched at Microsoft Build 2026, and while it's still in preview, the design is clear enough to understand why this tool matters and how it's going to reshape the way teams manage their .NET installations.
What dotnetup actually does
At its core, dotnetup is a single native binary that installs, tracks, and switches between .NET SDKs and runtimes. It runs on Windows, macOS, and Linux with identical behaviour on all three. No elevation required — everything goes into a per-user directory.
The flagship command is simple:
dotnetup install
That single line reads the global.json in the current directory, determines which SDK version (and which runtimes) the project needs, and acquires them. No hunting for download links, no running platform-specific installers, no forgetting to update your PATH.
If you need a preview SDK explicitly:
dotnetup sdk install preview
The tool is compiled with native AOT, so it starts instantly and has no dependency on an existing .NET installation — solving the bootstrapping problem that plagued earlier approaches where you needed a .NET SDK to install a .NET SDK.
The problem with the status quo
Before diving deeper into dotnetup's features, it's worth cataloguing just how fragmented .NET SDK management has become. Depending on your platform and context, you might be using any combination of:
- Visual Studio — installs SDKs as workload components, but you're stuck with whatever versions the VS update cycle provides, and the install location is opaque.
- dotnet-install.sh / dotnet-install.ps1 — Microsoft's official scripts that download and extract SDKs. They work, but they're stateless. They don't track what's installed, don't clean up old versions, and require you to manage PATH yourself.
- Package managers (apt, brew, winget, chocolatey) — each has its own update cadence, its own quirks around version availability, and its own opinions about where things should live.
- Manual downloads — the .NET website. Click, download, install. Repeat for every version on every machine.
- DNVM — a community tool that tried to fill this gap, but never reached official status or widespread adoption.
The result is that a typical developer machine accumulates SDK versions like sediment layers. Run dotnet --list-sdks on any machine that's been in use for more than a year and you'll likely see five or more versions, some of which you installed deliberately and some of which arrived as passengers with other tools.
How dotnetup works with global.json
If you're already using global.json to pin SDK versions (and you should be), dotnetup slots in naturally. The global.json file declares what your project needs:
{
"sdk": {
"version": "11.0.100-preview.5",
"rollForward": "latestPatch"
}
}
When someone clones the repository and runs dotnetup install, the tool reads this file, checks whether the specified SDK is already present in the user's dotnetup-managed directory, and downloads it if not. The rollForward policy is respected — if latestPatch is set, dotnetup can satisfy the requirement with any compatible patch version already installed.
The real power shows when you bump the SDK version. Change the version in global.json, commit, and push. Every developer who pulls the change and runs dotnetup install gets the new SDK automatically. No Slack message saying "hey everyone, please update your SDK". No wiki page with platform-specific instructions. The version requirement travels with the code.
Multi-runtime testing without the headache
One of dotnetup's most practical features is its ability to install runtimes independently of the SDK. If you maintain a library that multi-targets net8.0, net9.0, and net10.0, you need all three runtimes to actually run your test suite — but you only need one SDK to build.
Previously, this meant either installing three full SDKs (wasteful and confusing) or manually downloading individual runtime packages (tedious and error-prone). With dotnetup, the tool reads your project's target frameworks and ensures the correct runtimes are available alongside your single SDK installation.
This is particularly valuable for CI pipelines, where you want deterministic builds without bloating the agent image with every SDK version under the sun.
CI/CD: where dotnetup earns its keep
The .NET SDK's own main branch already uses dotnetup in its build infrastructure. That's a strong signal — the team is eating their own dog food.
For CI pipelines, dotnetup replaces the common pattern of calling dotnet-install.sh with hard-coded version strings:
# Before: brittle, platform-specific, version hard-coded in two places
steps:
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '11.0.100-preview.5'
- run: dotnet build
# After: version comes from global.json, single source of truth
steps:
- run: dotnetup install
- run: dotnet build
The version is no longer duplicated between global.json and your CI configuration. When you bump the SDK, the CI pipeline picks it up automatically because it reads the same global.json that developers use locally.
dotnetup also enables concurrent acquisition of SDKs and runtimes, which speeds up CI provisioning compared to sequential install scripts. And because it tracks installations in a manifest, it can skip downloads for components that are already cached on the agent.
Installation tracking and cleanup
Unlike install scripts that fire-and-forget, dotnetup maintains a manifest of every SDK and runtime it has installed. This means it can:
- Audit what's present — you can inspect exactly which versions are installed, when they were acquired, and from which channel (stable vs preview).
- Clean up old versions — when you no longer need an SDK version, dotnetup can remove it cleanly rather than leaving orphaned directories scattered across your filesystem.
- Update intelligently — dotnetup knows what it installed and can update specific channels without disturbing others.
This is a significant improvement over the current state where .dotnet directories grow unbounded and developers periodically resort to rm -rf ~/.dotnet and starting fresh.
Security: signed binaries and verified downloads
The dotnetup binary itself is compiled with native AOT and distributed as a signed executable. This matters for enterprise environments where unsigned binaries trigger security alerts.
More importantly, dotnetup is designed to verify the signatures of the SDK and runtime packages it downloads. This is supply chain security done right — every artefact is verified before it's extracted and made available. The existing dotnet-install.sh script downloads archives over HTTPS but doesn't perform signature verification on the contents.
How to try it today
dotnetup is currently in internal preview. There's no one-line installer on the .NET website yet — that's the eventual goal, but not today's reality. To try it now, you'll need to build from source:
- Clone the
dotnet/sdkrepository. - Check out the
release/dnupbranch, which contains the dotnetup source. - Build the project — the native AOT executable will be produced in the output directory.
git clone https://github.com/dotnet/sdk.git
cd sdk
git checkout release/dnup
# Build following the repo's instructions
The .NET team has also published a patterns demo repository at baronfel/dotnetup-repo-patterns-demo on GitHub, which walks through three real-world scenarios: multi-runtime library testing, cross-platform web app development, and SDK version bumps. It's worth cloning to see how dotnetup integrates with typical project structures.
// NOTE
dotnetup is in preview and the CLI surface may change before the public release. The core concepts — global.json integration, user-scoped installs, manifest tracking — are settled, but specific command names and flags are still being refined.
What's on the roadmap
The design proposal in the dotnet/designs repository outlines several features planned beyond the current preview:
- Self-updating — dotnetup will be able to update itself, similar to
rustup update. - Oneshot execution — run a command against a specific SDK version without permanently installing it. Think of it as
npxfor .NET SDKs, useful for A/B testing SDK versions or reproducing bugs against a specific release. - First-class CI/CD actions — purpose-built GitHub Actions and Azure DevOps tasks that wrap dotnetup, replacing the current
setup-dotnetaction. - Agent integration — support for LLM-powered coding agents that need to provision .NET tooling on demand.
The endgame is for dotnetup to become the recommended way to get .NET onto any machine — developer workstations, CI runners, Docker containers, and cloud development environments alike.
Common pitfalls
Assuming it's publicly available. As of July 2026, dotnetup is still in internal preview. Don't plan CI migrations around it until there's a stable release. The concepts are sound, but the binary isn't distributed through official channels yet.
Confusing dotnetup with dotnet-install scripts. They solve overlapping problems but work differently. dotnet-install scripts are stateless — they download and extract, full stop. dotnetup is stateful — it tracks what's installed, respects global.json, and manages the lifecycle of installations. During the transition period, the build infrastructure uses dotnetup as the primary path with dotnet-install scripts as a fallback.
Ignoring global.json. dotnetup's power comes from reading global.json. If your repository doesn't have one, dotnetup has nothing to work with. If you haven't adopted global.json yet, now is the time — it's the foundation that makes automated SDK management possible.
Expecting it to replace Visual Studio's SDK management. For developers who work primarily within Visual Studio, the IDE will continue to manage its own SDK installations. dotnetup targets command-line workflows, CI pipelines, and scenarios where Visual Studio isn't part of the picture. The two will coexist, though dotnetup may eventually integrate with VS workload management.
Summary
- dotnetup is an official, cross-platform .NET SDK and runtime version manager being built by the .NET team inside the
dotnet/sdkrepository. - It reads
global.jsonand installs exactly the SDK and runtimes a project needs — no manual downloads, no platform-specific scripts. - User-scoped installations require no elevation, and a manifest tracks every installed component for auditing and cleanup.
- The .NET SDK's own build infrastructure already uses dotnetup, validating the tool's design in production.
- It's still in internal preview — build from source on the
release/dnupbranch to try it, or explore thebaronfel/dotnetup-repo-patterns-demorepository for usage patterns. - The long-term goal is for dotnetup to become the default way to install .NET, replacing the current patchwork of installers, scripts, and package managers.