Setting up Docker and Angular dev environment with hot-reload (fast)

If you want to setup Docker on your Angular project, or are struggling to do so, keep reading this article

·

6 min read

Setting up Docker and Angular dev environment with hot-reload (fast)

So, you have an Angular project, or plan to create one, but you would like to benefit from the bonances of Docker. Or maybe you have tried yourself but are struggling with the setup process. This post is for you.

Why Docker?

TL;DR You want to use Docker if you are building a serious project, where your production environment will be different from your current one, for instance Ubuntu Server instead of your typical Windows machine. Another common usage is for teams, remember when your app didn't run on your colleague's computer and you found out it was a dependencies issue? Yeah, you'll not have to worry about dependencies installation anymore.

If you want a proper, larger explanation about Docker, you should google it as I don't have an article about it (yet).

Prerequisites

This guide assummes you've got the following:

  • A Docker installation. The installation process is different depending on your OS. If you are running Windows, the easiest way is to install Docker Desktop
  • A working Angular project

Setting up Docker in a project

These steps are very straightforward if you already got experience with Docker, as there's no difference in the process. If that's your case, you can skip this section. If not, keep reading and you'll find out how.

Create a file named just Dockerfile, with no extension, in your project's root folder. You can imagine Docker as some sort of Virtual Machine and that file will be a set of commands that will tell Docker how to setup that virtual machine, i.e the OS you want to run, the installation process for your app...

The Dockerfile has a very long page dedicated in Docker's wiki, hence we are now going to focus on the basic setup, as otherwise this post would be too long for a guide that is suppossed to be fast.

# Node 16.16 LTS, Alpine Linux is a small distribution image
FROM node:16.16-alpine3.15

WORKDIR /app

COPY package*.json ./

RUN npm install

# Copy all files to the work dir
COPY . . 

EXPOSE 4200

CMD ["npm", "start"]

This Dockerfile example can work for most of the NodeJS apps. What do all these commands mean?

  • FROM tells Docker which image to use to start the machine. If the image is not in the computer already, it will be downloaded. The image I'm using is node:16.16-alpine3.15, which runs NodeJS 16.16 in Alphine 3.15, a Linux distribution very (very) optimized. You can find a full list of NodeJS images in the official Docker Hub page
  • WORKDIR sets the virtual /app directory as the directory where the next commands will be ran. It also creates the directory if it does not exist.
  • COPY package*.json ./ now we copy our package.json file to the virtual workdir directory. - RUN npm install runs npm install on the workdir and then COPY . . copies all the files in our root directory to the virtual workdir.
  • EXPOSE 4200 will tell Docker we want to use port 4200, the port where Angular runs its webserver.
  • CMD ["npm", "start"] This will execute the npm start command when we start the Docker virtual machine.

Optionally, you can now create a .dockerignore file, which works just as the typical .gitignore file, and tells Docker which files to ignore.

node_modules
npm-debug.log

This way, when we run COPY . ., it will not copy our node_modules folder

Now we need to do a modification in our package.json file.

...
"scripts": {
    "start": "ng serve --host 0.0.0.0",
    ...
},
...

By default, Angular development web server will only accept connections coming from localhost, hence your own computer. As we are now running it on Docker virtual machine, it is not exactly your computer. By setting the --host parameter to 0.0.0.0, we are telling Angular to accept connections from any IP.

Let's try it

First, we need to build the image. Docker will be build it according to our Dockerfile commands. The following commands are meant to be ran on our project's root folder.

docker build -t imageName .

You can change imageName for the name you want. Once it is built, it's now time to run it and test it.

docker run -p 4200:4200 imageName

The argument -p tells Docker how to map the ports that we previously told to Expose. In this case, the virtual port 4200 will be redirected to the actual 4200 port.

Now you can try to connect to the following address with your browser: http://localhost:4200

However, you now may be realising that the hot-reload feature that used to work with ng serve command no longer works. Not only that, if you make a change and reload the browser, nothing changes.

Why is that? Remember you are no longer running the app in your computer, but on the virtual machine. We told Docker to copy the files to the virtual machine, so it is running a copy of your files. After making a change, you would need to build the image again and run the machine again. However, this is not suitable for development. In the next section, I'm going to explain you a better setup for our development, as well as enabling the hot-reload or live-reload again.

Introduction to volumes

Volumes are a nice feature of Docker. As explained above, we can't be building our image everytime we make a change, so volumes will help us doing that, but they are also useful for any scenario where you want your host computer files to be shared with the virtual machine, since that's what volumes essentially are.

Volumes allow you share files between your computer and the virtual machine, and viceversa. You can mount any local directory into a virtual one. How do we do it? Pretty easy, here's an example

docker run -p 4200:4200 -v C:\User\Documents\app\src:/app/src imageName

The -v parameter allows us to map any actual folder to any virtual one. In this case, we are mapping our src folder, where we have the code of our app, into /app/src, where the virtual code is meant to be stored.

PRO-TIP! Yeah, the Windows path is long, as you cannot use a relative path. If you are running the command in the root folder of your project, we can use some sort of trick that will make it work as a relative path instead of an absolute path (see commands below)

If you are using Windows CMD
docker run -p 4200:4200 -v %cd%/src:/app/src imageName

If you are using Windows Powershell
docker run -p 4200:4200 -v $pwd/src:/app/src imageName

Enabling hot-reload again

So after running our Docker image with volumes, our files are being changed in real-time finally. However, if you test it, you will notice that the browser does not automatically reload when there is a change. Why doesn't the hot-reload feature work?

After a few hours of research, I found out we require to do some changes:

On Dockerfile, we'll now also expose the port 49153, which is used by Angular to tell the browser when to reload

...
EXPOSE 4200 49153
...

On package.json, we'll add a new parameter to our ng serve command

"start": "ng serve --host 0.0.0.0 --poll 2000",

By default, Angular's webpack dev server uses native file system change detection. As we are not running the server on our local machine, it will never detect the changes. Because of that, we have to use the --poll parameter to tell the server to look for changes every 2000 ms (you can change the number)

Now you should build the image again and then run it, with the commands explained above. And that's it! We have a working Angular and Docker development setup with live-reload hot-reload