How to efficiently build multiple docker images from a large solution? - docker

I have a large .NET solution and want to efficiently build multiple docker images from it for a few app projects.
The Dockerfile for the first project looks like this:
# Solution common build steps
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /app
COPY . .
RUN setup-build-env.sh # Sets up additional NuGet feeds
RUN dotnet restore Linux.slnf
# Project specific build steps
RUN cd src/Category1/src/Project1 && dotnet publish -c Release -o publish
FROM mcr.microsoft.com/dotnet/runtime:3.1 AS base
WORKDIR /app
COPY --from=build-env /app/src/Category1/src/Project1/publish .
ENTRYPOINT dotnet Project1.dll
The parts up until including RUN dotnet restore Linux.slnf will be identical for every project image.
What I'm worried about most is the large (~400MB) COPY . . operation that seems to need to be repeated everytime this Dockerfile is built.
Each app project to be turned into a container references multiple projects in the solution, so just COPYing the single project in question won't work.
How do I efficiently build multiple app project images from my large solution?

Build a custom image with the common part
Common image
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build-env
WORKDIR /app
COPY . .
RUN setup-build-env.sh # Sets up additional NuGet feeds
RUN dotnet restore Linux.slnf
docker build -t common.image:1.0 .
Project image
FROM common.image:1.0
RUN cd src/Category1/src/Project1 && dotnet publish -c Release -o publish
FROM mcr.microsoft.com/dotnet/runtime:3.1 AS base
WORKDIR /app
COPY --from=build-env /app/src/Category1/src/Project1/publish .
ENTRYPOINT dotnet Project1.dll
Obviously if the solution changes you must rebuild the common image
If ALL project has the SAME Dockerfile (change only the name) you can use the ARG

Related

Order of stages in Dockerfile for .NET Core

Being fairly new to Docker, I'm currently reading several tutorials and there are two things I don't really understand. According to the official Docker documentation, the Dockerfile could look like this:
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY ../engine/examples ./
RUN dotnet publish -c Release -o out
# Build runtime image
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
The first stage is to import the sdk and the second is to import the dotnet runtime. However, in other tutorials, e.g. this one from Jetbrains, the order is reversed; first the runtime is imported and subsequently the sdk is imported.
First question; is there a preferred order of the imports (and why)?
Second question, in the example above, the csproj is first copied, dotnet restore is run, and then all other files are copied before building the application. Why not just copy everything at once?
The way they're structured has a lot to do with maximizing the use of cached layers from previous builds to make the build go faster and using a runtime base image that's smaller that a full SDK image.
If you don't care about the build speed and image size, you can do something really simple like
FROM mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR /src
COPY . .
RUN dotnet publish -o out
WORKDIR /src/out
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
This Dockerfile will have to build everything every time you change a file. You can speed up the restore of Nuget packages by realising that you rarely change the .csproj file.
FROM mcr.microsoft.com/dotnet/sdk:6.0
WORKDIR /src
COPY aspnetapp.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -o out
WORKDIR /src/out
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
Now when you just change some code (but not the .csproj file), Docker will see that up until COPY . . everything is the same as when you last ran docker build, so it'll just take the layers from the cache and you won't have to actually run the dotnet restore saving you some time.
To save some space on the final image, we can use the aspnet image as the base, rather than the sdk image. To do that we can use a multistage Dockerfile and copy files from the first stage to the last (and final) stage like this
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY aspnetapp.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -o out
FROM mcr.microsoft.com/dotnet/aspnet:6.0
WORKDIR /app
COPY --from=build /src/out /app
ENTRYPOINT ["dotnet", "aspnetapp.dll"]
Now the final image is smaller, so it takes up fewer resources when we run it.

Did you mean to run dotnet SDK commands? Please install dotnet SDK from

I am trying to dockerize my Angular ASP.NET Core WebAPI.
I have the following dockerfile created:
FROM mcr.microsoft.com/dotnet/core/sdk:2.2.105 AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY src/Fightplan_v1/Fightplan_v1.csproj ./
# Copy everything else and build
COPY . ./
RUN dotnet restore
RUN dotnet publish -c Release -o /app
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
WORKDIR /app
COPY --from=build-env /app .
ENTRYPOINT ["dotnet", "Fightplan_v1.dll"]
The structure of my project is as follows:
The WebApi project is located inside the "src/Fightplan_v1" folder.
I am able to build my image fine using the following command:
docker build -f fp.dockerfile -t test .
When I try to run the image using:
docker run -it test
I get the following error:
Did you mean to run dotnet SDK commands? Please install dotnet SDK from:
https://go.microsoft.com/fwlink/?LinkID=798306&clcid=0x409
The project is build on dotnet core 2.2.105 so what this ofcourse tells me is that the image does not have the necessary dotnet core sdk installed. But as you can see in the dockerfile the sdk version I am downloading is 2.2.105.
I read here that the ENTRYPOINT is case sensitive and I have checked this several times now.
If I do a dotnet publish locally and go into the bin/Debug/netcoreapp2.2 I will find a DLL file called "Fightplan_v1.dll". Which is the name I assume I need to add to ENTRYPOINT?
I might just be missing some other steps here but I am not sure what.
Any help would be much appreciated!
I think your docker file should be like this
ENTRYPOINT ["Fightplan_v1.dll"]
Or if this not work try this
CMD ASPNETCORE_URLS=http://*:$PORT dotnet Fightplan_v1.dll
I was missing a COPY . . in the dockerfile after dotnet restore. I guess I was missing some .cs files in order for it to be compiled correctly.
The working dockerfile ended up looking like this:
FROM mcr.microsoft.com/dotnet/core/sdk:2.2.105 AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY src/Fightplan_v1/Fightplan_v1.csproj ./
# Copy everything else and build
COPY . ./
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
WORKDIR /app
COPY --from=build-env /app .
ENTRYPOINT ["dotnet", "Fightplan_v1.dll"]

"An assembly specified in the application dependencies manifest was not found" using docker

I am trying to dockerize an an angular aspnet core 2.2 webapi using the following dockerfile:
FROM mcr.microsoft.com/dotnet/core/sdk:2.2.105 AS build
WORKDIR /src
# Copy csproj and restore as distinct layers
COPY ["Fightplan_v1/Fightplan_v1.csproj", "Fightplan_v1/"]
COPY ["Fightplan_v1.Autogenerate/Fightplan_v1.Autogenerate.csproj", "Fightplan_v1.Autogenerate/"]
COPY ["Fightplan_v1.Autogenerate.Test/Fightplan_v1.Autogenerate.Test.csproj", "Fightplan_v1.Autogenerate.Test/"]
COPY ["Fightplan_v1.Database/Fightplan_v1.Database.csproj", "Fightplan_v1.Database/"]
COPY ["Fightplan_v1.Helpers/Fightplan_v1.Helpers.csproj", "Fightplan_v1.Helpers/"]
COPY ["Fightplan_v1.Jobs/ConvertImagestoBlob/Fightplan_v1.ConvertImagestoBlob.csproj", "Fightplan_v1.Jobs/ConvertImagestoBlob/"]
COPY ["Fightplan_v1.Models/Fightplan_v1.Models.csproj", "Fightplan_v1.Models/"]
COPY ["Fightplan_v1.Shared/Fightplan_v1.Shared.csproj", "Fightplan_v1.Shared/"]
RUN dotnet restore "Fightplan_v1/Fightplan_v1.csproj"
# Copy everything else and build
COPY . .
WORKDIR "/src/Fightplan_v1"
RUN dotnet build "Fightplan_v1.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "Fightplan_v1.csproj" -c Release -o /app
# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS runtime
WORKDIR /app
COPY --from=build /app .
EXPOSE 80
ENTRYPOINT ["dotnet", "Fightplan_v1.dll"]
The build command used is:
docker build -f .\fp.dockerfile -t test .
The build goes through fine but when I try to run it using:
docker run -p 5100:80 -it test
I get the following error:
I have tried:
Adding -r linux-x64 to the end of publish to define the runtime: RUN dotnet publish "Fightplan_v1.csproj" -c Release -o /app -r linux-x64
Adding <PublishWithAspNetCoreTargetManifest>false</PublishWithAspNetCoreTargetManifest> to my .csproj file
None of the above fixes work unfortunately.
Since this is combined poject of an angular application and a webapi I might be missing some installations/dependencies?
You are copying the runtime files from wrong layer
COPY --from=build /app .
Try to change it to
COPY --from=publish /app .
Also, you probably need to separate out path in build and publish steps:
RUN dotnet build "Fightplan_v1.csproj" -c Release -o /app/build
RUN dotnet publish "Fightplan_v1.csproj" -c Release -o /app/publish
and then copy runtime files from the publish folder
COPY --from=publish /app/publish .
Edit. Basically, you need to copy publish artifacts into runtime, not build. Build will produce deps.json, which will lists all external dependencies, and during runtime this libraries are resolving using NuGet cache, if it is empty you will see an error. To copy dependencies along with project files you need to run publish.
So I assume, that theoretically dotnet build can be omitted, and is using only to discover compilation errors on early stage of docker build.

Auto generated Dockerfile for Web .net core application

In my opinion autogenerated Dockerfile for Web .net core application is too large, but why? Why Microsoft decided to create it like this?
This is autogenerated Dockerfile when we add flag "Add docker support" during App creation:
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster AS build
WORKDIR /src
COPY ["app/app.csproj", "app/"]
RUN dotnet restore "app/app.csproj"
COPY . .
WORKDIR "/src/app"
RUN dotnet build "app.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "app.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "app.dll"]
And in my opinion it can looks like this:
FROM mcr.microsoft.com/dotnet/core/sdk:3.0-buster as build
WORKDIR /src
COPY ["app/app.csproj", "app/"]
RUN dotnet restore "app/app.csproj"
COPY . .
WORKDIR "/src/app"
RUN dotnet build "app.csproj" -c Release -o /app/build
RUN dotnet publish "app.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/core/aspnet:3.0-buster-slim
WORKDIR /app
EXPOSE 80
EXPOSE 443
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "app.dll"]
Why Microsoft decided to first - get the aspnet:3.0-buster-slim just to expose ports and use them later as final? It would be much shorter just to get this image as the last step as in my example. Also do we need double From for the sdk:3.0-buster (fist named as build, second as publish)? It's possible to add multiple RUN one by one as in my example.
Maybe there is some tech suggestions why they decide to do that?
Thanks!
A Dockerfile is a series of steps used by the docker build . command. There are at a minimum three steps required:
FROM some-base-image
COPY some-code-or-local-content
CMD the-entrypoint-command
As our application becomes more and more complex additional steps are added. Like restoring packages and dependencies. Commands like below are used for that:
RUN dotnet restore
-or-
RUN npm install
Or the likes. As it becomes more difficult the image build time will increase and the image size itself will increase.
Docker build steps generates multiple docker images and caches them. Notice the below output:
$ docker build .
Sending build context to Docker daemon 310.7MB
Step 1/9 : FROM node:alpine
---> 4c6406de22fd
Step 2/9 : WORKDIR /app
---> Using cache
---> a6d9fba502f3
Step 3/9 : COPY ./package.json ./
---> dc39d95064cf
Step 4/9 : RUN npm install
---> Running in 7ccc864c268c
notice how step 2 is saying Using cache because docker realized that everything upto step 2 is the same as from previous build step it is safe to use the cached from the previous build commands.
One of the focuses of this template is building efficient images.
Efficiency could be achieved in two ways:
reducing the time taken to build the image
reducing the size of the final image
For #1 using cached images from the previous builds is leveraged. Dividing dockerfile to rely more and more on the previous build makes the build process faster. It is only possible to rely on cache if the Dockerfile is written efficiently.
By separating these stages of build and publish the docker build . command will be better able to use more and more cache from previous steps in docker file.
For #2 avoid installing packages that are not required, for example.
refer docker documentation for more details here.
By default, VisualStudio uses the Fast mode build that actually builds your projects on the local machine and then shares the output folder to the container using volume mounting.
In Fast mode, Visual Studio calls docker build with an argument that tells Docker to build only the base stage. Visual Studio handles the rest of the process without regard to the contents of the Dockerfile. So, when you modify your Dockerfile, such as to customize the container environment or install additional dependencies, you should put your modifications in the first stage. Any custom steps placed in the Dockerfile's build, publish, or final stages will not be executed.
Thus, the answer to your question
Why Microsoft decided to first - get the aspnet:3.0-buster-slim just to expose ports and use them later as final?
It's necessary in order to provide optimized Fast mode build and debugging in VisualStudio.

What should be included in a Docker container to deploy a ASP.NET web application?

I'm trying to understand Docker.
So far I get that Docker creates packages of our applications called containers with all the dependencies needed to run our applications. Then, the DevOps use these containers to deploy the applications.
I'm comfortable with the high-level overview, but, in the case of a ASP.NET web application, what would be included in the container?
Here is what your Dockerfile would look like.
FROM microsoft/aspnetcore-build:2.0 AS build-env
WORKDIR /app
# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore
# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out
# Build runtime image
FROM microsoft/aspnetcore:2.0
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "aspnetapp.dll"]

Resources