202 Angular App in Docker - chempkovsky/CS82ANGULAR GitHub Wiki

Notes

  • Read the article
  • Before creating Docker-file we need a copy of nginx.conf of the Nginx-server.
    • To get the file
      • pull the image
        • docker pull nginx:latest
      • run the image
        • docker run nginx
      • List the containers
        • docker ps
      • connect to container
        • docker exec -it nginx /bin/bash
      • Print /etc/nginx/nginx.conf-file
        • cat /etc/nginx/nginx.conf
      • Print /etc/nginx/conf.d/default.conf-file
        • cat /etc/nginx/conf.d/default.conf
  • Before creating Docker-file we need to modify assets/app-config.template.json-file
    • read the article
      • we changed the structure of the src\assets\app-config.json-file
        • The latter requires modifying the app-config.template.json file

Steps required to accomplish the task

Nginx conf

pull and run nginx

  • run the commands
docker pull nginx:latest
docker images
docker run nginx
Click to show the response
C:\Users\yury>docker pull nginx:latest
latest: Pulling from library/nginx
42c077c10790: Pull complete
62c70f376f6a: Pull complete
915cc9bd79c2: Pull complete
75a963e94de0: Pull complete
7b1fab684d70: Pull complete
db24d06d5af4: Pull complete
Digest: sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest

C:\Users\yury>docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

C:\Users\yury>docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    0e901e68141f   2 weeks ago   142MB

C:\Users\yury>docker run nginx
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: Getting the checksum of /etc/nginx/conf.d/default.conf
10-listen-on-ipv6-by-default.sh: info: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
/docker-entrypoint.sh: Configuration complete; ready for start up
2022/06/13 12:51:15 [notice] 1#1: using the "epoll" event method
2022/06/13 12:51:15 [notice] 1#1: nginx/1.21.6
2022/06/13 12:51:15 [notice] 1#1: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/06/13 12:51:15 [notice] 1#1: OS: Linux 5.10.102.1-microsoft-standard-WSL2
2022/06/13 12:51:15 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/06/13 12:51:15 [notice] 1#1: start worker processes
2022/06/13 12:51:15 [notice] 1#1: start worker process 31
2022/06/13 12:51:15 [notice] 1#1: start worker process 32
2022/06/13 12:51:15 [notice] 1#1: start worker process 33
2022/06/13 12:51:15 [notice] 1#1: start worker process 34
2022/06/13 12:51:15 [notice] 1#1: start worker process 35
2022/06/13 12:51:15 [notice] 1#1: start worker process 36
2022/06/13 12:51:15 [notice] 1#1: start worker process 37
2022/06/13 12:51:15 [notice] 1#1: start worker process 38

Get configs

  • run the commands
docker ps
docker exec -it 2ba47cad894e /bin/bash
cat /etc/nginx/nginx.conf
cat /etc/nginx/conf.d/default.conf
Click to show the response
C:\Users\yury>docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED              STATUS              PORTS     NAMES
2ba47cad894e   nginx     "/docker-entrypoint.…"   About a minute ago   Up About a minute   80/tcp    amazing_chaplygin

C:\Users\yury>docker exec -it 2ba47cad894e /bin/bash
root@2ba47cad894e:/# cat /etc/nginx/nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
root@2ba47cad894e:/# cat /etc/nginx/conf.d/default.conf
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

root@2ba47cad894e:/# exit
exit

C:\Users\yury>docker images
REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
nginx        latest    0e901e68141f   2 weeks ago   142MB

C:\Users\yury>

Create a copy of the config file

  • we will create the copy for /etc/nginx/conf.d/default.conf
  • It'll be nginxdefault.conf in the angular project
  • two special paths we will use later:
    • /usr/share/nginx/html/angular-phonebook/en/
    • /usr/share/nginx/html/angular-phonebook/ru/
