Framework-Dependent vs Self-Contained Deployment
A framework-dependent deployment (FDD, the default with dotnet publish) produces a small output that relies on the shared .NET runtime already being installed on the target machine, invoked via dotnet myapp.dll. A self-contained deployment (SCD) bundles the entire .NET runtime and all required libraries alongside the app, so it runs on a machine with no .NET installed at all — at the cost of a substantially larger output (often 60-100+ MB) and the responsibility of applying runtime security patches yourself by republishing, since the app no longer picks up shared runtime updates automatically.
Cricket analogy: A framework-dependent deployment is like a touring player who assumes the host ground already has proper practice nets and equipment available, while a self-contained deployment is like a player who packs their own full training setup, ready to perform anywhere regardless of what the venue provides.
Publishing a Self-Contained App
A self-contained publish requires specifying a Runtime Identifier (RID) so the correct platform-specific runtime bits are bundled: dotnet publish -c Release -r linux-x64 --self-contained true, with common RIDs including win-x64, linux-x64, linux-arm64, and osx-arm64 for Apple Silicon Macs. Because the RID must match the exact target OS and architecture, self-contained apps are not portable across platforms the way framework-dependent DLLs are — you must publish a separate output per target RID if you need to support multiple platforms, unlike an FDD output which just needs any matching dotnet runtime installed.
Cricket analogy: Publishing a separate output per RID (win-x64, linux-x64, osx-arm64) is like a kit manufacturer producing distinctly different boot models calibrated for grass, clay, and matting surfaces — one universal boot doesn't perform correctly on every playing surface.
Trimming and Single-File Publishing
PublishTrimmed (via -p:PublishTrimmed=true, only valid for self-contained apps) uses static analysis to remove unused assemblies and IL from the bundled runtime, meaningfully shrinking output size, though it can break code relying on unreferenced reflection unless annotated with DynamicallyAccessedMembers or explicitly rooted. PublishSingleFile bundles the app and its dependencies into one executable for simpler distribution, and PublishReadyToRun (R2R) precompiles IL to native code ahead of time, trading a larger output for meaningfully faster cold-start time since less JIT compilation happens at first run.
Cricket analogy: Trimming unused assemblies is like a touring squad's selectors cutting players from the travel party who won't actually be needed for that specific series — a leaner squad (smaller binary) that still covers every role that's genuinely required.
When to Choose Self-Contained Deployment
Self-contained deployment makes sense when you're shipping to machines that may not have .NET installed and you don't control (or don't want to require) that installation — desktop software distributed to end users, appliances, or air-gapped environments — or when you need multiple apps on the same machine pinned to different, potentially conflicting .NET versions side by side. The tradeoff is real: larger deployment artifacts, longer publish/upload times, and the operational burden of re-publishing every app individually to pick up .NET runtime security patches, versus a framework-dependent deployment where a single shared-runtime update on the host patches every app that depends on it at once.
Cricket analogy: Choosing self-contained for an air-gapped environment is like a team preparing for a tour to a remote ground with no local support staff — you bring everything you'll possibly need because you can't count on resupply once you arrive.
# Framework-dependent (default) - requires .NET runtime installed on target
dotnet publish -c Release -o ./out/fdd
# Self-contained, single-file, trimmed, ReadyToRun for Linux x64
dotnet publish -c Release -r linux-x64 \
--self-contained true \
-p:PublishSingleFile=true \
-p:PublishTrimmed=true \
-p:PublishReadyToRun=true \
-o ./out/scd-linux-x64
# Self-contained for Windows x64
dotnet publish -c Release -r win-x64 --self-contained true -o ./out/scd-win-x64
The Runtime Identifier (RID) catalog covers combinations like win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, and osx-arm64. Since .NET 6, you can also publish 'portable RIDs' (like linux-x64 without a specific distro qualifier) that work across most modern Linux distributions rather than needing a distro-specific RID.
PublishTrimmed's static analysis can't always see code reached only through reflection, dependency injection container resolution, or Type.GetType with a runtime-computed string — trimming such an app can throw MissingMethodException at runtime for code paths that worked fine untrimmed. Test trimmed builds thoroughly, and use [DynamicallyAccessedMembers] annotations or explicit trimmer root descriptors for code the trimmer can't statically discover.
- Framework-dependent deployment (FDD) is smaller but requires the matching .NET runtime pre-installed on the target machine.
- Self-contained deployment (SCD) bundles the entire runtime, running on machines with no .NET installed, at the cost of a much larger output.
- SCD requires specifying a Runtime Identifier (RID) like win-x64, linux-x64, or osx-arm64, and isn't portable across platforms like an FDD output.
- PublishTrimmed removes unused IL via static analysis but can break reflection-dependent code without proper trimmer annotations.
- PublishSingleFile bundles the app and dependencies into one executable; PublishReadyToRun precompiles IL to native code for faster startup.
- SCD is well suited to end-user desktop software, appliances, and air-gapped environments where you can't rely on a pre-installed runtime.
- SCD shifts the burden of applying runtime security patches to re-publishing each app individually, unlike FDD's shared-runtime patching model.
Practice what you learned
1. What is the key requirement for a framework-dependent deployment to run on a target machine?
2. What must you specify when publishing a self-contained .NET app?
3. What risk does PublishTrimmed introduce that requires careful testing?
4. What does PublishReadyToRun (R2R) trade off to improve cold-start performance?
5. Which scenario is the best fit for self-contained deployment?
Was this page helpful?
You May Also Like
.NET and Docker
Learn how to containerize .NET applications with multi-stage Dockerfiles, choose the right base images, and use the SDK's built-in container publishing.
Console Apps in .NET
Learn how .NET console applications are structured, how they read input and produce output, and how they're built and published as standalone command-line tools.
Class Libraries and NuGet Packages
Understand how to build reusable .NET class libraries, reference them across projects, and package and publish them as NuGet packages.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics