Published on

my experiences with dotnet chiseled images

Authors

there was awesome talks in .net conf 2023 keynote. one of the videos that i got interested was about new dotnet images. you can watch the talk here.

at the time i was doing some performance optimization on a project. there was a deadline and i was working to make the project production ready. here are some of the improvements that i made:

optimizationdescription
query optimizationdetect the queries that has performance issues and create necessary indexes and check the queries. (important note: do not use select *)
update dotnet versionupdated the project to dotnet 8
remove unnecessary libraries, implementationsthis project was cloned from some template. so i had to remove some of the libraries and implementations that is not used
multi-tenant structurethis project planned to lunch on cloud so many companies would use it so we had to implement multi-tenant structure
add redisin order to improve performance i had to add redis. i also used hybrid caching as it results the better performance in some places
optimize payload size on rabbitmqfor pub/sub mechanism rabbitmq was used in this project. i reduced query size of payload to handle messages faster
memory/cpu controlsincreased memory and cpu sizes of applications and also added multi-node structure to increase availability
logging structurethere was no general logging structure used in this project. i chose serilog first but for faster results i ended up using nlog

after these optimizations one of the the things that bothered me was docker size of the project. it was 370mb at the time. so first i reduced the size by 80mb removing unused libraries and implementations. then i watched the talk in .net conf 2023 keynote about dotnet chiseled images. it was good for both performance, security. because in chiseled image there is no root user and there is no unnecessary packages. (there is no package manager so you can't use apt).

you can see the detailed explanations in this article.

also you can check the chiseled images and sizes on microsoft here.

example dockerfile i used first

# Learn about building .NET container images:
# https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md
FROM --platform=$BUILDPLATFORM  mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
ARG TARGETARCH
WORKDIR /source

# copy csproj and restore as distinct layers
COPY aspnetapp/*.csproj .
RUN dotnet restore -a $TARGETARCH

# copy everything else and build app
COPY aspnetapp/. .
RUN dotnet publish -a $TARGETARCH --no-restore -o /app


# final stage/image
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled
EXPOSE 8080
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["./aspnetapp"]

but there was an error because the project that i am working has a dependency on libicu70 library. but i found a workaround here

so final dockerfile was similar to this

FROM golang:1.20 as chisel

RUN git clone --depth 1 -b main https://github.com/canonical/chisel /opt/chisel
WORKDIR /opt/chisel
RUN go build ./cmd/chisel


FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build

RUN apt-get update \
    && apt-get install -y fdupes \
    && rm -rf /var/lib/apt/lists/*

COPY --from=chisel /opt/chisel/chisel /usr/bin/
COPY --from=mcr.microsoft.com/dotnet/nightly/runtime:6.0-jammy-chiseled / /runtime-ref

RUN mkdir /rootfs \
    && chisel cut --release "ubuntu-22.04" --root /rootfs \
        libicu70_libs \
    \
    # Remove duplicates from rootfs that exist in runtime-ref
    && fdupes /runtime-ref /rootfs -rdpN \
    \
    # Delete duplicate symlinks
    # Function to find and format symlinks w/o including root dir (format: /path/to/symlink /path/to/target)
    && getsymlinks() { find $1 -type l -printf '%p %l\n' | sed -n "s/^\\$1\\(.*\\)/\\1/p"; } \
    # Combine set of symlinks between rootfs and runtime-ref
    && (getsymlinks "/rootfs"; getsymlinks "/runtime-ref") \
        # Sort them
        | sort \
        # Find the duplicates
        | uniq -d \
        # Extract just the path to the symlink
        | cut -d' ' -f1 \
        # Prepend the rootfs directory to the paths
        | sed -e 's/^/\/rootfs/' \
        # Delete the files
        | xargs rm \
    \
    # Delete empty directories
    && find /rootfs -type d -empty -delete

WORKDIR /source

# copy csproj and restore as distinct layers
COPY *.csproj .
RUN dotnet restore

# copy and publish app and libraries
COPY . .
RUN dotnet publish -c release -o /app --no-restore


# final stage/image
FROM mcr.microsoft.com/dotnet/nightly/runtime:8.0-jammy-chiseled

ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false

COPY --from=build /rootfs /
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "dotnetapp.dll"]

after his docker image's size has been reduced by approximately 50% - now it is 180mb. and security benefits comes with it as chiseled images doesn't have a root user.