Click to show nginxdefault.conf
# Browser preferred language detection (does NOT require AcceptLanguageModule)
map $http_accept_language $accept_language {
  ~*^ru ru;
  ~*^en en;
}

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    # location / {
    #    root   /usr/share/nginx/html;
    #    index  index.html index.htm;
    # }

    # Fallback to default language if no preference defined by browser
    if ($accept_language ~ "^$") {
        set $accept_language "en";
    }

    location /ru/ {
      alias /usr/share/nginx/html/angular-phonebook/ru/;
      try_files $uri$args $uri$args/ /ru/index.html;
    }
    location /en/ {
      alias /usr/share/nginx/html/angular-phonebook/en/;
      try_files $uri$args $uri$args/ /en/index.html;
    }
    location / {
      alias /usr/share/nginx/html/angular-phonebook/$accept_language/;
      try_files $uri$args $uri$args/ /$accept_language/index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   /usr/share/nginx/html;
    #}

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

app config template

  • we added "divisionLpWebApi", "employeeLpWebApi" and "phoneLpWebApi" in the app-config.json file.
    • the new version of the app-config.template.json file will be as follows:
{
    "webApiUrl": "${WA_URL}",
    "securityUrl": "${SEC_URL}",
    "permissionWebApi": "${PERM_URL}",
    "divisionLpWebApi": "${DIVLP_URL}",
    "employeeLpWebApi": "${EMPLP_URL}",
    "phoneLpWebApi": "${PHNLP_URL}"
}

Dockerfile

  • we will use two stage assembly
  • create Dockerfile in the Angular project
Click to show Dockerfile
################################################################
# Build the app in the separate image which is node:latest
################################################################
FROM node:latest as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration=ru --prod --output-path=/dist/angular-phonebook
RUN mv /dist /ru
RUN ng build --configuration=en --prod --output-path=/dist/angular-phonebook
RUN mv /dist /en


################################################################
# Copy form node:latest image into  nginx:latest and exec "CMD"
################################################################
FROM nginx:latest as nginxrslt
COPY nginxdefault.conf /etc/nginx/conf.d/default.conf
COPY --from=build /ru/angular-phonebook/ru/ /usr/share/nginx/html/angular-phonebook/ru/
COPY --from=build /en/angular-phonebook/en/ /usr/share/nginx/html/angular-phonebook/en/
CMD ["/bin/sh",  "-c",  "envsubst < /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.json && envsubst < /usr/share/nginx/html/angular-phonebook/en/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json &&  exec nginx -g 'daemon off;'"]
  • The command to create the image
docker build . -f Dockerfile -t angular-phone-book

Run Docker Image

  • run the command
docker run -d -p 90:80 -e PHNLP_URL="http://localhost:5055/" -e EMPLP_URL="http://localhost:5055/" -e DIVLP_URL="http://localhost:5055/"  -e WA_URL="http://localhost:5165/" -e SEC_URL="http://localhost:5165/" -e PERM_URL="http://localhost:5165/" angular-phone-book:latest

Docker swarm

  • Suppose we have more than two locales and our CMD[...] command is getting too long
    • we can simplify Dockerfile and use some features of the Docker swarm
Click to show Dockerfile
################################################################
# Build the app in the separate image which is node:latest
################################################################
FROM node:latest as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration=ru --prod --output-path=/dist/angular-phonebook
RUN mv /dist /ru
RUN ng build --configuration=en --prod --output-path=/dist/angular-phonebook
RUN mv /dist /en


################################################################
# Copy form node:latest image into  nginx:latest and exec "CMD"
################################################################
FROM nginx:latest as nginxrslt
## COPY nginxdefault.conf /etc/nginx/conf.d/default.conf
COPY --from=build /ru/angular-phonebook/ru/ /usr/share/nginx/html/angular-phonebook/ru/
COPY --from=build /en/angular-phonebook/en/ /usr/share/nginx/html/angular-phonebook/en/
## CMD ["/bin/sh",  "-c",  "exec nginx -g 'daemon off;'"]

Create nginx config

  • In the current folder create the nginxdefault.conf file with the following content
Click to show the file
# Browser preferred language detection (does NOT require AcceptLanguageModule)
map $http_accept_language $accept_language {
  ~*^ru ru;
  ~*^en en;
}

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    # location / {
    #    root   /usr/share/nginx/html;
    #    index  index.html index.htm;
    # }

    # Fallback to default language if no preference defined by browser
    if ($accept_language ~ "^$") {
        set $accept_language "en";
    }

    location /ru/ {
      alias /usr/share/nginx/html/angular-phonebook/ru/;
      try_files $uri$args $uri$args/ /ru/index.html;
    }
    location /en/ {
      alias /usr/share/nginx/html/angular-phonebook/en/;
      try_files $uri$args $uri$args/ /en/index.html;
    }
    location / {
      alias /usr/share/nginx/html/angular-phonebook/$accept_language/;
      try_files $uri$args $uri$args/ /$accept_language/index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   /usr/share/nginx/html;
    #}

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
  • run the following commands
docker swarm init
docker config create nginxdefault.conf ./nginxdefault.conf
docker config ls
docker config inspect nginxdefault.conf --pretty

Create app config

  • In the current folder create the app-config.json file with the following content
Click to show the sample
{
  "webApiUrl": "http://localhost:5165/",
  "securityUrl": "http://localhost:5165/",
  "permissionWebApi": "http://localhost:5165/",
  "divisionLpWebApi": "http://localhost:5055/",
  "employeeLpWebApi": "http://localhost:5055/",
  "phoneLpWebApi": "http://localhost:5055/"
}
  • run the following commands
docker swarm init
docker config create app-config.json ./app-config.json
docker config ls
docker config inspect app-config.json --pretty

Create service

  • run the following command
docker service create  ^
     --name angular-phone-book-service  ^
     --config source=app-config.json,target=/usr/share/nginx/html/angular-phonebook/ru/assets/app-config.json  ^
     --config source=app-config.json,target=/usr/share/nginx/html/angular-phonebook/en/assets/app-config.json  ^
     --config source=nginxdefault.conf,target=/etc/nginx/conf.d/default.conf  ^
     --publish published=90,target=80  ^
     angular-phone-book:latest ^
    sh -c "exec nginx -g 'daemon off;'"

Inspect

  • run the commands
docker service ls
docker ps
docker exec -it edb915519670 /bin/bash
cat /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json
cat /etc/nginx/conf.d/default.conf
exit
Click to show the response
C:\Program Files\Git\usr\bin>docker service ls
ID             NAME                         MODE         REPLICAS   IMAGE                       PORTS
cwxp4f6uz04v   angular-phone-book-service   replicated   1/1        angular-phone-book:latest   *:90->80/tcp

C:\Program Files\Git\usr\bin>docker ps
CONTAINER ID   IMAGE                       COMMAND                  CREATED          STATUS          PORTS     NAMES
edb915519670   angular-phone-book:latest   "/docker-entrypoint.…"   16 minutes ago   Up 16 minutes   80/tcp    angular-phone-book-service.1.yp5bk2m1pkldmp799jvhgcz6k

C:\Program Files\Git\usr\bin>docker exec -it edb915519670 /bin/bash
root@edb915519670:/# cat /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json

{
  "webApiUrl": "http://localhost:5165/",
  "securityUrl": "http://localhost:5165/",
  "permissionWebApi": "http://localhost:5165/",
  "divisionLpWebApi": "http://localhost:5055/",
  "employeeLpWebApi": "http://localhost:5055/",
  "phoneLpWebApi": "http://localhost:5055/"
}root@edb915519670:/# cat /etc/nginx/conf.d/default.conf
# Browser preferred language detection (does NOT require AcceptLanguageModule)
map $http_accept_language $accept_language {
  ~*^ru ru;
  ~*^en en;
}

server {
    listen       80;
    listen  [::]:80;
    server_name  localhost;

    #access_log  /var/log/nginx/host.access.log  main;

    # location / {
    #    root   /usr/share/nginx/html;
    #    index  index.html index.htm;
    # }

    # Fallback to default language if no preference defined by browser
    if ($accept_language ~ "^$") {
        set $accept_language "en";
    }

    location /ru/ {
      alias /usr/share/nginx/html/angular-phonebook/ru/;
      try_files $uri$args $uri$args/ /ru/index.html;
    }
    location /en/ {
      alias /usr/share/nginx/html/angular-phonebook/en/;
      try_files $uri$args $uri$args/ /en/index.html;
    }
    location / {
      alias /usr/share/nginx/html/angular-phonebook/$accept_language/;
      try_files $uri$args $uri$args/ /$accept_language/index.html;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    #location = /50x.html {
    #    root   /usr/share/nginx/html;
    #}

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}
root@edb915519670:/# exit
exit
C:\Program Files\Git\usr\bin>

Remove service

  • run the commands
docker service ls
docker service rm angular-phone-book-service
docker service ls

Remove configs

  • run the commands
docker config ls
docker config rm app-config.json
docker config rm nginxdefault.conf
docker config ls

Docker compose

  • Suppose we have more than two locales and our CMD[...] command is getting too long
    • we can simplify Dockerfile and use some features of the Docker compose
Click to show Dockerfile
################################################################
# Build the app in the separate image which is node:latest
################################################################
FROM node:latest as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration=ru --prod --output-path=/dist/angular-phonebook
RUN mv /dist /ru
RUN ng build --configuration=en --prod --output-path=/dist/angular-phonebook
RUN mv /dist /en


################################################################
# Copy form node:latest image into  nginx:latest and exec "CMD"
################################################################
FROM nginx:latest as nginxrslt
## COPY nginxdefault.conf /etc/nginx/conf.d/default.conf
COPY --from=build /ru/angular-phonebook/ru/ /usr/share/nginx/html/angular-phonebook/ru/
COPY --from=build /en/angular-phonebook/en/ /usr/share/nginx/html/angular-phonebook/en/
## CMD ["/bin/sh",  "-c",  "exec nginx -g 'daemon off;'"]

Create Docker compose

  • in the same folder we create docker-compose.yml-file with the content as follows
Click to show Dockerfile
services:
  angularphonebook:
    build: .
    container_name: 'angular-phone-book'
    configs:
      - source: appconfig
        target: /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.json
        uid: "103"
        gid: "103"
        mode: 0440
      - source: appconfig
        target: /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json
        uid: "103"
        gid: "103"
        mode: 0440
      - source: nginxdefaultconf
        target: /etc/nginx/conf.d/default.conf
        uid: "103"
        gid: "103"
        mode: 0440
    ports:
      - 90:80
    command: ["/bin/sh", "-c", "exec nginx -g 'daemon off;'"]
configs:
  appconfig:
    file: ./app-config.json
  nginxdefaultconf:
    file: ./nginxdefault.conf

Up Docker compose

  • run the command
docker-compose -f "docker-compose.yml" up -d --build

Down Docker compose

  • run the command
docker-compose -f "docker-compose.yml" down
  • remove image
docker image ls
docker image rm angularphonebook_angularphonebook

Bash Script

Create startnginx sh

  • in the same folder we create startnginx.sh file with the content as follows
#!/bin/bash
envsubst < /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.json
envsubst < /usr/share/nginx/html/angular-phonebook/en/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json
exec nginx -g 'daemon off;'

Dockerfile with Bash Script

  • in the same folder we create Dockerfile file with the content as follows
Click to show Dockerfile
################################################################
# Build the app in the separate image which is node:latest
################################################################
FROM node:latest as build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm install -g @angular/cli
RUN ng build --configuration=ru --prod --output-path=/dist/angular-phonebook
RUN mv /dist /ru
RUN ng build --configuration=en --prod --output-path=/dist/angular-phonebook
RUN mv /dist /en


################################################################
# Copy form node:latest image into  nginx:latest and exec "CMD"
################################################################
FROM nginx:latest as nginxrslt
COPY nginxdefault.conf /etc/nginx/conf.d/default.conf
COPY --from=build /ru/angular-phonebook/ru/ /usr/share/nginx/html/angular-phonebook/ru/
COPY --from=build /en/angular-phonebook/en/ /usr/share/nginx/html/angular-phonebook/en/
COPY ./startnginx.sh /
RUN chmod +x /startnginx.sh
CMD ["/startnginx.sh"]
## CMD ["/bin/sh",  "-c",  "envsubst < /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/ru/assets/app-config.json && envsubst < /usr/share/nginx/html/angular-phonebook/en/assets/app-config.template.json > /usr/share/nginx/html/angular-phonebook/en/assets/app-config.json &&  exec nginx -g 'daemon off;'"]
  • the command to create the image
docker build . -f Dockerfile -t angular-phone-book

Run Docker Image with Bash Script

  • run the command
docker run -d -p 90:80 -e PHNLP_URL="http://localhost:5055/" -e EMPLP_URL="http://localhost:5055/" -e DIVLP_URL="http://localhost:5055/"  -e WA_URL="http://localhost:5165/" -e SEC_URL="http://localhost:5165/" -e PERM_URL="http://localhost:5165/" angular-phone-book:latest
  • Inspect

  • to remove the container and image run the commands

docker ps
docker container stop e1c6ce6470a4
docker container rm e1c6ce6470a4
docker image rm angular-phone-book

Final Docker Image

docker build . -f Dockerfile -t angular-phn-bk
⚠️ **GitHub.com Fallback** ⚠️