A blog by Devendra Tewari
All that you need to build Docker containers in your chosen technology stack is a Linux machine running Docker, and a build automation system such as GitLab or Jenkins that builds from source code and pushes containers out to a container registry.
This post shows how to build containers for .NET Core using a multistage Docker build. Multistage builds allow you to build an image based on only the output you need from prior stages.
Initialize an empty git repo and create a folder called src
. Use the dotnet
command to create a console or web application in the src
folder.
If you’ll run container builds on the development machine, add a .dockerignore
file in the root directory so that certain directories don’t get copied over into the image being built
**/bin
**/obj
**/out
**/.vscode
**/.vs
.dotnet
**/node_modules
We’ll build a Dockerfile in stages.
First, we’ll restore dotnet packages, after copying just the .NET solution and project files to the image
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-alpine AS dotnet-package-restore
COPY ./src/MyService.sln /src/MyService.sln
COPY ./src/MyService/MyService.csproj /src/MyService/MyService.csproj
COPY ./src/MyServiceTests/MyServiceTests.csproj /src/MyServiceTests/MyServiceTests.csproj
RUN dotnet restore /src/MyService.sln
Docker caches each build stage by default, so future builds are faster if there are no changes in the files.
Next, if you’re building a web app—we’re building an Angular SPA, for instance—you may need a stage that installs packages using npm
FROM node:lts-alpine AS nodejs-package-install
ARG NPM_TOKEN
COPY ./src/MyService/ClientApp/.npmrc /src/MyService/ClientApp/.npmrc
COPY ./src/MyService/ClientApp/package.json /src/MyService/ClientApp/package.json
COPY ./src/MyService/ClientApp/package-lock.json /src/MyService/ClientApp/package-lock.json
WORKDIR /src/MyService/ClientApp
RUN npm install
.npmrc
receives private module credentials through the environment variable NPM_TOKEN.
Next, build the web app, after copying the /src
folder from the previous stage, and all the source code
FROM nodejs-package-install AS nodejs-builder
ARG NPM_TOKEN
COPY --from=dotnet-package-restore /src /src
COPY ./src/MyService/ClientApp /src/MyService/ClientApp
WORKDIR /src/MyService/ClientApp
RUN npm run build -- --prod
RUN rm -f /src/MyService/ClientApp/.npmrc
Next, you can use the output of the build stage to run unit tests using npm
FROM nodejs-builder AS nodejs-test
RUN npm test
Next, we’ll copy the /src
folder from the nodejs-builder
stage, and use the .NET SDK to build and publish all runtime dependencies
FROM dotnet-package-restore AS dotnet-builder
COPY ./src /src
COPY --from=nodejs-builder /src /src
RUN dotnet publish -c Release /src/MyService.sln
If dotnet-builder
is your first stage—when you are building a console application, for instance—you can eliminate the COPY
statement above.
Next, we’ll run unit tests and check code coverage using coverlet
FROM dotnet-builder AS dotnet-test
ENV ASPNETCORE_ENVIRONMENT=Development
RUN dotnet test /p:CollectCoverage=true /p:Threshold=80 /src/MyServiceTests/MyServiceTests.csproj
The last stage builds the image that will get released out to the container repository
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-alpine AS release
RUN mkdir /service
COPY --from=dotnet-builder /src/MyService/bin/Release/netcoreapp2.2/publish /service
WORKDIR /service
CMD [ "dotnet", "MyService.dll" ]
If you need just the basic .NET runtime, change base image mcr.microsoft.com/dotnet/core/aspnet:2.2-alpine
to mcr.microsoft.com/dotnet/core/runtime:2.2-alpine
.