7

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...

King Reload
  • 2,780
  • 1
  • 17
  • 42

2 Answers2

9

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.

King Reload
  • 2,780
  • 1
  • 17
  • 42
0

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

Chai
  • 1,796
  • 2
  • 18
  • 31
  • it will need to be able to support multiple BE and FE containers for the future, so I will need to build it having upstream in mind, I also don't use docker-compose.yaml – King Reload Jun 12 '22 at 14:01
  • plus the current setup already does the reverse proxy correctly, but the Vue Vite is doing something weird, making it show a blank page, it already does correctly point to the FE container – King Reload Jun 12 '22 at 14:05
  • Ahh allright, sorry I misunderstood the question at first. But then it seems to be a question more related to building the vite app with docker than a reverse proxy , correct? I am not familiar with vite specifically, but in general it seems fine. Do you see any errors in the browser console ? All the requests for ja and css being made etc ? – Chai Jun 12 '22 at 14:41
  • I see that it loads the index.html, but it doesn't really show any .js or .css, when I go inside the Docker container that contains the FE application, it also shows in the dist folder where it gets built to that it only contains the index.html. However when I do the same thing for on the desktop locally it builds the same stuff and it does show correctly, which really confuses me to why it doesn't work – King Reload Jun 12 '22 at 14:58