skip to Main Content

As shown below, I have set up the environment with docker containers. The reason for this is because I only have a single VPS where I can run both frontend and backend on.

The way I want to tackle the problem is to put Nginx inside a docker container, using certbot for the verification (which already works) and then to reverse proxy into the frontend or backend based on what location the user is requesting.
enter image description here
As shown I am not sure how Nginx should communicate with the frontend or backend.

When I tried to do

upstream docker-frontend {
    server frontend:8081;
}

upstream docker-backend {
    server backend:8080;
}

Gave me a 502 bad gateway error. I somewhere on Stackoverflow found that instead of running the backend or frontend purely on 8080->8080/tcp or 8081->8081/tcp, I should run both on 80/tcp.
I also made sure to put all the containers on the same network, which seemed to have helped slightly.

Then as follows write the Nginx configuration

upstream docker-frontend {
    server frontend:80;
}

upstream docker-backend {
    server backend:80;
}

However by doing so I now have a completely blank page with nothing showing up. I can assure you that there’s no blank page in the frontend (which is built with Vue 3.0 vite).

nginx.conf

user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] :$request" '
        '$status $body_bytes_sent "$http_referer" '
        '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;
    sendfile on;
    keepalive_timeout 65;

    upstream docker-frontend {
        server frontend:80;
    }

    upstream docker-backend {
        server backend:80;
    }
 
    server {
        listen 8081;
 
        location / {
            proxy_pass         http://docker-frontend/;
            proxy_redirect     off;
            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;
        }
    }
 
    server {
        listen 8080;
 
        location / {
            proxy_pass         http://docker-backend/example/;
            proxy_redirect     off;
            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;
        }
    }

    server {
        listen 80;
        listen [::]:80;
        server_name www.example.com;

        include letsencrypt-acme-challenge.conf;

        return 301 https://www.example.com;
    }

    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name example.com;

        ssl_certificate fullchain.pem;
        ssl_certificate_key privkey.pem;
        ssl_trusted_certificate chain.pem;
        return 301 https://www.example.com;
    }

    server {
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        server_name www.example.com;
        ssl_certificate fullchain.pem;
        ssl_certificate_key privkey.pem;
        ssl_trusted_certificate chain.pem;
        root /usr/share/nginx/html/;

        location / {
            gzip off;
            root /usr/share/nginx/html/;
            index index.html;
            try_files $uri $uri/ /index.html;

            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
            add_header X-Frame-Options DENY;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1; mode=block";
            add_header Referrer-Policy "origin";

            proxy_pass http://docker-frontend/;
            proxy_redirect off;
            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;
        }

        location /example/ {
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
            add_header X-Frame-Options DENY;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1; mode=block";
            add_header Referrer-Policy "origin";

            proxy_pass http://docker-backend/example/;
            proxy_redirect off;
            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;
        }
    }
}

The nginx.conf created in the process of trying to learn how reverse proxy works, but I cannot seem to figure out why it is still showing a blank page. Any help would be appreciated.

EDIT: It does seem like it shows the index.html, but I don’t see any of the css or javascript within the page. Right now the dockerfile of the Vue app is configured as follows

Dockerfile

