skip to Main Content

I have an application which consists of two containers:

  • a backend node.js server on express.js, referred to as ‘app-server’
  • a front-end react.js client based on create-react-app, referred to as ‘app-client’. This one uses an nginx server to set up reverse proxy for /api requests to the application – like this post describes

Both are dockerized and deployed on AWS ECS Fargate and are supposed to be hosted on the same domain example.com, just different paths, respectively /app-server and app-client. So the users would access the app via https://example.com/app-client. In the future we would like to host more apps on the same domain using path based routing.

To achieve that we have defined a rule on the Application Load Balancer listener on EC2 which looks like this:

IF
- Path is: /app-client*
- Host is: example.com

THEN
- Forward to
  - app-client-service-alb-h: 1 (100%)
  - Group-level stickiness: Off

Also tested - Path is: /app-client, without the * at the end.

I managed to adjust the backend express server to work with this scenario but I’m struggling to make the React.js app work with the path forwarding – it just shows a blank page (worked without a problem when it was hosted directly on a domain, let’s say http://app-client.com).

My guess is it has something to do with the nginx config because the request URL’s are correct but there are just no .html, .js and .css files there.

Dockerfile looks like this:

FROM node:11.5.0 as builder

WORKDIR /usr/src/app

ADD ./ /usr/src/app

RUN npm install && 
    npm run build


# ------------------------------------------------------
# Production Build
# ------------------------------------------------------
FROM nginx:1.16.0-alpine

COPY --from=builder /usr/src/app/build/ /usr/share/nginx/html

RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Nginx config is set up this way:

server {

  listen 80;

  server_name example.com;

  location /api {
    proxy_pass https://example.com/app-server;
    proxy_pass_request_headers on;
  }   

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

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }

}

React app’s package.json file was adjusted by adding homepage setting:

"homepage": "/app-client"

Also tried with "homepage": "https://example.com/app-client". In both scenarios requested paths look correct, for example:

https://example.com/app-client/static/js/main.32859df6.chunk.js

The problem is that there is nothing found under this address, the page is blank. That’s why I am assuming that nginx config needs to be adjusted to provide the file from correct location but have no idea what to adjust (I did also test removing the "homepage" from package.json but that also didn’t solve it.).

I tried changing the location to location /app-client { or location ^~ /app-client { but that didn’t work:

location /app-client {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

I blindly followed many other solutions, for example:

None of them fixed the problem and I’m a rookie in that area so I’m stuck… Please help 🙁

——- Edited ——-

The issue is solved and I’m posting the answer in case someone else finds it helpful.

The Nginx config should look like this, with appropriate rewrite defined:

server {

  listen 80;

  # All files should be at this directory; put it above location, otherwise it was looking in a wrong directory somewhere in  `/etc/...`
  root /usr/share/nginx/html;

  # Prefix app-client -> rewrite
  location /app-client {
    rewrite ^/app-client(?:/(.*))?$ /$1;
  }

  # proxy for backend server /api requests
  location /api {
    # the ending `/api` part depends on whether your server routes also start with `/api` or not, mine do hence it was required
    proxy_pass https://example.com/app-server/api; 
    proxy_pass_request_headers on;
  } 
  
  location / {
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

}

2

Answers


  1. Chosen as BEST ANSWER

    The issue is solved and I'm posting the answer in case someone else finds it helpful.

    The Nginx config should look like this, with appropriate rewrite defined:

    server {
    
      listen 80;
    
      # All files should be at this directory; put it above location, otherwise it was looking in a wrong directory somewhere in  `/etc/...`
      root /usr/share/nginx/html;
    
      # Prefix app-client -> rewrite
      location /app-client {
        rewrite ^/app-client(?:/(.*))?$ /$1;
      }
    
      # proxy for backend server /api requests
      location /api {
        # the ending `/api` part depends on whether your server routes also start with `/api` or not, mine do hence it was required
        proxy_pass https://example.com/app-server/api; 
        proxy_pass_request_headers on;
      } 
      
      location / {
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
      }
    
    }
    

  2. Here is an example of my microservices application hosted on AWS with ALB & ECS.

    ECS has multiple backend services, react app static files being served from frontend container service which is NGINX reverse proxy.

    NGINX_ALB is the ALB DNS name stored in env variable inside container during Dockerfile build.

    Gets substituted into nginx config using:

    CMD ["/bin/sh", "-c", "export NGINX_ALB && envsubst ‘$$NGINX_ALB’ < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/nginx.conf && nginx -g ‘daemon off;’"]

    upstream alb {
    least_conn;
    server ${NGINX_ALB}:80 max_fails=3;
    }
    
    server {
    # ref https://www.veggiespam.com/bad-headers/
    proxy_hide_header Server;
    proxy_hide_header X-Powered-By;
    proxy_hide_header X-AspNetMvc-Version;
    proxy_hide_header X-AspNet-Version;
    proxy_hide_header X-Drupal-Cache;
    proxy_hide_header X-Drupal-Dynamic-Cache;
    proxy_hide_header X-Generator;
    proxy_hide_header X-Runtime;
    proxy_hide_header X-Rack-Cache;
    
    # proxy_set_headers moved to server directive allowing them to be referenced by all locations
    proxy_http_version 1.1;
    proxy_set_header   Connection ""; 
    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-Host $server_name;
    
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 300s;
    
    listen 80;
    
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    
    location /auth/ {  
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /api/sigma {   
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /api/ {  
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /db {  
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /scheduler {  
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /validator {
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /alarm/ {
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    location /reporter {
        proxy_pass http://alb;
        proxy_redirect off;
    }
    
    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        root  /usr/share/nginx/html;
    }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search