In this article, I will share with you my approach on how I dockerize my angular applications. While the code I will share may not be the best practice but it served well for my usages from the development environment to production builds.

I will assume that you will have some knowledge of Docker because I will not explain everything in detail.

So, let’s start!

Create Angular App via Angular CLI

You need to have the following installed in your local machine: node, npm, and angular cli. You can find the installation for each from their respective documentations.

Run the following

ng new sample-app

As of writing, the command will produce a new Angular App Version 9. The Ivy update is awesome by the way.


Dockerfile

I will be using multi-stage builds of docker to create my Dockerfile. If you are not familiar with multi-stage builds you can read it here.

My best practice is to create a docker folder. So make sure to create a docker folder from the root project folder.
Into the docker folder create a Dockerfile like so:

FROM node:13.10.1-alpine3.11 as build

WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH

COPY ./package.json /app/package.json
RUN npm install
RUN npm install -g @angular/[email protected]

COPY ./ /app

RUN ng build --prod --output-path=dist

# Dev Image

FROM build as dev
EXPOSE 80
COPY ./docker/dev-entrypoint.sh /
ENTRYPOINT ["/bin/sh", "/dev-entrypoint.sh"]

ENTRYPOINT [“/bin/sh”, “/dev-entrypoint.sh”] this command basically means that whenever the container is started run the dev-entrypoint.sh script.


dev-entrypoint.sh

create a dev-entrypoint.sh file inside the docker folder like so;

cd /app
set -ex \
  && npm install \
  && $(npm bin)/ng serve --watch --port 80 --host 0.0.0.0 --aot --disable-host-check

As you can notice, we are telling angular CLI to use AOT for compilation and we are using the containers’ port 80 to run the application. The option –disable-host-check is needed when you are accessing the container with some sort of proxy.


docker-compose.yml

Now, it’s time to create the docker-compose.yml

version:  '3.7'
services:
  spa:
    build:
      context: .
      dockerfile: ./docker/Dockerfile
      target: dev
    ports:
      - '91:80'
    volumes:
      - ./:/app
    environment:
      - API_URL=localhost:82

Running The Container

The following command will build docker images and run the angular container:

docker-compose up -d

Now navigate to localhost:91 (we mapped the host’s port 90 to container’s port 80) and you will see your app running as expected.

Voila! You got a running angular application via docker.


Production Build with Nginx

I always segregate my docker configuration between environments. As such I have a docker-compose file name as docker-compose.prod.yml to be used only on production builds.

docker-compose.prod.yml

Create docker-compose.prod.yml from project root directory like so:

version:  '3.7'
services:
  spa:
    build:
      context: .
      dockerfile: ./docker/Dockerfile
      target: prod
    ports:
      - '${SPA_PORT}:80'
    volumes:
      - ./docker/default.conf:/etc/nginx/conf.d/default.conf
    environment:
       - SPA_API_URL=${SPA_API_URL}

As you can see we are using environment variables here. You can put your environment variables into your OS’ environment variables or into a file called .env

Also note, that when you are using CI/CD pipelines it is always better to use a built image in your compose file.
Like so:

version:  '3.7'
services:
  spa:
    image: '<whatever-image-name'
    ports:
      - '${SPA_PORT}:80'
    volumes:
      - ./docker/default.conf:/etc/nginx/conf.d/default.conf
    environment:
       - SPA_API_URL=${SPA_API_URL}

Just make sure you have built against the prod stage of the multi-stage build Dockerfile.

Dockerfile

Add a new stage into our existing Dockerfile like so:

FROM nginx:alpine as prod

RUN apk update &amp;&amp; \
  apk add --no-cache make gettext
  
COPY ./docker/default.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/dist /usr/share/nginx/html

COPY ./docker/entrypoint*.sh /
RUN chmod +x /entrypoint*.sh

EXPOSE 80 443
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]


Create Nginx Configuration File

Now we need to create a Nginx config file. Create the default.conf file inside the docker folder with the following contents:

server {
  listen 80;
  root /usr/share/nginx/html;
  location / {
    try_files $uri $uri/ /index.html;
  }
}

entrypoint.sh

We need a different entrypoint script for a production build. Create an entrypoint.sh file inside docker folder with the following contents like so:

#!/usr/bin/env bash
set -eux

for f in $(find . -name main-es2015.*.js); do
    envsubst '${SPA_API_URL}' < $f > "$f.tmp"
    cp "$f.tmp" $f
done

exec "[email protected]"

What this script does is that it looks for SPA_API_URL in the built distribution file and replace it with the environment variables we just provided in the docker-compose.yml. In order to make this possible, we have to modify our environment.prod.ts like so:

export const environment = {
  production: true,
  apiUrl: '${SPA_API_URL}'
};

You should know that when we build the app with ng build –prod it will use the environment.prod.ts to substitute all the values in environment.ts. In this way, the interpolated string “${SPA_API_URL}” will now appear on our final built file and thus, we can use envsubst to replace it with environment variables.


Running The Application

docker-compose -f docker-compose.prod.yml up -d --build 

Navigate into the port you’ve assigned in your compose file. Now you will see your docker app running in a container using Nginx.


Conclusion

I really hope that you got something from this article. Again, this may not be the best practice but you can refer to this article for your improvement. You can get the code on my Github account right here.

I would like my next article to be still about dockerizing apps. Maybe next time would be a Symfony App? Would you like that?

Again thanks again for reading my article!