What’s better than starting a new greenfield project? You finally have the opportunity to leverage all the new patterns, technologies, and frameworks that you’ve been dying to get your hands on. A project I started recently had a strong focus on Docker. Being a new project, we were also able to use the latest version of ASP.NET Core and Visual Studio 2019. If you’ve read any of my previous posts, you know that .NET Core and Visual Studio have a lot of very convenient interaction points with Docker.
Things that would normally take a couple hours to setup are created with the click of a button. This is of course until you use the ASP.NET Core Angular template and noticed the Enable Docker Support checkbox is disabled. If only life were so simple. Fortunately with a couple small changes, we are able to incorporate Docker into the default ASP.NET Core Angular template and take advantage of a benefits that come along with the Visual Studio container tools.
In this article, we will discuss enhancing the default template configuration to build and run your ASP.NET Core Angular app in Docker.
THE ANGULAR PROJECT TEMPLATE
When we create a new ASP.NET Core Web Application using the Angular template, a new “Hello, World” application is generated. In addition to the Angular frontend, we also have an ASP.NET Core API setup server-side. Debugging the project in Visual Studio, we will notice both the Angular and ASP.NET Core applications are running together.
If you’ve worked on a project like this before, you are probably used to debugging your frontend with the Angular CLI and your API with Visual Studio. The Angular project template appears to be doing both for us! But how? We can see this by looking at the Startup.cs
file.
As we can see, npm start
is getting executed under the hood by leveraging the Microsoft.AspNetCore.SpaServices
nuget package. Looking at our package.json file, we can see this is effectively calling ng serve
.
All this bridges the frontend and backend together nicely however, it doesn’t come without some downsides. First and foremost, when a backend change is made, ng serve
needs to run again which can take 10+ seconds depending on the size of the frontend application. Microsoft mentions this in their documentation and offers a convenient workaround as shown in the Startup
class below.
Making this change allows us to debug our Angular application from the command-line while using Visual Studio to debug our ASP.NET Core API.
INTRODUCING DOCKER
While we have a nice development environment setup, let’s circle back to the beginning of the article. On this new project, we wanted to leverage Docker as much as possible. So how does Docker fit into our new project? Well at the moment it doesn’t but, we can fix that!
Just like anything else, we have a couple options. For example, we could take the entire project and throw it into a container. This of course would work however, it will have the same issues as previously discussed. Any changes to the backend code would require us to rebuild the backend AND frontend projects and vise versa. To build them independently, we will have to split them into separate containers.
First lets take a look at running our ASP.NET Core API in Docker.
ASP.NET CORE WITH DOCKER
Even though we can’t include Docker support when we create an Angular project, we can still add Docker support after the fact from the project menu. This will automatically generate a Dockerfile similar to the one shown below.
At this point if we try to debug our project, the browser will display a “Failed to proxy the request to http://localhost:4200/” error message. As the error message states, our ASP.NET project is unable access our Angular application. Since ASP.NET Core is now running in a container, we will get the same error message even if ng serve
is running locally. We can solve this by continuing our with our journey into Docker.
ANGULAR WITH DOCKER
The next step is to configure our Angular application to run in Docker as well. For this, we will create a separate Dockerfile
. This gives us the flexibility to stop and start our API without rebuilding the frontend. I like to use a Dockerfile
similar to the one shown below.
The resulting Docker image image will contain node, npm, and the Angular CLI. When the container is started, ng server
will be executed in the app folder. Of course the app folder will be empty but, we can make our code accessible to that location by mounting a volume in the container.
BRINGING IT ALL TOGETHER
To run these two containers together, we use a tool that goes hand-in-hand with Docker called Docker Compose. If you are not familar with Docker Compose, it is a container orchestration too that is used for configuring and running multi-container Docker applications. When using Docker Compose everything gets configured in a yml configuration file. We can create a create a docker-compose.yml file in Visual Studio by right-clicking the project and selecting Add | Container Orchestration Support.
After it is created, our Angular container will need to be included, as shown below.
Lastly, we will need to update our Startup.cs
file to proxy requests to the Angular container. We use the service name (jrtech.angular.app) in order to work with Docker’s internal network.
Voila! Now if we set the docker-compose
project as our startup project and debug, both containers will start running. If we make some changes and rerun the debugger, it will be fast as the Angular container never stops!
DEPLOYING AS A SINGLE CONTAINER
We now have a nice development process setup however, we may not want to deploy in this configuration. While nice for local development, deploying our frontend and backend applications separately means we will have to deal with CORS, preflight requests, etc. If we want to avoid this, we can support a single container deployment by making a few tweaks to our Dockerfile.
By default, Visual Studio creates a multistage Dockerfile. We can extend this by adding an additional stage for our Angular application. We also introduce a build time argument which will dictate if we want to build the frontend components or not.
We give the argument a default value of false but, in our docker-compose.yml
file, we override it to true. This way when we build our containers with docker-compose, the Angular application will not be redundantly built inside of the ASP.NET Core container.
Lastly, we can remove the majority of the custom build configuration in our project file as all of our Angular build steps are now in Docker. I cleaned mine up to look very similar to a typical ASP.NET Core web application.
With this configuration, when running our application with docker-compose our Angular app is built in a separate container. When the container is built individually, all of the Angular components are self-contained within the ASP.NET Core container. This gives us the ability build, compile, and debug these applications while developing and generate a self-contained image for deployment.