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.
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
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
new nginx.conf
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
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
Then running the following commands to start it up
The command above runs the frontend or backend
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.
Since you do not have multiple BE or FE containers, I do not think you need
upstream
. (See the loadbalancing usecase explained hereWhat you want is nginx have one server, listening on port 80 (usually) and redirect calls to the different locations. something like
We need the
server name
to know the base url. Then when a request comes tosome.url.com/
orsome.url.com/a-page
for example, it will be redirected to the FE container, when your code makes a request tosome.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:
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 usedocker-compose up