Building Asp.Net Core Applications With an Angular Frontend Using Docker

Ashwin Kumar
Sep 13 · 5 min read

Running .Net Core applications with a frontend SPA framework like Angular or React is now as simple as using one of the built-in templates in Visual Studio. Visual Studio also provides excellent docker support but there can be a few gotchas along the way that we need to be aware of.

In this blog post, we’ll be looking at building an ASP.Net Core application with an Angular SPA for the frontend and creating a ready-to-publish docker image out of it. The first thing we need is to create the project.

In Visual Studio, create a new project using the Asp.Net Core Web Application template and click next. Give the project a name and click create. On the next step, select Angular from the list of templates and click create.

Image for post
Image for post

Run the project by hitting F5. You should see the application open with the ‘hello world’ page.

Now to add docker support, we can right click the project and click on docker support under the add menu.

Image for post
Image for post

This will create a docker file in the project which looks like this,

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["DotNetAngular.csproj", ""]
RUN dotnet restore "./DotNetAngular.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "DotNetAngular.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DotNetAngular.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DotNetAngular.dll"]

Taking a closer look at the sections, we see a multi-stage docker build process with the first section showing a base image (aspnet:3.1-buster-slim) which will contain our application,

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

The next section shows another image (sdk:3.1-buster) which is used to build our application,

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["DotNetAngular.csproj", ""]
RUN dotnet restore "./DotNetAngular.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "DotNetAngular.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DotNetAngular.csproj" -c Release -o /app/publish

Once the build process is complete, the contents of our application is copied into base, ending the process with an image containing our application.

Lets try to build this image that is ready to publish with docker. On a terminal window, navigate to the root of your project and try to build the image,

docker build -t dotnetangular .
Image for post
Image for post

Ooops! Errors!

Image for post
Image for post

We can see the publish process failed with an error when trying to do an npm install,

/bin/sh: 2: /tmp/tmpa036048c83bb44a0a2d9834ce6d641b7.exec.cmd: npm: not found
/src/DotNetAngular.csproj(42,5): error MSB3073: The command "npm install" exited with code 127.
The command '/bin/sh -c dotnet publish "DotNetAngular.csproj" -c Release -o /app/publish' returned a non-zero code: 1

Where is the npm install coming from? Upon editing the project file, the target for publish reveals the exact set of commands that are run to build the angular project,

<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build:ssr -- --prod" Condition=" '$(BuildServerSideRenderer)' == 'true' " />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**; $(SpaRoot)dist-server\**" />
<DistFiles Include="$(SpaRoot)node_modules\**" Condition="'$(BuildServerSideRenderer)' == 'true'" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>

The target named ‘PublishRunWebpack’ is attempting to build the angular project, and the reason for failure is that it cannot find the npm command in the build container since the build image does not come with nodejs installed.

A simple fix would just be to install nodejs in the build container before starting the dotnet build process. To do this, edit the docker file and add the lines to install nodejs,

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
# Install NodeJs
RUN apt-get update && \
apt-get install -y wget && \
apt-get install -y gnupg2 && \
wget -qO-
https://deb.nodesource.com/setup_12.x | bash - && \
apt-get install -y build-essential nodejs
# End Install

WORKDIR /src
COPY ["DotNetAngular.csproj", ""]
RUN dotnet restore "./DotNetAngular.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "DotNetAngular.csproj" -c Release -o /app/build

Note: The node js version is set to 12.x but you can change this to match the nodejs version that you need for your project.

Run the docker build command again and the build should go through successfully.

Image for post
Image for post

Although this is working, we can make the process run faster by deploying an image of the build container with nodejs installed in it. This can be made available in your container registry or on docker hub, and we can then use this image in our docker file instead of the official image. I already have a version of this deployed to my docker hub here (Github). Another good addition would be to run the unit tests before the publish step and to add the — no-cache option to the dotnet restore command since docker will take care of caching layers. (Thanks Richard Collette for the tip)

To use it, revert the docker file build section to use this image and add the step to run unit tests, like so,

FROM ashwin027/dotnet-3.1-buster-node:latest AS build
WORKDIR /src
COPY ["DotNetAngular.csproj", ""]
RUN dotnet restore --no-cache "./DotNetAngular.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "DotNetAngular.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet test "DotNetAngular.csproj" --configuration Release --no-restore
RUN dotnet publish "DotNetAngular.csproj" -c Release -o /app/publish

Lets start a container based on this new image,

docker run -p 8001:80 -d dotnetangular

Open the app at http://localhost:8001 and you should see the app load successfully.

If you have further questions on the topic, feedback on the article or just want to say hi you can hit me up on twitter or linkedin.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store