简体   繁体   中英

Need help proxying React and NodeJS apps with nginx-proxy on a VPS, in Docker containers

What I'm trying to do is to deploy a dockerized monorepo project (using NX as the monorepo framework) with the Nestjs + React + MySQL + Nginx stack on a VPS. I want the nginx proxy to listen on the host's port 88 (because another stack uses port 80, it's an old stack I do not dare touch). The OS of the VPS is CentOS 7.

I'll try to spare most of the details of the builds (Dockerfile) but know that the builds work, it is all working in my local environment (mostly due to the fact that I dont use nginx-proxy for local development) and I know it's either a matter of my Docker configs (I use docker-compose) or the host's networking that comes into play.

Here's a 'bird's eye view' of the stack:

  • React-frontend container is running a react app (using nx serve react-frontend) on port 4200 in the container, exposing port 4200 to the host
  • backend-api container is running a nodejs app (using nodejs entrypoint) on port 3333 of the container, exposing the port to the host
  • a MySQL container running a mysql server running on port 3306 of the container, exposed on port 3307 of the Host
  • A nginx-proxy using the jwilder/nginx-proxy docker image (I also tried with nginxproxy/nginx-proxy docker image) listening on port 88 of the host and redirecting the request to react-frontend container through proxy pass (this is the part that I'm failing at).

So here's my "compose-prod.yml" docker-compose file:

version: "3.7"

networks:
  corp:
    driver: bridge
  nginx-proxy:
    external:
      name: nginx-proxy

volumes:
  backend-db-volume:
    driver: local

services:
  nginx-proxy:
    image: jwilder/nginx-proxy # also tried nginxproxy/nginx-proxy image
    container_name: nginx-proxy
    networks:
      - corp
      - nginx-proxy
    environment:
      HTTP_PORT: 88
    ports:
      - "88:88" # also tried "88:80" but that gives me "connection refused" in the browser
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro

  backend-db:
    image: backend-db
    hostname: backend-db
    restart: unless-stopped
    volumes:
      - backend-db-volume:/var/lib/mysql
    networks:
      - corp
    build:
      context: ./apps/backend-db
      dockerfile: ./Dockerfile
    ports:
      - 3307:3306
    expose:
      - 3306

  backend-api:
    container_name: backend-api
    depends_on:
      - backend-db
    build:
      context: ./
      cache_from:
        - base-image:nx-base
      dockerfile: ./apps/backend-api/Dockerfile
      args:
        NODE_ENV: "production"
        BUILD_FLAG: ""
    image: backend-api:nx-dev
    ports:
      - "3333:3333"
    environment:
      NODE_ENV: "production"
      PORT: 3333
      [... other env configs ommitted, like DB variables, etc.]
    networks:
      - corp
    restart: on-failure

  react-frontend:
    container_name: react-frontend
    build:
      context: ./
      dockerfile: ./apps/react-frontend/Dockerfile
      args:
        NODE_ENV: "production"
        BUILD_FLAG: ""
    image: react-frontend:nx-dev
    environment:
      VIRTUAL_HOST: react-frontend # note that my domain is react-frontend.com, obfuscated ofc ... which I also tried using in VIRTUAL_HOST config
      VIRTUAL_PORT: 4200
      NGINX_PROXY_CONTAINER: nginx-proxy
      NODE_ENV: "production"
      [...other env configs ommitted]
    ports:
      - "4200:4200"
    expose:
      - 4200
    networks:
      - nginx-proxy
      - corp
    restart: on-failure

The nginx-proxy container automatically detects containers running with VIRTUAL_HOST env. variable enabled, generates configs for those from the compose-prod.yml file. Right now, the configuration generated, that I get using the "docker exec nginx-proxy cat /etc/nginx/conf.d/default.conf" command is this:

# nginx-proxy version : 1.0.1-6-gc4ad18f
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
# scheme used to connect to this server
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
  default $http_x_forwarded_proto;
  ''      $scheme;
}

# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
# server port the client connected to
map $http_x_forwarded_port $proxy_x_forwarded_port {
  default $http_x_forwarded_port;
  ''      $server_port;
}

# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
# Connection header that may have been passed to this server
map $http_upgrade $proxy_connection {
  default upgrade;
  '' close;
}

# Apply fix for very long server names
server_names_hash_bucket_size 128;
# Default dhparam
ssl_dhparam /etc/nginx/dhparam/dhparam.pem;
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
  default off;
  https on;
}

gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
                 '"$request" $status $body_bytes_sent '
                 '"$http_referer" "$http_user_agent" '
                 '"$upstream_addr"';
access_log off;
                ssl_protocols TLSv1.2 TLSv1.3;
                ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
                ssl_prefer_server_ciphers off;
error_log /dev/stderr;
resolver 127.0.0.11;
# HTTP 1.1 support
proxy_http_version 1.1;
proxy_buffering off;
proxy_set_header Host $http_host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
proxy_set_header X-Original-URI $request_uri;
# Mitigate httpoxy attack (see README for details)
proxy_set_header Proxy "";
server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 503;
}
        # react-frontend
upstream react-frontend {
        ## Can be connected with "react-frontend_corp" network
        # react-frontend
        server <IP of react-frontend container on Docker network>:4200;
        # Cannot connect to network 'nginx-proxy' of this container
        # Cannot connect to network 'react-frontend_corp' of this container
        ## Can be connected with "nginx-proxy" network
        # react-frontend
        server <IP of react-frontend container on Docker network>:4200;
}
server {
        server_name react-frontend;
        listen 88 ;
        access_log /var/log/nginx/access.log vhost;
        location / {
                proxy_pass http://react-frontend;
        }
}

When I access "example.com:88" I get a "503 Service Temporarily Unavailable" page in my browser returned from nginx and I see this in nginx's access logs:

nginx-proxy     | nginx.1     | example.com xx.yy.zz.ip - - [06/Jul/2022:16:48:12 +0000] "GET / HTTP/1.1" 503 592 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"
nginx-proxy     | nginx.1     | example.com xx.yy.zz.ip - - [06/Jul/2022:16:48:12 +0000] "GET /favicon.ico HTTP/1.1" 503 592 "http://example.com:88/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" "-"

I omit the Dockerfiles since the nginx-proxy container is not built, it's taken as is from the image and all the builds work ... it's the deployment which gives me trouble.

Anyone who has any pointer on what I'm missing ? What I should check ? This is for a personal project and even though I can get around as a devop, Docker networking/deployment still baffles me sometimes.

EDIT: I'm adding the VPS (host) vhost config (nginx) here ... so maybe I can proxy-pass using this configuration ... how would I go about modifying this config so I can proxy pass requests to the "example" docker container exposing port 4200 (instead of a root directory on the VPS) ?

# configuration file /etc/nginx/conf.d/users/example.conf:
proxy_cache_path /var/cache/ea-nginx/proxy/example levels=1:2 keys_zone=example:10m inactive=60m;

