Why MERN app can't communicate with backend if deployed with docker?

Viewed 190

I deployed a MERN app to a digital ocean droplet with Docker. If I run my docker-compose.yml file local on my PC it works well. I have 2 containers: 1 backend, 1 frontend. If I try to compose-up on droplet, it seems the frontend is ok but can't communicate with backend.

enter image description here

I use http-proxy-middleware, my setupProxy.js file:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function (app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://0.0.0.0:5001',
      changeOrigin: true,
    })
  );
};

I tried target: 'http://main-be:5001', too, as main-be is the name of my backend container, but get the same error. Just the Request URL is http://main-be:5001/api/auth/login in the chrome/devops/network.

enter image description here

...also another page: enter image description here

My docker-compose.yml file:

version: '3.4'

networks:
  main:

services:
  main-be:
    image: main-be:latest
    container_name: main-be
    ports:
      - '5001:5001'
    networks:
      main:
    volumes:
      - ./backend/config.env:/app/config.env
    command: 'npm run prod'
  main-fe:
    image: main-fe:latest
    container_name: main-fe
    networks:
      main:
    volumes:
      - ./frontend/.env:/app/.env
    ports:
      - '3000:3000'
    command: 'npm start'

My Dockerfile in the frontend folder:

FROM node:12.2.0-alpine
COPY . .
RUN npm ci
CMD ["npm", "start"]

My Dockerfile in the backend folder:

FROM node:12-alpine3.14
WORKDIR /app
COPY . .
RUN npm ci --production
CMD ["npm", "run", "prod"]

backend/package.json file:

  "scripts": {
    "start": "nodemon --watch --exec node --experimental-modules server.js",
    "dev": "nodemon server.js",
    "prod": "node server.js"
  },

frontend/.env file:

SKIP_PREFLIGHT_CHECK=true
HOST=0.0.0.0

backend/config.env file:

DE_ENV=development
PORT=5001

My deploy.sh script to build images, copy to droplet...

#build and save backend and frontend images
docker build -t main-be ./backend & docker build -t main-fe ./frontend
docker save -o ./main-be.tar main-be & docker save -o ./main-fe.tar main-fe

#deploy services
ssh root@46.111.119.161 "pwd && mkdir -p ~/apps/first && cd ~/apps/first && ls -al && echo 'im in' && rm main-be.tar && rm main-fe.tar &> /dev/null" 

#::scp file
#scp ./frontend/.env root@46.111.119.161:~/apps/first/frontend

#upload main-be.tar and main-fe.tar to VM via ssh
scp ./main-be.tar ./main-fe.tar root@46.111.119.161:~/apps/thesis/

scp ./docker-compose.yml root@46.111.119.161:~/apps/first/
ssh root@46.111.119.161 "cd ~/apps/first && ls -1 *.tar | xargs --no-run-if-empty -L 1 docker load -i"

ssh root@46.111.119.161 "cd ~/apps/first && sudo docker-compose up"

frontend/src/utils/axios.js:

import axios from 'axios';

export const baseURL = 'http://localhost:5001';

export default axios.create({ baseURL });

frontend/src/utils/constants.js:

const API_BASE_ORIGIN = `http://localhost:5001`;

export { API_BASE_ORIGIN };

I have been trying for days but can't see where the problem is so any help highly appreciated.

1 Answers

I am no expert on MERN (we mainly run Angular & .Net), but I have to warn you of one thing. We had an issue when setting this up in the beginning as well worked locally in containers but not on our deployment servers because we forgot the basic thing about web applications.

Applications run in your browser, whereas if you deploy an application stack somewhere else, the REST of the services (APIs, DB and such) do not. So referencing your IP/DNS/localhost inside your application won't work, because there is nothing there. A container that contains a WEB application is there to only serve your browser (client) files and then the JS and the logic are executed inside your browser, not the container.

I suspect this might be affecting your ability to connect to the backend.

To solve this you have two options.

  • Create an HTTP proxy as an additional service and your FE calls that proxy (set up a domain and routing), for instance, Nginx, Traefik, ... and that proxy then can reference your backend with the service name, since it does live in the same environment than API.
  • Expose the HTTP port directly from the container and then your FE can call remoteServerIP:exposedPort and you will connect directly to the container's interface. (NOTE: I do not recommend this way for real use, only for testing direct connectivity without any proxy)
Related