Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to route Angular app inside Nginx container?

My question

I want to deploy my angular app with docker containers. Unfortunately, I struggle to route specific uri.

The frontend image contains nginx and my compiled angular app. To carry out routing inside the container, nginx points all uris to my compiled app with try_files $uri $uri/ myapplication/index.html =404 and then angular router takes charge of everything.

However, when I run the frontend container. I'm just able to reach my app and the angular routing isn't working at all.

On the other hand, if I serve my compiled app with nginx without docker, the routing works perfectly.

Given that, I wonder:

how to route angular app properly inside a docker container ?

You can find all the details below Tl;dr


Whole build process

My app is composed of three services:

  1. Angular Frontend ( Nginx + Angular )
  2. Backend API ( NodeJs + Express )
  3. Database ( MongoDB )

1) Context

I have the following file tree:

|frontend
||package.json
||nginx.conf
||frontend.dockerfile
||Jumble
|backend
||package.json
||backend.dockerfile
||server.js
||Jumble
|docker-compose.yml

The Docker-compose.yml file:

services:
  frontend:
    container_name: clockmachine-frontend
    build:
      context: ./frontend
      dockerfile: clockmachine-frontend.dockerfile

  database:
    container_name: mongo
    image: mongo
    ports:
    - "27017:27017"

  backend:
    container_name: clockmachine-api
    image: clockmachine-api
    build:
      context: ./backend
      dockerfile: clockmachine-api.dockerfile
    environment:
      - NODE_ENV=production
    ports:
      - "3000:3000"

My Frontend Dockerfile:

#### Stage 0, Build Angular frontend
FROM node:latest as build
WORKDIR /app
COPY package.json package.json
RUN npm install
COPY . .
RUN npm run build -- --prod

####Stage 1, Build Nginx backend
FROM nginx:alpine
COPY --from=build /app/dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf

The nginx.conf file:

server {
  listen 0.0.0.0:80;
  listen [::]:80;
  default_type application/octet-stream;
  client_max_body_size  256M;

  root /usr/share/nginx/html/myapplication;
  index index.html;

  location / {
    try_files $uri $uri/ /index.html =404;
  }
}

The Backend dockerfile:

#### Stage 0, Build API
FROM node:alpine
LABEL author="Olivier D'Ancona"
WORKDIR /app
COPY package.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node","server.js"]

2) The Build

I built my application in the main folder like this:

sudo docker-compose build
sudo docker-compose up

The frontend will be compiled and be served inside nginx server. The backend will be deployed with nodejs and express. The database will pull a mongo image from the dockerhub.

At this point, the application is building without issue. I controlled the status of my services:

  1. http://localhost:27017/ Is connected with a Mongo db instance (It looks like you are trying to access MongoDB over HTTP on the native driver port.)
  2. http://localhost:3000/api/ is working as well ( I got in the terminal connection to mongo successfull)
  3. http://localhost:8080 Unable to connect (Firefox can't establish a connection to the server at localhost:8080.)
  4. http://localhost/student this is a static route of my app (Welcome to nginx! If you see this page, the nginx web server is successfully installed and working. Further configuration is required.)

3) The frontend routing problem

So at this point, the database and the backend are working together. My application is served on localhost/myapplication/index.html but the angular routing is not working instead the browser tells me he's unable to reach my app. I find someone have similar issue here.


4) Isolating the container

So I tried to build and run the frontend container alone:

I changed directory to the folder /frontend and typed:

sudo docker build -t mytag -f clockmachine-frontend.dockerfile .
sudo docker run mytag

The building process takes around 10 minutes and completes successfully. This time when I reached http://localhost/myapplication the application is working properly but again faced this routing problem.


5) Running nginx without container

So, I tried to run nginx directly on my machine. I compiled my frontend application with:

ng build --prod

and moved it over /usr/share/nginx/html/myapplication

I used the same nginx.conf configuration file and put it into /etc/nginx/conf.d/default.conf and I even tried to change the filename and not overwriting the default.conf file.

The result is my application running properly on localhost/myapplication with angular routing working perfectly!


6) Conclusion

All in all, I tried to run a docker-compose file and the frontend container had routing issues. Then I isolated this container and it had the same result. Finally, I served my application directly with nginx and it was a success because the routing is working properly.

like image 368
Olivier D'Ancona Avatar asked May 14 '20 14:05

Olivier D'Ancona


Video Answer


2 Answers

Finally, after 15 days of labour, I fixed it!

It was the listen [::]:80 line in the nginx configuration who was the problem. In addition, the port was not set properly for the frontend container.

This is how I achieve it:

frontend Dockerfile:

# Stage 0: compile angular frontend
FROM node:latest as build
WORKDIR /app
COPY . . 
RUN npm install
RUN npm run build --prod


# Stage 1: serve app with nginx server
FROM nginx:latest
COPY --from=build /app/dist/pointeuse  /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80

Nginx configuration:

server {
  listen 80;
  sendfile on;
  default_type application/octet-stream;

  gzip on;
  gzip_http_version 1.1;
  gzip_disable      "MSIE [1-6]\.";
  gzip_min_length   256;
  gzip_vary         on;
  gzip_proxied      expired no-cache no-store private auth;
  gzip_types        text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
  gzip_comp_level   9;

  root /usr/share/nginx/html;

  location / {
    try_files $uri $uri/ /index.html =404;
  }
}

Docker-compose file:

version: '3.1'

services:
        frontend:
                container_name: clockmachine-frontend
                depends_on:
                        - database 
                        - backend
                image: clockmachine-frontend
                build:
                        context: ./frontend
                        dockerfile: frontend.dockerfile
                ports:
                        - "80:80"
        database:
                container_name: mongo
                image: mongo
                ports:
                        - "27017:27017"

        backend:
                container_name: clockmachine-api
                image: clockmachine-api
                build:
                        context: ./backend
                        dockerfile: backend.dockerfile
                environment:
                        - NODE_ENV=production
                ports:
                        - "3000:3000"

As you can notice, I renamed the files of my context.

Containerize angular application with nginx server is a mess because the documentation are disparate.

In case you want deploy an Angular app

Check this link:

  1. Angular deployment Documentation
  2. Dan Wahlin example
  3. Serving static content with nginx
  4. Nginx.org documentation
like image 154
Olivier D'Ancona Avatar answered Oct 19 '22 10:10

Olivier D'Ancona


I think that the problem is that you use /myapplication prefix for the frontent application.
The best would be if you host app directly on the root url and not use the "virtual directory".

However if you still need to use it, you need to set in Angular index.html Base href (https://angular.io/guide/deployment#the-base-tag) to the path that you will use, in this case "/myapplication".

Secondly, the try_files $uri $uri/ myapplication/index.html =404 should be try_files myapplication/$uri myapplication/$uri/ myapplication/index.html =404 as you don't want to redirect elements that does not start with myapplication.

like image 22
Miq Avatar answered Oct 19 '22 09:10

Miq