Running Angular applications inside a Docker container – part 1

Introduction

This is the first post in a series of posts in which I will deploy an Angular2 application and an Express server inside a Docker container. By the end of the series, I will build a sample application (client and server) and use Nginx with HAProxy to proxy the requests to their intended servers and also dynamically balance the requests when we add or remove servers from the cluster.

What is Docker?

Docker is an open-source software that enables you to automate the deployment of any application into software containers. Containers, unlinke virtual machines, share the operating system on which they are running. This means that containers are much more efficient in using system resources resulting in more instances of your application running on the same hardware.

Prerequisites

  1. Install Docker with brew install docker if you are on a Mac or use your OS intaller from https://www.docker.com/community-edition#/download.
  2. Make sure you have NPM installed on your system.
  3. Install AngularCLI by running npm install -g @angular/cli.

Creating the Angular application.

We will use the Angular CLI to generate a sample Angular app:

  • Create a new directory for your project: mkdir ng-2-docker
  • Generate a new angular app. I will name it client: cd ng-2-docker ng new client
  • Start the development server by running ng serve
  • Open your browser and navigate to http://localhost:4200

If you see the message “app works!” you are good to go.

“Dockerizing” the client application.

We begin by creating a Dockerfile inside the client application folder. This file is like a blueprint for the docker container. We will create a new image from the base nodejs 7 image, copy all the application source files into our image, install de dependencies and then run the application.

ng-2-docker/client/Dockerfile

#  Create a new image from the base nodejs 7 image.
FROM node:7
# Create the target directory in the imahge
RUN mkdir -p /usr/src/app
# Set the created directory as the working directory
WORKDIR /usr/src/app
# Copy the package.json inside the working directory
COPY package.json /usr/src/app
# Install required dependencies
RUN npm install
# Copy the client application source files. You can use .dockerignore to exlcude files. Works just as .gitignore does.
COPY . /usr/src/app
# Open port 4200. This is the port that our development server uses
EXPOSE 4200
# Start the application. This is the same as running ng serve.
CMD ["npm", "start"]

Before building our image we just need to change one thing. By default, the webpack-dev-server is not accessible from other hosts. In order to make it available we need to add a parameter into our package.json file.

ng-2-docker/client/package.json

From this:

...
"scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
...

Into this:

...
"scripts": {
    "ng": "ng",
    "start": "ng serve -H 0.0.0.0",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
...

Now we need to build our image. Make sure you are in the client directory and then run:

docker build -t ng-2-docker/client .

This will build our custom nodejs image that will run our Angular client application.
In order to test our image we need to deploy it in a container: docker run --rm -p 80:4200 ng-2-docker/client

  • –rm parameter will immediately remove the container when it stops.
  • -p parameter maps the container’s 4200 port to port 80 on your local machine.
  • The last part of the command it’s the image that we’ve just built.

We can test our application by opening http://localhost in your browser.
If you see the message “app works!” then you are successfully running your Angular application inside d docker container. You can also run docker ps and you should see one running container.

Putting it all together with Docker Compose

Mapping container’s port to local machine’s port it’s ok for what we need but in a real production environment you would most probably use Nginx to reverse proxy the requests to your application server/s. When a request comes on port 80 in your Nginx server, it will be forwarded to port 4200 into your application server (webpack-dev-server in our example). In order to be able to easily orchestrate multiple images and containers we are going to use docker-compose. This utility comes pre-installed with Docker.

First, we are going to create our Nginx image. We will start from a base Nginx image and just add our own default configuration to proxy the requests to our application server.

  • Create new folder named nginx in the root folder of our project: mkdir nginx
  • Into this new folder create 2 files: Dockerfile and default.conf

ng-2-docker/nginx/default.conf

server {
    location / {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_pass http://client:4200/;
    }
}

The config above will instruct Nginx (which will run in a separate container) to proxy all request coming on / to the angular client application running in another container on port 4200.

Now for the Dockerfile:

ng-2-docker/nginx/Dockerfile

#  Create a new image from the base nginx image.
FROM nginx
# Overwrite nginx's default configuration file with our own.
COPY default.conf /etc/nginx/conf.d/

Now, int root of the project create a new file called docker-compose.yml

ng-2-docker/docker-compose.yml

version: '2'

services:

  # Build the container using the client Dockerfile
  client:
      build: ./client

  # Build the container using the nginx Dockerfile
  nginx:
    build: ./nginx
    # Map Nginx port 80 to the local machine's port 80
    ports:
      - "80:80"
    # Link the client container so that Nginx will have access to it
    links:
      - client

Now in the root folder run docker-compose up -d --build --remove-orphans and everything should magically work. Test it at http://localhost.

Useful docker-compose commands:

  • docker-compose ps list running containers
  • docker-compose stop stops our comntainers
  • docker-compose start starts our containers
  • docker-compose up -d --build --remove-orphans re-builds the containers. Useful if you modify any configuration or source file
  • docker-compose logs brings up the container logs

Editing code without container re-build?

This is all fine and dandy but it’s a bit of a headache to re-build the containers everytime you modify a css file. In order to map the client container’s code We just need to add a line of code into our docker-compose.yml file.

ng-2-docker/docker-compose.yml

...
  # Build the container using the client Dockerfile
  client:
      build: ./client
  # This line maps the contents of the client folder into the container.
      volumes:
        - ./client:/usr/src/app
...

In order to test our changes run:

  • docker-compose up -d --build --remove-orphans to re-build the image
  • Open your browser at http://localhost. You should see app works!
  • Edit ng-2-docker/client/src/app/app.component.ts and change the title value to “app works with Docker!”
  • Refresh your browser and see the magic happening.

What’s next?

In the next post we will create an API server with Express and MongoDB and connect our client application to it.

P.S: The code is available here

13 comments on “Running Angular applications inside a Docker container – part 1

  1. webdevmy123 says:

    Hi there,
    Thanks for your article, this really help me 🙂
    I did follow you instruction and it is work perfectly only one thing that I notice. I always got error in console panel say that

    GET http://localhost:4200/sockjs-node/info?t=1501148115284 net::ERR_CONNECTION_REFUSED
    ..
    [WDS] Disconnected!

    Do you have any solution to solve this issue?

    Thank very much.

    Like

    • Daniel Popescu says:

      Hey. My guess is that the connection for the live reload is not forwarded properly. Try adding this to your nginx/default.conf:

      location /sockjs-node/ {
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection “upgrade”;
      proxy_http_version 1.1;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;
      proxy_pass http://client:4200;
      }

      Like

      • webdevmy123 says:

        Hey Daniel,

        Thanks for you fast response, the issue doesn’t seem to be fixed after I have updated nginx/default.conf and rebuild the image

        server {
        location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://client:4200/;
        }
        location /sockjs-node/ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection “upgrade”
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
        proxy_pass http://client:4200;
        }
        }

        Thanks,
        My

        Like

  2. Sourav Pati says:

    Hi Daniel , Its a great article , I was following along and able to run the angular app successfully in my localhost . So I decided to deploy the app in aws , after i successfully deployed , when I used the public dns on the browser I am getting “invalid host header” . In order to test the app is running , I entered into the aws ec2 container and when ran the curl command curl http://loclahost:80 I get the app works response . only when I used the browser to access the app I get “invalid host header” . Would you be able to give some clue ?

    Like

    • Daniel Popescu says:

      My guess is that the http server is not accepting a different host than localhost. Check this issue: https://github.com/angular/angular-cli/issues/6349

      You will need to add a disable-host-check parameter to ng serve.

      P.S: ng serve should not be used in production. It’s just for development. In a production environment you should use nginx to server the compiled angular app directly.

      Like

      • Sourav Pati says:

        Thank you for such a quick response ! I have looked at the linked , now my questions are

        1. in order to disable host check i need to add “ng serve disable-host-check” in the package.json instead of “ng serve -H 0.0.0.0” , is this what you are saying ?

        2. I could not follow this “In a production environment you should use nginx to server ” , could you elaborate what do you mean by this .

        Thanks !

        Like

      • Gabriel Chivoiu says:

        @ Hi Pati, yes you need to modify the ng serve to look like this:
        “start”: “ng serve –host 0.0.0.0 –disable-host-check”,
        For me it worked.

        Like

  3. Pranay Anand says:

    Hi Daniel,

    awesome post loved every bit of it..
    When can we get another post on “create an API server with Express and MongoDB and connect our client application to it”??

    Thanks in advance..!!!

    Like

  4. Frank says:

    Hi Daniel,
    I have been trying to setup an express server separately in a docker container with mongo. I been working on it for days, but whatever I do, I just can’t to set it up right.
    when I call let url = ‘http://myserver/api/registerCoin’; it times out or it tells me it can’t be resolved
    I can access the containers and when I ping the names they resolve ok.
    However can’t telnet to the ip of the containers on their specific ports. Port 4200 works fine I can access my website but port 3000 get’s time outs and I basically can’t access any database data.
    I really would love to see an answer to this, tried stackoverflow to no result.

    worker_processes 2;

    events {
    worker_connections 1024;
    }
    http {
    upstream my-server {
    server myserver:3000;
    }

    upstream client {
    server client:4200;
    }

    server {

    location / {
    proxy_pass http://client;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection ‘upgrade’;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Forwarded-For $remote_addr;
    }
    location /api/ {
    proxy_pass http://my-server;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection ‘upgrade’;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Forwarded-For $remote_addr;
    }
    }
    }

    docker compose:

    version: ‘3’

    services:

    nginx:
    build: ./nginx
    # Map Nginx port 80 to the local machine’s port 80
    volumes:
    – ./dist:/usr/share/nginx/html
    ports:
    – “80:80”
    depends_on:
    – client
    networks:
    – app-network
    # Build the container using the client Dockerfile
    client:
    build: ./
    # This line maps the contents of the client folder into the container.
    volumes:
    – ./:/usr/src/app
    ports:
    – “4200:4200”
    networks:
    – app-network

    myserver:
    build: ./express-server
    volumes:
    – ./express-server:/usr/src/server
    environment:
    – NODE_ENV=development
    depends_on:
    – mongo
    ports:
    – “3000:3000”
    networks:
    – app-network

    # Link the client container so that Nginx will have access to it
    mongo:
    environment:
    – AUTH=yes
    – MONGO_INITDB_ROOT_USERNAME=superAdmin
    – MONGO_INITDB_ROOT_PASSWORD=admin123

    image: mongo
    volumes:
    – /var/mongodata/data:/data/db
    ports:
    – “27017:27017”
    networks:
    – app-network

    networks:

    app-network:

    driver: bridge

    Like

  5. […] going wrong here? My application isn’t called client, it has different name. I was following (https://dpopescu.me/2017/03/13/running-angular-applications-inside-a-docker-container-part-1/) this […]

    Liked by 1 person

  6. SRIJI NAGARAJAN says:

    Please add you next tutorial create an API server with Express and MongoDB and connect our client application to it. It will be really useful

    Liked by 1 person

    • Daniel Popescu says:

      Hey. I’m glad it was useful so far. I will try and continue the article. Haven’t had much time lately. Thanks for the interest.

      Like

  7. SRIJI NAGARAJAN says:

    Hi Daniel,

    i am trying to add hostname in ng serve –host samplehost –port 4200 and then add the same in default.conf

    location / {
    root /usr/share/nginx/html;
    index index.html index.htm;
    try_files $uri $uri/ /index.html =404;
    proxy_pass http://samplehost:4200;
    }
    Deployed in docker container and but when i am trying to open http://samplehost:4200 it gives me error ERR_EMPTY_RESPONSE. Pls need ur suggestion

    Like

Leave a Reply to Dockerizing Angular 2 application fails – program faq Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s