For one of our projects we needed to develop a (simple) dashboard application to monitor production environments. Angular is one of the most popular frontend frameworks at the moment, as is Docker as container platform, so we chose to combine these 2 to develop the dashboard.
When getting started with Angular development, you will first need to install NodeJS and NPM. While writing this blog, NodeJS version 10.15.0 was being used. The corresponding NPM version is 6.4.1. You can check both versions by executing the below statements. For development we use a 64-bit Windows 10 system.
node --version npm --version
When you have NPM on your system, you can install the Angular CLI (Command Line Interface) very easy by executing the below command.
npm install -g @angular/cli
We have used version 7.1.4 of the Angular CLI. Again, when you want to check your version, this can be done by executing ‘ng version’.
Now that we have installed the Angular CLI, we can generate our Angular application. It is pretty easy to setup the initial application. This can be done by executing the below statement. Make sure you execute this statement from the directory where you want to generate your project. This will automatically create a new (sub)directory ‘test-application’ with the sources of the Angular demo application. You can accept all defaults in the wizard.
ng new test-application
After the application is generated successfully, we can run the application:
cd test-application ng serve
As you can also see in the log, you can now access the application via http://localhost:4200.
Now that we have this simple application running, let’s try to run it on a Docker container. But first a brief explanation about some Docker terms:
We (obviously) used Docker for Windows on our local system. For this blog version 18.09.1 of Docker was used. You can check your version via ‘docker –version’.
Now we know what Dockerfiles, images and containers are, we also know that we need to start by creating a ‘Dockerfile’. This file can be created in the root of the Angular application (so directly within the ‘test-application’ directory):
FROM node:11.6.0-alpine AS builder COPY . ./test-application WORKDIR /test-application RUN npm i RUN $(npm bin)/ng build --prod FROM nginx:1.15.8-alpine COPY --from=builder /test-application/dist/test-application/ /usr/share/nginx/html
As you can see, this Dockerfile consists of 2 parts (stages). Both starting with the ‘FROM’ tag. The first one is the stage that will just be used for building the Angular application. The final stage will only contain a webserver and does not require build tools like Node. This obviously has a positive effect on the size of the final image.
The ‘builder’ stage will be initialized from the external node image. We will use the (lightweight) Alpine Linux distribution.
Tip: If you want to see how the parent images are built, just look them up on DockerHub. When you search ‘node’ and click the version we use (11.6.0-alpine), we can see the Dockerfile which is used to build this image.
Next, we use the ‘COPY’ tag to copy the current directory (which is the ‘test-application’ directory) content to the Docker image. When you want to exclude files to be copied to the stage(s) and/or image(s), you can create a ‘.dockerignore’ file on the same level as the ‘Dockerfile’. We will also change the working directory of the image to this new directory using the ‘WORKDIR’ tag.
Finally we will run the commands ‘npm i’ and ‘ng build –prod’ to retrieve the required Node modules and build a (production-like) release. The built result will be stored in the ‘dist’ folder.
Now that we have a binary version of our application available in the builder stage, it’s time to create our runtime Docker image. We will use an Nginx image as it’s base. Nginx is a webserver which we will use to run the built binary from the builder stage.
Again, we will use the ‘COPY’ tag to place the binary files on the image. However, this time the source is not the host system, but the builder stage.
As mentioned before, the ‘.dockerignore’ file can be used to prevent files being copied to the Docker images. In our case, we won’t include the ‘dist’ folder, because we want to construct this in the builder stage. Copying it from the host system first would not make sense. We also exclude the ‘node_modules’ folder which contains all required Node modules. This folder will be (re)constructed within the builder stage itself by the ‘npm i’ command:
Now that we have all configuration in place, we can build the Docker image by executing the following command:
docker build --rm -t test-application:latest .
The ‘–rm’ flag is used to automatically remove the intermediate containers once the image has been built successful. The ‘-t’ flag is used to provide a tag name of the Docker image. This tag name is also used when we run the image on a Docker container:
docker run --rm -d -p 90:80/tcp test-application:latest
Again, the ‘–rm’ flag is used to automatically clean up (remove) the container once it’s stopped. The ‘-d’ flag Is used to start the container in detached mode. Finally, we will connect port 90 of the host system to TCP traffic on port 80 within the image. Port 80 is the default port used by Nginx.
When you browse to http://localhost:90, you will now see the application which is running from Docker!
Tip: When you don’t see your Angular application or changes that should have been deployed, you can try to browse in incognito mode, or to clear the browser cache (CTRL + SHIFT + R in Firefox and Chrome)