Skip to content

Deployment Single Binary

Jung Hyun, Nam edited this page May 28, 2026 · 3 revisions

Deployment — Single Binary

Cadenza's promise: whoever you hand the binary to runs your program. No .NET runtime to pre-install, no dotnet command, no extra files.

This guide applies to all five SDK variants (Cadenza, Cadenza.Worker, Cadenza.Web, Cadenza.Mcp, Cadenza.Agent). The defaults are the same: a single self-contained binary, ReadyToRun-compiled, with single-file compression enabled.

The default command

dotnet publish app.cs -r linux-x64 -c Release

Output: bin/Release/net10.0/linux-x64/publish/app — roughly 30–40 MB after compression.

That's the whole flow. The defaults that ship with Cadenza set SelfContained=true, PublishSingleFile=true, PublishReadyToRun=true, and EnableCompressionInSingleFile=true — there are no extra /p: flags to pass.

Runtime IDs

Platform RID
Linux x64 linux-x64
Linux ARM64 linux-arm64
Linux musl x64 (Alpine) linux-musl-x64
Linux musl ARM64 linux-musl-arm64
macOS x64 osx-x64
macOS Apple Silicon osx-arm64
Windows x64 win-x64
Windows ARM64 win-arm64

Cross-compile freely — no platform toolchain needed on the build host.

Tuning

Faster cold start (larger binary)

Disable compression — the binary is ~50–70 MB instead of ~30–40 MB, but startup skips decompression:

dotnet publish app.cs -r linux-x64 -c Release \
  -p:EnableCompressionInSingleFile=false

No temp extraction at runtime

By default, the single-file host extracts a small number of native libraries to a temp directory on first launch. Bundle them into the binary instead:

dotnet publish app.cs -r linux-x64 -c Release \
  -p:IncludeNativeLibrariesForSelfExtract=true

Useful in containers and read-only filesystems.

NativeAOT (opt-in)

NativeAOT compiles to native code ahead of time. Binaries shrink to ~10–30 MB and cold start drops to milliseconds.

Add one directive to the script:

#:property PublishAot=true

Then publish normally:

dotnet publish app.cs -r linux-x64 -c Release

For even smaller AOT binaries, also drop ICU:

#:property PublishAot=true
#:property InvariantGlobalization=true

AOT-clean SDKs

SDK AOT-clean?
Cadenza
Cadenza.Worker
Cadenza.Web
Cadenza.Mcp
Cadenza.Agent ⚠️ Reflection-based tool middleware in Microsoft.Extensions.AI is not AOT-safe today

For AOT-required AI workloads, compose Cadenza.Web + a manual IChatClient rather than Cadenza.Agent.

What AOT means for your code

AOT requires every type that crosses a reflection boundary to be visible at compile time:

  • JSON: use source-generated JsonSerializerContext (the Http.GetJson / Json.Parse signatures already require it).
  • Minimal API handlers: keep your handler signatures explicit; avoid dynamic and object request bodies.
  • Reflection-heavy libraries: may or may not work. Check the library's docs.

JIT vs AOT — picking one

Concern JIT (default) AOT
Binary size 30–40 MB 10–30 MB
Cold start ~100–300 ms ~10–30 ms
Build time Fast Slower (1–2 min)
All libraries work Yes Most, not all
Reflection-heavy code Fine Needs care

Default to JIT. Reach for AOT when you ship widely (CLIs, daemons in many containers) or when start latency matters (FaaS, short-lived workers).

Trimming

AOT implies trimming. For trimmed-but-not-AOT builds:

#:property PublishTrimmed=true
#:property TrimMode=full

Trimming removes unused IL from the binary. Most pure-.NET code trims cleanly; libraries that use reflection need either [DynamicallyAccessedMembers] annotations or trim-warning suppression.

Cross-compilation in CI

for rid in linux-x64 linux-arm64 osx-arm64 win-x64; do
  dotnet publish app.cs -r $rid -c Release -o "./out/$rid"
done

You get four binaries, one per platform, from the same source.

Where the file lands

bin/Release/net10.0/<RID>/publish/<name>[.exe]

with <name> taken from the .cs filename. Copy it anywhere and run.

Further reading

See also

Clone this wiki locally