Why restore and build are needed before publish in dockerfile autogenerated for .NET Core by VisualStudio

Viewed 886

VisualStudio generates Dockerfile for .NET Core like this:

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["src/Sandbox/Sandbox.csproj", "src/Sandbox/"]
RUN dotnet restore "src/Sandbox/Sandbox.csproj"
COPY . .
WORKDIR "/src/src/Sandbox"
RUN dotnet build "Sandbox.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Sandbox.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Sandbox.dll"]

There are three different steps with SDK there:

  • restore
  • build
  • publish

It looks like it can be simplified to one publish stage and get something like this:

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS publish
WORKDIR /src
COPY . .
RUN dotnet publish "src/Sandbox/Sandbox.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Sandbox.dll"]

Is this separation in steps necessary to make it easier to customize each step in the future, or is there any other reason to do so?

2 Answers

It looks like they are trying to optimize things - by using docker caching and layering.

Splitting off restore from build/publish is a good idea. Your code changes often, but your project files and dependencies change little. Docker uses caching for things that don't change. If you can separate your project file from the rest of your code, you can cache the "restore" operation and make future builds faster.

See build caching in the docker docs for more information about how this works.

Also take a look at this: https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-5.0#the-dockerfile

I am not sure what this gets you:

RUN dotnet build "Sandbox.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Sandbox.csproj" -c Release -o /app/publish

I feel like the publish could be combined into the previous stage. I must be missing something.

Edit: On further thinking, perhaps this lets you use the cache (again!) while customizing how you publish?

For example, if you wanted to publish in self-contained mode, or target a different runtime id, you would just have to change the publish command and not the build command. If your application is large, that might save you some time.

Splitting into restore, build, and publish steps provides better caching of docker images at each of these steps.

For example, if the csproj file has not been modified, the cached image is used for the restore step instead of building a new one:

CACHED [build 4/7] RUN dotnet restore "src/Sandbox/Sandbox.csproj"

The same can be applied to the build step as long as there are no changes in all source files:

CACHED [build 7/7] RUN dotnet build "Sandbox.csproj" -c Release -o /app/build
Related