#### main domain for example ##
server {
    server_name example.com www.example.com mail.example.com;
    listen 80;
    listen [::]:80;

    include conf.d/includes-optional/cloudflare.conf;

    set $CPANEL_APACHE_PROXY_PASS $scheme://apache_backend_${scheme}_51_222_24_216;

    # For includes:
    set $CPANEL_APACHE_PROXY_IP 51.222.24.216;
    set $CPANEL_APACHE_PROXY_SSL_IP 51.222.24.216;

    set $CPANEL_PROXY_CACHE example;
    set $CPANEL_SKIP_PROXY_CACHING 0;

    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /var/cpanel/ssl/apache_tls/example.com/combined;
    ssl_certificate_key /var/cpanel/ssl/apache_tls/example.com/combined;

    ssl_protocols TLSv1.2 TLSv1.3;
    proxy_ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;
    proxy_ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256;

    root /home/example/public_html;

    location /cpanelwebcall {
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass http://127.0.0.1:2082/cpanelwebcall;
    }

    location /Microsoft-Server-ActiveSync {
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass http://127.0.0.1:2090/Microsoft-Server-ActiveSync;
    }

    location = /favicon.ico {
        allow all;
        log_not_found off;
        access_log off;
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }

    location = /robots.txt {
        allow all;
        log_not_found off;
        access_log off;
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }

    location / {
        proxy_cache $CPANEL_PROXY_CACHE;
        proxy_no_cache $CPANEL_SKIP_PROXY_CACHING;
        proxy_cache_bypass $CPANEL_SKIP_PROXY_CACHING;

        proxy_cache_valid 200 301 302 60m;
        proxy_cache_valid 404 1m;
        proxy_cache_use_stale error timeout http_429 http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 1;
        proxy_cache_lock on;

        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }


    include conf.d/server-includes/*.conf;
    include conf.d/users/example/*.conf;
    include conf.d/users/example/example.com/*.conf;
}
server {
    listen 80;
    listen [::]:80;

    listen 443 ssl;
    listen [::]:443 ssl;

    ssl_certificate /var/cpanel/ssl/apache_tls/example.com/combined;
    ssl_certificate_key /var/cpanel/ssl/apache_tls/example.com/combined;

    server_name  cpanel.example.com cpcalendars.example.com cpcontacts.example.com webdisk.example.com webmail.example.com;

    include conf.d/includes-optional/cloudflare.conf;

    set $CPANEL_APACHE_PROXY_PASS $scheme://apache_backend_${scheme}_51_222_24_216;

    # For includes:
    set $CPANEL_APACHE_PROXY_IP 51.222.24.216;
    set $CPANEL_APACHE_PROXY_SSL_IP 51.222.24.216;

    location /.well-known/cpanel-dcv {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location /.well-known/pki-validation {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location /.well-known/acme-challenge {
        root /home/example/public_html;
        disable_symlinks if_not_owner;
    }

    location / {
        # Force https for service subdomains
        if ($scheme = http) {
            return 301 https://$host$request_uri;
        }

        # no cache
        proxy_cache off;
        proxy_no_cache 1;
        proxy_cache_bypass 1;

        # pass to Apache
        include conf.d/includes-optional/cpanel-proxy.conf;
        proxy_pass $CPANEL_APACHE_PROXY_PASS;
    }
}

If you want to access your app using example.com or www.example.com you have to set the server_name example.com *.example.com; . You can reach the docker container using DNS too while using docker-compose, in your case backend-api:3333 or react-frontend:4200 . There are few correction in your configs.

server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 503;
}
        # react-frontend
upstream frontends {
        server react-frontend:4200;
        server react-frontend:4200;
}
upstream backends {
        server backend-api:3333;
        server backend-api:3333;
}
server {
        server_name 127.0.0.1 example.com *.example.com;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        location / {
                proxy_pass http://frontends;
        }
        # if required , only then use it otherwise remove it [for direct api calls]
        location /api/v1 {
                proxy_pass http://backends;
        }
}

One can add more options or configs , according to the requirements. We are seeing 503 default page because of server_name _ configs in the above snippet. One can configure it , if needed. (like below snippet)

server {
        server_name _; # This is just an invalid value which will never trigger on a real hostname.
        server_tokens off;
        listen 88;
        access_log /var/log/nginx/access.log vhost;
        return 403 "..Ops";
}

Not sure, why two networks are required, if you are running everything in one compose file. If two networks are required, below is the example for docker-compose file and nginx.conf .

docker-compose.yaml

version: "3.7"
networks:
  corp:
    driver: bridge
  nginx-proxy:
    external:
      name: nginx-proxy

services:
  nginx-proxy:
    container_name: nginx-proxy
    image: nginx
    ports:
      - 3210:80
    networks:
      - nginx-proxy
      - corp
    volumes:
    - another-nginx.conf:/etc/nginx/conf.d/another-nginx.conf

  react-frontend:
    container_name: react-frontend
    image: httpd
    ports:
      - 3211:80
    networks:
      - nginx-proxy
      - corp
  
  backend-x:
    container_name: backend
    image: nginx
    ports:
      - 3212:80
    networks:
      - corp
  
  db-x:
    container_name: db
    image: httpd
    ports:
      - 3213:80
    networks:
      - corp

another-nginx.conf

server {
    server_name _; # This is just an invalid value which will never trigger on a real hostname.
    server_tokens off;
    listen 88;
    access_log /var/log/nginx/access.log;
    return 503;
}
    # react-frontend
upstream frontends {
    server react-frontend:80;
}
upstream backends {
    server backend:80;
}
server {
    server_name 127.0.0.1 localhost example.com *.example.com;
    listen 80;
    access_log /var/log/nginx/access.log;
    location / {
        proxy_pass http://frontends;
    }
    # if required , only then use it otherwise remove it [for direct api calls]
    location /api/v1 {
        proxy_pass http://backends;
    }
}

To create nginx-proxy network if not present.

docker network create nginx-proxy

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM