This article will teach you how you can use Docker to build and run an Angular application.
We will create a lightweight and efficient Docker image using Docker’s multi-stage builds feature to serve the app using NGINX.
This post is part of the Dockerized Django Back-end API with Angular Front-end Tutorial. Check out all the parts of the tutorial there.
In the last part of the tutorial, we’ve created a simple dockerized Django API that can create Todo tasks. In this blog post, we’ll move towards the front-end and build the basis of our Angular app to interact with our API.
To get the code to where we left off in the last blog post, use:
$ git checkout v1.4
Creating an Empty Angular App to Dockerize
Firstly, let’s create a new directory in our repo root to hold all Angular related code and cd
inside it:
$ mkdir angular
$ cd angular
Next, let’s start an initial Angular project using the Angular CLI.
If you don’t already have the Angular CLI, install it using:
$ npm install -g @angular/cli@latest
Next, create a new application using:
$ ng new angular-app
Select Yes for Angular routing. For the stylesheet format, I chose CSS, but you can select whatever you want.
Run the app to ensure it compiles and works correctly.
$ cd angular-app
$ ng serve
Visit http://localhost:4200 in your browser to confirm the skeleton app works.
You can also checkout the code with the skeleton app using:
$ git checkout v1.5
When developing the app, it’s sufficient to just use ng serve
, as the app is dynamically rebuilt on file changes.
When it comes to moving towards a production deployment, setting up a Docker image makes sense as you’ll see shortly.
Creating the Docker Image in Stages
In this section, we will build a Docker image that can be used to serve our Angular app.
The image will be built in two stages:
-
Build Stage – use a Node Alpine Docker image to compile our app.
-
Delivery Stage – serve files from build stage using an NGINX Alpine Docker image.
Stage 1 – Compiling the Angular App
Create a Dockerfile
file in the angular
directory of the repo root.
In the first stage, we will use a node
Alpine image as our base Docker image:
FROM node:11.4.0-alpine as builder
We name this stage builder
so we can reference it later in the second stage.
Then, we create and set a working directory:
RUN mkdir /app
WORKDIR /app
After, we copy and install our app dependencies:
# Copy app dependencies.
COPY angular-app/package.json angular-app/package-lock.json /app/angular-app/
# Install app dependencies.
RUN npm install --prefix angular-app
The --prefix
option installs the dependencies in the angular-app
directory.
Lastly, we copy the app files and build it in ./dist/out
:
# Copy app files.
COPY . /app
# Build app
RUN npm run build --prefix angular-app -- --output-path=./dist/out
We copy the app files after installing dependencies so that if package.json
doesn’t change, Docker will cache the installation step so later image builds will be faster.
Stage 1 looks as follows:
Ignoring files using .dockerignore
Before moving on to the second stage, there is one issue that can cause potential problems.
The second COPY
command copies everything, including the node_modules
. This means that the dependencies we’ve installed on the Docker image using RUN npm install
will be overwritten.
We can create a .dockerignore
file in the angular
directory to tell Docker that the node_modules
directory should be ignored when copying files. Just create the file with the contents:
Thanks to Lukas for this tip from his article.
Stage 2 – Serving the Angular App Using NGINX
Using multi-stage builds, we can start from a new image with NGINX installed and just copy the built app from the builder
stage.
Therefore, we use an NGINX Alpine image as the base:
FROM nginx:1.15.7-alpine
Then, we remove the default NGINX website:
RUN rm -rf /usr/share/nginx/html/*
After, we copy the compiled app from the builder image to the NGINX public folder:
COPY --from=builder /app/angular-app/dist/out /usr/share/nginx/html
Lastly, we need to copy an NGINX configuration file to the Docker image. We’ll create this file next.
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
In the end, stage 2 will be added right after stage 1 in the same Dockerfile
, and looks as follows:
Creating a Default NGINX Configuration File
We need to inform NGINX which files to serve. Create a new directory under the angular
one called nginx
.
$ mkdir nginx
Inside this directory, create a NGINX configuration file called nginx.conf
with the contents:
Here, we tell NGINX to listen on port 80 and for requested files that can’t be found, serve index.html
.
Updating the docker-compose File
We can now spin up a container serving the Angular app via NGINX using the docker-compose.yaml
file we created in the first part of the tutorial.
In the repo root, update docker-compose.yaml
as follows:
We’ve just added a new service called ng
and told Docker to map port 8080 on the host to port 80 on the container, where NGINX listens for requests.
If you execute docker-compose up
from the repo root, you can then see a new ng
container running concurrently with the Django and the PostgreSQL ones:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
67933b65acac drf-angular-docker-tutorial_dj "python manage.py ru…" 5 minutes ago Up 5 minutes 0.0.0.0:80->80/tcp dj
c478693e2dd3 postgres "docker-entrypoint.s…" 5 minutes ago Up 5 minutes 5432/tcp drf-angular-docker-tutorial_db_1
a6b6c7dc7ab8 drf-angular-docker-tutorial_ng "nginx -g 'daemon of…" 5 minutes ago Up 5 minutes 0.0.0.0:8080->80/tcp ng
If you visit http://localhost:8080, you can see the app being served via NGINX.
Awesome! To get to this point of the tutorial, just:
$ git checkout v1.6
Summary
In this part of the tutorial, we’ve learned how to build an Angular application using Docker and how to serve it in an NGINX container.
In the next blog post, we’ll see how we can consume the Django REST API using the Angular app.
Credit: I used the following resources as a basis for this article: