Publishing and deployment
Framework-dependent versus self-contained output, containerising a service, and the trimming and AOT trade-offs you must measure.
Choosing a publish mode
| Mode | Command shape | Trade-off |
|---|---|---|
| Framework-dependent | dotnet publish -c Release | Smallest output; the host must have a compatible runtime installed |
| Self-contained | -r linux-x64 --self-contained true | Ships the runtime; large (roughly 60-80 MB) but no runtime prerequisite |
| Single file | -p:PublishSingleFile=true | One executable to copy; extraction cost and slower first start unless combined with R2R |
| ReadyToRun | -p:PublishReadyToRun=true | Ahead-of-time compiled IL for faster startup; bigger files, more build time |
| Native AOT | -p:PublishAot=true | No runtime, minimal memory and fast start; no dynamic loading, limited reflection |
# framework-dependent: the default production choice
dotnet publish src/Shop.Api -c Release -o out
# self-contained for a host you do not control
dotnet publish src/Shop.Api -c Release -r linux-x64 --self-contained true -o out
# container image that runs on the runtime-only base image
dotnet publish src/Shop.Api -c Release -r linux-x64 --self-contained false -o out- Set
InvariantGlobalization=truewhen you do not need culture-specific collation — it drops ICU and shrinks self-contained output substantially. -opointing outside the project directory avoids the published files being picked up by the next glob-based build.- Publish with the same configuration that produced your tests. Rebuilding differently for deployment invalidates the testing you did.
Containers and hosts
A multi-stage Dockerfile keeps the SDK out of the runtime image. The build stage restores against the lock file, publishes, and the final stage copies only the output.
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.sln ./
COPY src/Shop.Api/*.csproj src/Shop.Api/
COPY src/Shop.Core/*.csproj src/Shop.Core/
RUN dotnet restore src/Shop.Api/Shop.Api.csproj
COPY . .
RUN dotnet publish src/Shop.Api/Shop.Api.csproj -c Release -o /app --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
USER $APP_UID
ENTRYPOINT ["dotnet", "Shop.Api.dll"]- Copy project files and restore before copying source so the package layer is cached across code changes.
- The
aspnetruntime image is smaller than the SDK image and contains no compiler. - Run as a non-root user; the official images define
APP_UIDfor exactly that. - Read the port and connection strings from environment variables so the same image promotes from staging to production.
⚠️
Framework-dependent output and the runtime image version must stay in step. Publishing for
net8.0 then running on a net7.0 runtime fails at startup with a missing-framework error that no amount of container tweaking will fix.Trimming and native AOT
Trimming removes unreferenced code, and native AOT compiles ahead of time into a native binary. Both rely on the compiler proving what is reachable, so anything reflective breaks unless it is preserved or replaced.
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
<PublishAot>true</PublishAot>
<InvariantGlobalization>true</InvariantGlobalization>
<StackTraceSupport>false</StackTraceSupport>
</PropertyGroup>// reflection-based serialisation is the classic AOT casualty
// prefer a source-generated context
[JsonSerializable(typeof(Order))]
[JsonSerializable(typeof(List<Order>))]
internal partial class ShopJsonContext : JsonSerializerContext
{
}
// register it once
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.TypeInfoResolverChain.Insert(0, ShopJsonContext.Default));- Publish warnings starting with
IL2name the exact call site that cannot be statically analysed — fix them, do not suppress them. - Measure before committing to AOT: a CLI tool benefits enormously, a reflection-heavy framework integration usually does not.
- Keep the JIT build as your default. AOT is a deployment optimisation, not an architectural requirement.
FAQ
Self-contained or framework-dependent?
Framework-dependent in containers and on hosts you manage, because the base image already carries the runtime and patching is a tag change. Self-contained when you hand someone a binary for a machine you cannot provision.
Should I publish ReadyToRun?
If cold start matters. It typically cuts startup noticeably at the cost of a larger output and a slower build; measure your own service rather than trusting a general figure.
Related
The dotnet CLI workflow Minimal APIs
Last refreshed 2026-09-18.