FROM node:lts-alpine as build-stage
RUN mkdir -p /app
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine as production-stage
COPY ./nginx.conf /etc/nginx/nginx.conf
RUN rm -rf /usr/share/nginx/html/*
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

However I am still not sure why it does not load the javascript or css. Is there a special way of setting up a reverse proxy for Vue? Or is my nginx.conf not set up properly?

EDIT: Even after adding hot reload

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

It still does not want to show anything but a blank page.

When I inspect the docker container of the Vue application with

docker exec -it example /bin/sh

In the /html folder it shows with the favicon, assets and the index.html, but there’s no css or js folder? Within the assets folder however there’s a weird index.a6f56555.js and index.1212255f.css. Did anyone else experience this before or is it just me?

EDIT: I found out that Vue isn’t correctly building itself with the Dockerfile to the /html folder, for some reason it only puts the index.html there but not the javascript or css?

vite.config.js

import { fileURLToPath, URL } from 'url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import svgLoader from 'vite-svg-loader'

export default defineConfig({
  server: {
    port: 8081
  },
  plugins: [vue(), svgLoader()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
  publicPath: process.env.NODE_ENV === 'production'
    ? ''
    : '/',
  css: {
    loaderOptions: {
      sass: {
        prependData: `@import "@/styles/_variables.scss";`
      },
    },
    preprocessorOptions: {
      scss: {
        implementation: require('sass'),
        additionalData: `
            @import "./src/assets/scss/main.scss";
        `
      }
    }
  },
  base: './'
})

Even with setting up the Vite config file as recommended by multiple sources and the package.json containing the

npm run build && vite preview –port 8081 –host

command, it still shows up with a blank page, can anyone tell me what I’ve been doing wrong? Because I have no clue at this point…

2

Answers


  1. Chosen as BEST ANSWER

    I managed to fix the problem myself. It took me quite some time to figure out, but by chance I stumbled on this page: https://dev.to/programmingdecoded/docker-configuration-with-nginx-routing-for-vue-and-laravel-49e9

    folder/file structure frontend

    /node_modules 
    /src
    nginx.conf
    Dockerfile
    vite.config.js
    

    new nginx.conf

    upstream docker-frontend {
        server example-frontend:8081;
    }
    
    upstream docker-backend {
        server example-backend:8080;
    }
    
    server {
        listen 80;
        listen [::]:80;
        server_name www.example.com;
    
        include letsencrypt-acme-challenge.conf;
    
        return 301 https://$host;
    }
    
    server {
        listen 443 ssl;
        listen [::]:443 ssl;
        server_name example.com;
    
        ssl_certificate fullchain.pem;
        ssl_certificate_key privkey.pem;
        ssl_trusted_certificate chain.pem;
        return 301 https://$host;
    }
    
    server {
        listen 443 ssl default_server;
        listen [::]:443 ssl default_server;
        server_name www.example.com;
        ssl_certificate fullchain.pem;
        ssl_certificate_key privkey.pem;
        ssl_trusted_certificate chain.pem;
    
    
        location / {
            gzip off;
            index index.html;
            root /usr/share/nginx/html/;
            error_log  /var/log/nginx/error.log;
            access_log /var/log/nginx/access.log main;
    
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
            add_header X-Frame-Options DENY;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1; mode=block";
            add_header Referrer-Policy "origin";
    
            proxy_pass http://docker-frontend;
            proxy_redirect off;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            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_cache_bypass $http_upgrade;
        }
    
        location /example/ {
            add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
            add_header X-Frame-Options DENY;
            add_header X-Content-Type-Options nosniff;
            add_header X-XSS-Protection "1; mode=block";
            add_header Referrer-Policy "origin";
    
            proxy_pass http://docker-backend/example;
            proxy_redirect off;
            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;
        }
    }
    

    As you can see above, I also made the upstream to 8081 and 8080. You can also see that I removed the http tag, workers tag, etc. This is because instead of replacing the nginx.conf inside the /etc/nginx, I replaced the /etc/nginx/conf.d/default.conf with my custom nginx.conf above.

    Then as followed I configured the Dockerfile

    FROM node:lts-alpine as build-stage
    RUN mkdir -p /app
    WORKDIR /app
    COPY package*.json ./
    RUN npm install
    COPY . .
    RUN npm run build
    
    FROM nginx:stable-alpine as production-stage
    EXPOSE 8081
    COPY nginx.conf /etc/nginx/conf.d/default.conf
    RUN rm -rf /usr/share/nginx/html/*
    COPY --from=build-stage /app/dist /usr/share/nginx/html
    CMD ["nginx", "-g", "daemon off;"]
    

    Here I made sure to expose the 8081 port instead of the 80 port and I also made sure that here as well, I should not replace the nginx.conf inside the /etc/nginx but the /etc/nginx/conf.d/default.conf with the nginx.conf below.

    Then the Vue application needs it's own nginx.conf as well Vue nginx.conf

    server {
        listen 8081;
        root /usr/share/nginx/html;
        include /etc/nginx/mime.types;
    
        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
    }
    

    Then running the following commands to start it up

    docker run -it -d -p 8081:8081 --network=example --name example-frontend example

    The command above runs the frontend or backend

    docker run -it --rm --name nginx -v ${PWD}/example/nginx.conf:/etc/nginx/conf.d/default.conf -v ${PWD}/example/letsencrypt-acme-challenge.conf:/etc/nginx/snippets/letsencrypt-acme-challenge.conf -v ${PWD}/example:/letsencrypt -v ${PWD}/example/letsencrypt/certs:/etc/letsencrypt --network=example -p 80:80 -p 443:443 nginx

    This command runs the nginx and makes sure to store the external files that are in the Ubuntu server inside the docker container. For this as well, make sure that you configure towards /etc/nginx/conf.d/default.conf and not replace the actual nginx.conf from /etc/nginx.

    To me this was a very strange solution, but it also made sense.


  2. Since you do not have multiple BE or FE containers, I do not think you need upstream. (See the loadbalancing usecase explained here

    What you want is nginx have one server, listening on port 80 (usually) and redirect calls to the different locations. something like

    server {
            listen 80;
            server_name  your.url.com;
    
            location /api {
                proxy_pass         http://backend_container_name:8080;
                ...
            }
     
            location / {
                proxy_pass         http://frontend_container_name:8081;
                ...
            }
        }
    

    We need the server name to know the base url. Then when a request comes to some.url.com/ or some.url.com/a-page for example, it will be redirected to the FE container, when your code makes a request to some.url.com/api/some_endpoint it will be redirected to the BE container.

    They have to be made from the browser to the same url:port, otherwise you’ll get cors issues, this is why we do the reverse proxy.

    From the container, to another container, a connection is easily made provided that they are:

    1. in the same docker network. OR
    2. have some discovery service url (for instance when using AWS ECS or the like)

    For the containers to be in the same network, all you need to do is either vreate a network and indiviually link the network to the containers, or just put them in the same docker-compose.yaml and use docker-compose up

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