You do not have to install a .NET preview system-wide to try it. Since the .NET 10 SDK, global.json can point dotnet at an SDK that lives in a folder of your repository (the paths key, documented in the global.json reference), and dotnet workload install run from that folder puts the workloads there too. Nothing lands in /usr/local/share/dotnet, no sudo, no wondering which SDK your real projects are on. Outside the repository nothing changes. Delete the folder and the preview was never there.
I found this when I needed the .NET 11 RC2 nightlies: I had macOS 27 and Xcode 27 before RC2 was out, RC1 could not build iOS any more, and I had to keep testing Maui.NativeStyles, the library I am working on. The dotnet/maui and dotnet/macios repositories work this way every day, and almost nobody else seems to. So I wrote dotnet-nightly-local, a script that does it for any channel and any workload (install.sh for macOS and Linux, install.ps1 for PowerShell 7 anywhere, tests for both), and this post is one pass through it: which channels exist, what each one offers, install, build, update, clean. Every number comes from runs on October 9, 2026.
curl -sSL -o ~/install.sh https://raw.githubusercontent.com/danardelean/dotnet-nightly-local/main/install.sh
chmod +x ~/install.sh
1. The channels
A channel is the name of a build lane of dotnet/dotnet: 12.0.1xx is main, already branded as .NET 12; 11.0.1xx-rc2 the RC2 release branch of .NET 11; 11.0.1xx its RTM branch. --list-channels probes every channel name the .NET build system declares and prints today's SDK version, which is the column to read: the official builds table lags, and still calls 11.0.1xx "main". Abridged:
$ ~/install.sh --list-channels
channel current daily SDK
11.0.1xx 11.0.100-rtm.26480.113
11.0.1xx-rc2 11.0.100-rc.2.26473.112
12.0.1xx 12.0.100-alpha.1.26509.101
So today there are two things to try: the next version, the .NET 12 alpha, and the nightly builds of the current one, .NET 11 RC2, which is the lane built against Xcode 27 and is due in a few days. When 13.0.1xx appears, nothing below changes but the name.
2. The workloads of each channel
--list-workloads resolves the channel's SDK version with one HEAD request, looks for every workload manifest of that version's band on the feeds an install would use, and prints the workload ids each manifest defines. It downloads nothing but the manifests, a few kilobytes each, and takes about a minute. The .NET 12 lane:
$ ~/install.sh --list-workloads --channel 12.0.1xx
SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1), channel 12.0.1xx
Feeds:
(from dotnet/maui NuGet.config: main net11.0 release/11.0.1xx-rc2)
...
Manifests (12.0.100-alpha.1) and the workloads each defines:
microsoft.net.sdk.android: 37.99.0-preview.1.135 (dotnet12)
android
microsoft.net.sdk.ios: 27.0.12277-net12-p1 (dotnet12)
ios
...
microsoft.net.workload.mono.toolchain.current: 12.0.100-alpha.1.26509.101 (dotnet12)
mobile-librarybuilder wasi-experimental wasm-experimental wasm-tools
microsoft.net.workload.mono.toolchain.net11: 12.0.100-alpha.1.26509.101 (dotnet12)
mobile-librarybuilder-net11 wasi-experimental-net11 wasm-experimental-net11 wasm-tools-net11
...
Not on any of the feeds for this band: microsoft.net.sdk.maui
Each pair is a manifest (the package that describes one family of workloads) with the build the script would install and the feed it comes from, then the workload ids you can pass to --workloads. A manifest whose workloads are all abstract (the emscripten ones, pulled in by wasm-tools) says so. The last line names the usual manifests no feed has for this band: maui, because dotnet/maui has not moved to .NET 12 yet. The other two lanes, in one line each:
-
11.0.1xx-rc2: all six SDK manifests.maui 11.0.0-rc.2.26505.5definesmaui maui-android maui-desktop maui-ios maui-maccatalyst maui-mobile maui-tizen maui-windows; iOS is27.0.12212-net11-rc.2, built for Xcode 27. -
11.0.1xx(RTM):mauiandandroidonly. No RTM manifest for iOS, Mac Catalyst, macOS or tvOS is public yet, so the lane cannot build for Apple platforms today:--workloads iosis skipped, andmauicomes with the SDK's bundled Preview 6 iOS (Xcode 26). The listing tells you before you spend the download.
3. Install
The next version, with the two workloads it has:
mkdir net12 && cd net12
~/install.sh --channel 12.0.1xx --workloads ios,android,mobile-librarybuilder-net11
SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1) in /Users/dan/net12/.dotnet
Feeds:
(from dotnet/maui NuGet.config: main net11.0 release/11.0.1xx-rc2)
...
Manifests (12.0.100-alpha.1):
microsoft.net.sdk.android: 37.99.0-preview.1.135 (dotnet12)
microsoft.net.sdk.ios: 27.0.12277-net12-p1 (dotnet12)
...
Successfully installed workload(s) ios android mobile-librarybuilder-net11.
global.json: SDK 12.0.100-alpha.1.26509.101 from .dotnet
Three and a half minutes on my connection, 7 GB in ./net12/.dotnet (the SDK and its runtime alone are about 430 MB; the rest is packs), nothing anywhere else. The third workload is the alpha being an alpha: its templates still create net11.0-ios and net11.0-android projects, and such a project needs the .NET 11 build of the mono.toolchain packs that ios and android do not pull in, the MonoTargets build tasks and the Mono AOT cross-compilers for iOS. The build names the workload that has them, mobile-librarybuilder-net11. (The manifest's name is historical: Android apps run on CoreCLR since .NET for Android 37 and its workload ships no Mono runtime any more; iOS apps still run on Mono, with NativeAOT as the option.) Ask for maui here and the script skips it with a message, since no manifest of the band defines it.
The nightly builds of the current version, same script, different channel, and maui exists:
mkdir net11 && cd net11
~/install.sh --channel 11.0.1xx-rc2 --workloads maui
Inside either folder the plain dotnet is the one in .dotnet; outside, it is whatever was on the machine:
$ dotnet --version # in net12
12.0.100-alpha.1.26509.101
$ (cd ~ && dotnet --version)
10.0.401
$ ./.dotnet/dotnet workload list
android 37.99.0-preview.1.135/12.0.100-alpha.1
ios 27.0.12277-net12-p1/12.0.100-alpha.1
mobile-librarybuilder-net11 12.0.100-alpha.1.26509.101/12.0.100-alpha.1
The workload commands have to go through the folder's own dotnet: run through the system one they look at the system install, where the nightly band does not exist, and print an empty table. Builds do not care, the MSBuild resolver finds the folder from the SDK directory.
One more file. Projects built with a nightly restore runtime packages of the same nightly version, which are on the dotnet12 (or dotnet11) feed and not on nuget.org, so give them a NuGet.config next to the project; the script prints the URL as its last line:
<configuration>
<packageSources>
<add key="dotnet12" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet12/nuget/v3/index.json" />
</packageSources>
</configuration>
4. Test
The SDK alone, no workload needed:
dotnet new console -o Hello && dotnet run --project Hello
Hello, World!
The workloads, on the .NET 12 alpha with Xcode 27 (iOS needs a Mac):
dotnet new ios -o HelloIos && dotnet build HelloIos
dotnet new android -o HelloDroid && dotnet build HelloDroid
HelloIos -> /Users/dan/net12/HelloIos/bin/Debug/net11.0-ios/iossimulator-arm64/HelloIos.dll
Build succeeded. (23 s; HelloIos.app for the simulator)
HelloDroid -> /Users/dan/net12/HelloDroid/bin/Debug/net11.0-android/HelloDroid.dll
Build succeeded. (21 s; com.companyname.HelloDroid-Signed.apk)
And the real reason I started: in the net11 folder, Maui.NativeStyles has been building for Xcode 27 since October 6, 2026 with dotnet build -f net11.0-ios, while the machine-wide .NET 10 keeps building everything else. Three things changed in the app for .NET 11 and Xcode 27, none of them in the script: .NET for Android 37 rejects SupportedOSPlatformVersion below 24 (XA4216); the iOS 27 bindings return UIFont? from SystemFontOfSize and FromDescriptor, three new warnings; and an app built with the iOS 27 SDK and no scene delegate is terminated at launch, which I covered in an earlier post. (If something you depend on pulls in Microsoft.Maui.Controls.Compatibility, pin it to 11.0.0-preview.6.26324.7: there is no later build on the feed.)
5. Update
Nightlies move daily: the alpha went from 26508.114 to 26509.101 while I was writing this. To update, run the same install command again, with --prune so that it drops what it replaces. On a folder I had installed in the morning from 26508.114, with android:
~/install.sh --channel 12.0.1xx --workloads android --prune
SDK 12.0.100-alpha.1.26509.101 (band 12.0.100-alpha.1) in /Users/dan/net12/.dotnet
Manifests (12.0.100-alpha.1):
microsoft.net.sdk.android: 37.99.0-preview.1.135 (present)
microsoft.net.sdk.ios: 27.0.12277-net12-p1 (present)
...
Workload(s) 'android' are already installed.
pruning sdk/12.0.100-alpha.1.26508.114
pruning shared/Microsoft.NETCore.App/12.0.0-alpha.1.26508.114
pruning shared/Microsoft.AspNetCore.App/12.0.0-alpha.1.26508.114
pruning host/fxr/12.0.0-alpha.1.26508.114
global.json: SDK 12.0.100-alpha.1.26509.101 from .dotnet
Sixty-five seconds, most of it the SDK download. The newer daily went in next to the old one, every manifest was still the newest build on the feeds so nothing was refetched, the workload was recognised as installed, the old SDK, runtime and host were pruned, and global.json now pins the new build, so dotnet --version in the folder says 26509.101. When a manifest has moved (the Android one went from .129 to .135 during the day), its newer version replaces the older folder and the workload's packs are installed for it; the script then shuts down the build servers of that SDK, which would otherwise keep the old manifests in memory. The install and the update are the same command, so a scripts/install-nightly.sh wrapper in the repository with the channel and workloads filled in is all a teammate needs, before and after.
6. Clean
~/install.sh --clean
Removed /Users/dan/net12/.dotnet
Removed global.json (it pointed at .dotnet)
That is the whole undo: the folder and the global.json that pointed at it, nothing else was written. It refuses a folder that holds neither a dotnet executable nor an sdk/ directory, so a typo cannot delete your project. When RC2 ships, --channel 11.0 --quality preview gives you the release itself, with the workloads from nuget.org and none of the nightly feeds.
How it works
Three standard pieces, nothing clever:
-
The SDK. Microsoft's
dotnet-install.shwith--install-dir ./.dotnetextracts the SDK and its runtime there and touches nothing else; it is the documented way to install .NET without an installer. -
The workloads.
dotnet workload installinstalls into the root of thedotnetexecutable that runs it, so./.dotnet/dotnet workload install ioslands in./.dotnet/. -
global.json.
{ "sdk": { "version": "12.0.100-alpha.1.26509.101", "paths": [ ".dotnet", "$host$" ] } }. Inside the repositorydotnetresolves to the folder; anywhere else$host$means the machine-wide install. The global.json reference describespathsas made for local SDK installations relative to a repository root, and its own example uses".dotnet"with anerrorMessagethat tells you to run./install.sh. dotnet/maui and dotnet/macios have exactly this in their ownglobal.json.
Why a script and not just dotnet-install.sh
Because the SDK alone is not enough, and each of these cost me a wrong turn. The script does what the dotnet/maui build does for each:
-
The bundled manifests are stale. A daily SDK ships baseline workload manifests months old (the newest RC2 daily still carries July's Preview 6 iOS and MAUI). The script downloads the newest
<id>.Manifest-<band>of every manifest the SDK knows and places it insdk-manifests/<band>/, then installs with--skip-manifest-update. -
The feeds are several, and some are hidden.
dotnet<major>on dnceng and nuget.org, plus whatever dotnet/maui's branch for that band lists in itsNuGet.config: release builds of dotnet/macios and dotnet/android land on isolateddarc-pub-*feeds that only that file names. The .NET 12 iOS and Android workloads still carry .NET 10 servicing packs from such builds, and dotnet/maui'smain(still a .NET 10 branch) does not name those feeds, so for an alpha the script also reads the previous major's release branches. -
A compat manifest can point at a runtime that is not public yet. The
*-net10workloads reference the .NET 10 runtime the SDK was built with, a servicing build that reaches nuget.org on Patch Tuesday. The script points them at the newest public one; those packs are not used to build for the new .NET anyway. It is the one real hack, on a throwaway SDK. -
Version sorting.
mainpublishes into the RC2 band too, andpreview.7sorts aboverc.2if you compare numbers. SemVer precedence, and prefer the band's own label. -
Build servers remember. MSBuild nodes and the compiler server outlive a build and keep the manifests they loaded; after a refresh my next build imported a targets file of a version that was gone. The script runs
dotnet build-server shutdownafter it changes anything. -
Nothing in it knows about .NET 11 or 12. The band, the feeds (
dotnet13when the time comes, plus the dotnet/maui branches of that band and of the previous major) and the compat runtime are all derived from the version it finds. Two conventions it cannot look up are what to check if a future lane looks wrong, the naming of dotnet/maui's branches and the list of manifest ids it probes on nuget.org; both are one line in the script and both can be overridden from the command line (--maui-branch,--feeds).
Caveats
-
install.shhas done every run quoted here on macOS.install.ps1has run on macOS and Linux; its Windows path is written and tested against a fake SDK, not yet on a Windows machine. -
--skip-sign-checkand hand-placed manifests bypass the installer's own checks, as the dotnet/maui build does. Read the script before running it; it writesglobal.jsonin the current directory and nothing outside the folder you name. - Everything about the alpha is a snapshot of October 9, 2026. Workloads appear and move;
--list-channelsand--list-workloadsare the truth on your day.
Credits
Both the scripts and this article were written together with Claude, Anthropic's AI assistant, in Claude Code. The runs, the Mac, the wrong turns and the decision to look at .NET 12 are mine; the first draft of most lines, the tests and a good part of the digging through the dotnet/maui and dotnet/macios build files are Claude's. Every number here comes from a run on my machine or from the feeds, checked on October 9, 2026.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more