Slim your dotnet dockerfile

After discovering ways to shrink your python dockerfile, I immediately cringed when I saw my baseline dotnet Docker image was about 200MB. How can this be slimmed? Thanks to Brandon Atkinson for the ideas.

The base Microsoft runtime image is a startling 190MB, so the first thing we’ll need to do is replace this image with something a bit more slim. Alpine Linux anyone? Let’s look at the minimum setup you’ll need to produce a single executable for an Alpine distro.

Publish A Single Executable

There are a few optimizations in this category to share, but first let’s configure our publish command. It should look something like this:

RUN dotnet publish \
	--runtime linux-musl-x64 \
	--self-contained true \
	-p:PublishSingleFile=true \
	-c Release \
	-o /app/publish crawler.csproj

The most relevant flags are --runtime, which lets us compile our code specifically for the Alpine architecture, --self-contained, which publishes all the dotnet runtime dependencies along with our code, and -p:PublishSingleFile, which puts everything into one executable.

Optimize Code Linking

I don’t fully understand how dotnet code is compiled, but any compiler option which leaves out what isn’t needed is bound to cut down on our image size. Add the following XML to the relevant .csproj file(s) to reduce the final output with the IL Linker tool. I’m building a console app, so that’d be the console project.

	<PropertyGroup>
    <PublishTrimmed>true</PublishTrimmed>
    <CrossGenDuringPublish>false</CrossGenDuringPublish>
	</PropertyGroup>

Include Runtime Dependencies

Our compiled code still expects a pile of libraries to be installed on our image, so we’ll have to add those manually to Alpine. It’s possible through trial-and-error that some of these might not be relevant, but that’s up to you.

FROM docker.io/alpine:3.16.4
COPY --from=build /app/publish .
RUN apk add --no-cache \
	openssh libunwind \
	nghttp2-libs libidn krb5-libs libuuid lttng-ust zlib \
	libstdc++ libintl \
	icu

Final Outcome

By publishing everything our code needs to run in a single executable, optimizing the final output to only the libraries that are linked, and adding a few machine dependencies, I was able to reduce the final image from 200MB to 52MB. That’s four for the price of one!