skip to Main Content

dear stack overflow,

I’m trying to configure an Nginx reverse proxy to serve two react apps based on the base URL of each under the same domain, below you will find the current configuration used in the projects.

The current situation is that when browsing to the ADMIN URL (/admin/) the index.html file is loaded for every single request that is done by the browser, so all the assets are loaded as if they are the index.html, so my assumption is that the missing configuration is in one of the nginx.conf files?


Project Structure

  • NGINX (proxy)
    • NGINX – React APP (/)
    • NGINX – React APP Admin (/admin)

Configuration

Docker Compose

version: "3.7"

services:
  nginx:
    image: nginx:stable-alpine
    container_name: nginx
    ports:
      - 8000:80
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
  frontend:
    container_name: frontend
    build:
      context: ../frontend
      dockerfile: Dockerfile.prod
  frontend-admin:
    container_name: frontend-admin
    build:
      context: ../frontend-admin
      dockerfile: Dockerfile.prod

nginx.conf

http {

  server {
    listen                80;

    location /admin/ {
      proxy_pass          http://frontend-admin:80;
    }

    location / {
      proxy_pass          http://frontend:80;
    }

  }
}

REACT APPs

The files below are used in both projects, I have tried changing the ‘location /‘ from the admin project to ‘location /admin/‘ but without any success.

Dockerfile

# build environment
FROM node:14.16.1-alpine as build
WORKDIR /usr/src/app
COPY package.json yarn.lock ./
RUN yarn
COPY . ./
RUN yarn build

# production environment
FROM nginx:stable-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY --from=build /usr/src/app/dist /usr/share/nginx/html
COPY --from=build /usr/src/app/nginx.conf /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

nginx.conf

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

React APP

vite.config.js

// ...
export default defineConfig(({ mode }) => ({
  base: '/',
  // ...
}));

React APP Admin

vite.config.js

// ...
export default defineConfig(({ mode }) => ({
  base: '/admin/',
  // ...
}));

2

Answers


  1. What worked for me is to use rewrite on admin’s location block so the downstream nginx would receive clean paths.

    After some modifications, the global config looks like this:

    user nginx;
    
    events {
      worker_connections 1024;
    }
    
    http {
      include /etc/nginx/mime.types;
    
      server {
        listen 80;
    
        location /admin {
          rewrite /admin(.*) /$1 break;
          proxy_pass http://frontend-admin:80;
        }
    
        location / {
          proxy_pass http://frontend:80;
        }
      }
    }
    

    While app configs look like this:

    user nginx;
    
    events {
      worker_connections 1024;
    }
    
    http {
      include /etc/nginx/mime.types;
      sendfile on;
      server {
        listen 80;
    
        location / {
          root /usr/share/nginx/html;
          index index.html index.htm;
          try_files $uri $uri/ /index.html =404;
        }
      }
    }
    

    Then, requests are forwarded correctly to the admin nginx:

    frontend-admin    | 172.21.0.4 - - [19/Jul/2021:14:35:02 +0000] "GET / HTTP/1.0" 200 362 "-" "curl/7.77.0"
    nginx             | 172.21.0.1 - - [19/Jul/2021:14:35:02 +0000] "GET /admin HTTP/1.1" 200 362 "-" "curl/7.77.0"
    

    Also couple of things to notice:

    1. I had to include /etc/nginx/mime.types for all nginx to send the right Content-Types to the browser.
    2. Instead of removing default.conf and adding nginx.conf to /etc/nginx/conf.d I directly replaced /etc/nginx/nginx.conf
    3. Used nginx:latest and node:14.16.1 for building, but alpine should work just fine

    docker-compose.yml

    version: "3"
    
    services:
      nginx:
        image: nginx:latest
        container_name: nginx
        ports:
          - 8000:80
        volumes:
          - ./nginx.conf:/etc/nginx/nginx.conf
      frontend:
        container_name: frontend
        build:
          context: ./frontend
          dockerfile: ../Dockerfile.prod
      frontend-admin:
        container_name: frontend-admin
        build:
          context: ./frontend-admin
          dockerfile: ../Dockerfile.prod
    

    Dockerfile.prod

    # build environment
    FROM node:14.16.1 as build
    WORKDIR /usr/src/app
    COPY [ "package.json", "yarn.lock", "./" ]
    RUN yarn install --frozen-lockfile
    COPY . ./
    RUN yarn build
    
    # production environment
    FROM nginx:latest
    
    COPY --from=build /usr/src/app/dist /usr/share/nginx/html
    COPY --from=build /usr/src/app/nginx.conf /etc/nginx/nginx.conf
    
    Login or Signup to reply.
  2. better use sub domaines, this way you won’t have such issue, below an example I use for jenkins:

    server     {
        listen 80;
        listen [::]:80;
    
        server_name     jenkins.site;
    
        location / {
                    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;
                    include       /etc/nginx/mime.types;
                    proxy_pass http://jenkins:8080;
    

    }
    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search