If you’ve experimented with Rust, you already know how simple it is to get a basic toolchain in place, with the simple rustup command-line tool. The same tool works on Windows, macOS, and Linux, building a consistent environment that’s ready for you to pick up your favorite coding editor and start work.
It’s a simple enough tool, and one that’s done much to help popularize a new programming language. With rustup, you know what you’re getting, and you can start working on any laptop, anywhere.
Managing .NET environments is complex
Things are not so simple with .NET. Yes, it’s cross platform, running on Windows, macOS, and Linux. However, it’s not so easy to set up, often relying on third-party repositories and installers, as well as many different scripts that install different versions and their associated platforms. Have you installed ASP.NET Core? Do you need Aspire, or are you building a mobile application with MAUI? Everything has its own custom install route, which will likely differ depending on the target OS. To cap it off, .NET tools often need to be run with the highest level of privileges needed, requiring developers to have administrative rights.
None of that is particularly developer friendly, and it makes it hard to build .NET tooling into ephemeral environments like CI/CD build tools and runners, where you need to populate a hosted container for test and compilation, loading a new instance of the .NET CLI every time you fire up a GitHub Action — and having to run tooling with a much higher set of privileges than you want for an automated process. Things get more confusing when you’re maintaining an application with a current build of .NET at the same time as updating it for the next long-term release, all on the same development workstation.
With platform-specific toolchains and installation mechanisms, complexity gets in the way of coding and .NET becomes less of a priority, despite its cross-platform features and its maturity. It’s clearly time for a new approach to installing .NET, making it easier to set up, without requiring users to elevate permissions (helping keep their systems secure).
Up and running with dotnetup
That’s where the public preview of dotnetup comes into play. As the name suggests, dotnetup is the equivalent of rustup for .NET: one command that sets you up to start coding wherever you are, on a Windows PC, in a Linux container, or on a MacBook. Like rustup, dotnetup is a simple command-line tool that installs SDKs and runtimes, along with platform tooling. It’s configurable, so if you only need specific tools, you can install only them. And dotnetup supports working with different versions of .NET, keeping them isolated from each other, allowing you to dedicate specific workspaces to different .NET releases. It allows you to develop against specific versions, without affecting the global, machine-wide runtime used to run .NET applications on your PC.
As a command line tool, dotnetup looks and behaves the same wherever you run it. Installation is straightforward, using curl and the default shell on macOS and Linux, and PowerShell tools on Windows. The install scripts don’t set the paths for the CLI binary, so you need to copy and run the two lines of settings scripts output when the install completes. This installs the current signed build, though you can use a different set of scripts to install the current nightly release if you want to experiment with new features.
The same .NET environment wherever you need it
I installed dotnetup on three different systems: a Windows PC, a MacBook Neo, and a PC running Ubuntu. All three installs went smoothly, and when I ran the dotnetup init command, the tool showed where the SDKs and runtimes would be installed. It’s important to note that these locations are in a user’s own personal directories, and the SDKs and runtimes install in the current user context. It’s recommended to avoid using elevated account privileges. Microsoft is working on newer versions of dotnetup that will allow this for Windows.
I did find that different operating systems installed different versions of the .NET SDKs. Windows installed SDKs for .NET 9 and .NET 10, along with the familiar .NET runtime, a Windows desktop runtime, and ASP.NET. macOS installed only the .NET 10 SDK, and Linux did the same. I was unable to install ASP.NET on my Mac, even when explicitly trying to load it.
While it’s easiest to simply accept the defaults, dotnetup offers an interactive mode that provides options for managing the versions of .NET installed and the environment they run in, and for cleaning up unwanted or out-of-date versions. You can use the interactive mode to choose a .NET channel, like latest or long-term support version, or a specific version. In the latter case, you can choose major or minor releases, with the option of picking a feature band (say a 10.0.1xx series release) or a specific SDK.
This approach lets you tune your development environment to a wide set of different .NET installations, which can be useful when an organization has a locked-down image for all its PCs, allowing you to build applications that target that specific build.
Setting up SDKs and runtimes
With multiple .NET releases installed, you need to be able to target a specific SDK for testing. Windows users by default run in “everywhere” mode, which sets the default PATH and DOTNET_ROOT environment variables for a PC. The other options for setting up .NET environments are “none”, where the .NET runtime needs to be called directly from the dotnetup CLI, and “shell”, which ties environment variables to your current shell. These last two options are the only ones available for Linux and macOS installs.
Key to how dotnetup works is the idea of installation roots. Each root is a directory, with a defined architecture (likely to be either x64 or Arm64), with a set of installation channels that define what is installed. The default is in its own data directory, though you can define your own specific install paths, for example, using separate roots for .NET 9 and .NET 10. However, you cannot use a system-level .NET directory to keep your development environment separate from the one used by your desktop.
One useful feature is the ability to migrate an existing .NET environment into one that’s managed by dotnetup. If you’ve been working with a single .NET SDK, this will allow you to get your development environment under control and start associating your SDKs and runtimes with specific directories and projects.
Using dotnetup in a CI/CD pipeline
Where a tool like dotnetup comes into its own is as part of a continuous integration platform. dotnetup is designed to be completely automated. It doesn’t need a terminal, and when running in a pipeline, it disables its first-use onboarding features. By building dotnetup into scripts and ensuring that it doesn’t run interactively, you can install a specific .NET version for use in builds or tests, defining a local environment. Microsoft provides sample YAML for building dotnetup into a GitHub Action, first installing the tooling and then using it to install an SDK and build and run a test suite.
If you’re working with a dotnetup-managed runtime, you can use the dotnetup command alongside the familiar dotnet CLI to manage operations, by simply prefixing your dotnet command string with dotnetup. This approach works well in constrained environments like GitHub Actions.
A good first step, but what’s next?
For a preview, dotnetup is an interesting tool, one that scratches an itch many .NET developers have. However, there are elements missing, especially around support for more .NET runtimes and extensions. It would be nice to be able to set up, say, an Aspire environment, or one for working on .NET WebAssembly applications. As the reach of .NET has expanded, the need to manage multiple toolchains and runtimes has become essential, and dotnetup needs to support these—either directly or via an extension mechanism that would allow you to add your own choice of repositories to its global.json configuration file.
Even so, there’s a lot to like here. The tooling is easy to use, and the initial feature set is enough to get you started. While it would be nice to see integration with tools like Visual Studio Code, letting you switch .NET runtimes and SDKs with projects, features like this will need to wait until this first version gets released. Let’s hope it will be soon!
