4

I'm trying to Dockerize a classic NodeJS (Express, TS) + Angular application using nginx, but I struggle binding correctly my api throught nginx.

In fact, it seems that nginx target my localhost:80...

enter image description here

...while I want it to target my localhost:3000. When I manually ping that url while adding :3000 after localhost, it works.

I'm trying to keep my code simple. Here are my different files.

Back Dockerfile :

FROM node:15.14-alpine AS build

RUN mkdir -p /build/tmp
WORKDIR /build/tmp

COPY . .
RUN npm ci

CMD ["npm", "run", "start"]

Front Dockerfile :

### STAGE 1: Build ###
FROM node:15.14-alpine AS build
WORKDIR /usr/src/app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

### STAGE 2: Run ###
FROM nginx:1.17.1-alpine
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=build /usr/src/app/dist/front /usr/share/nginx/html

dockercompose.yml :

version: '3.4'

services:
  mymusicads-front:
    container_name: mymusicads-front
    build:
      context: ./front
      dockerfile: Dockerfile
    ports:
      - 80:80
    restart: on-failure
    depends_on:
      - mymusicads-api
    networks:
      - front-back

  mymusicads-api:
    container_name: mymusicads-api
    build:
      context: ./api
      dockerfile: Dockerfile
    ports:
      - 3000:3000
    networks:
      - front-back

networks:
  front-back:
    driver: bridge

nginx.conf :

events{}

http {
    include /etc/nginx/mime.types;

    upstream api {
      server 127.0.0.1:3000;
    }

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

    server {
      listen 80;
      server_name api;

      location /api/ {
          proxy_pass http://localhost:3000/;
          proxy_redirect off;
      }
    }
}

Thanks !

EDIT : By checking the error, we can see we're obtaining my Angular index.html file as a response, instead of the JSON object my API should return, which may signify an proxying error. Seems like nginx is missing the location /api/ instruction, or maybe the whole server one.

Denis Lebon
  • 953
  • 1
  • 7
  • 13
  • 1
    Don't you need to use container name `mymusicads-api` instead of `localhost` with the `proxy_pass`, e.g. `proxy_pass http://mymusicads-api:3000/`? – Ivan Shatsky Oct 26 '21 at 11:20
  • Hello Ivan ! It's not working either, but I still have the same error, which may mean my nginx config doesn't even detect I'm trying to proxy to my api. Any suggestion ? – Denis Lebon Oct 26 '21 at 12:01
  • 1
    From your screenshot I see that some HTML ` \n\n \n...` is returned instead of JSON object. Look at that HTML body text, maybe it has some clue? – Ivan Shatsky Oct 26 '21 at 12:10
  • Indeed, it is my Angular index.html. What I think is that while reverse proxying, my nginx stop at the first server and consider every operation like a call to my angular app, which always return the index.html. I've tried to swap the two server declarations, but it doesn't work too. I'll precise this in the original question :) – Denis Lebon Oct 26 '21 at 12:15
  • 1
    OMG, I didn't notice you are using two server blocks. Of course only one will pick up your request. Wait a minute, I'll try to write an alternate config as an answer... – Ivan Shatsky Oct 26 '21 at 12:19
  • 1
    Check the update to the answer. – Ivan Shatsky Oct 26 '21 at 12:50

1 Answers1

4

When you use multiple server blocks in your configuration, nginx chooses the server block to process your request by the HTTP Host header value. If it won't match any of the specified server names (or if the Host header is absent at all), the default server block will be used. You can specify the default server block explicitly using default_server flag for the listen directive. If you don't specify it explicitly, the very first server block listening on the specific port will be used. See the How nginx processes a request official documentation page or this answer for even more details.

Try this configuration instead:

events {}

http {
    include /etc/nginx/mime.types;
    server {
        listen 80;
        root /usr/share/nginx/html;
        index index.html;
        location / {
            try_files $uri $uri/ /index.html;
        }
        location /api/ {
            proxy_pass http://mymusicads-api:3000/;
            proxy_redirect off;
        }
    }
}

You don't need to expose the node.js port 3000 to the outer world, it is only mymusicads-front container that should have an access to that port. According to What is the difference between docker-compose ports vs expose SO thread your dockercompose.yml file should be something like

version: '3.4'

services:
  mymusicads-front:
    container_name: mymusicads-front
    build:
      context: ./front
      dockerfile: Dockerfile
    ports:
      - 80:80
    restart: on-failure
    depends_on:
      - mymusicads-api
    networks:
      - front-back

  mymusicads-api:
    container_name: mymusicads-api
    build:
      context: ./api
      dockerfile: Dockerfile
    expose:
      - 3000
    networks:
      - front-back

networks:
  front-back:
    driver: bridge
Ivan Shatsky
  • 13,267
  • 2
  • 21
  • 37
  • Great, thank you very much for those explanations ! I'm having a 502 Bad Gateway now, which is definitely an improvement, I will update my question when I found how to solve that. Answer accepted ✔ – Denis Lebon Oct 26 '21 at 13:07
  • 1
    The corresponding line from the nginx error log can be really helpful to track that. Additionally, any request like `/api/addata/Lucho` will appear at your node.js container as a `/addata/Lucho`.If that behavior is undesirable, change `proxy_pass http://mymusicads-api/;` directive to `proxy_pass http://mymusicads-api;` (without the trailing slash). See [A little confused about trailing slash behavior in nginx](https://stackoverflow.com/questions/53649885/a-little-confused-about-trailing-slash-behavior-in-nginx) SO thread for the details. – Ivan Shatsky Oct 26 '21 at 13:23
  • Is it supposed to hit `http://mymusicads-api/...` then ? It still targets `http://localhost/api/...`. The error line is actually `2021/10/26 14:08:15 [error] 7#7: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.176.1, server: , request: "GET /api/addata/Lucho HTTP/1.1", upstream: "http://192.168.176.2:80/addata/Lucho", host: "localhost", referrer: "http://localhost/dashboard` – Denis Lebon Oct 26 '21 at 14:25
  • 1
    I'm sorry, of course it should include the port number: `proxy_pass http://mymusicads-api:3000/;` (or `proxy_pass http://mymusicads-api:3000;` if you need to preserve the `/api` prefix for the node.js application). – Ivan Shatsky Oct 26 '21 at 14:33
  • It works, tysm Ivan ! Port + removing the final slash was the solution, since I use `/api/...` on my backend to call this endpoint. – Denis Lebon Oct 26 '21 at 14:42