skip to Main Content

I’m creating a website using vite/React for the frontend and socket.io for the backend server in two separate docker containers, and I have an issue, I can’t connect to the socket.io server, I get this error: Firefox can’t establish a connection to the server at ws://localhost:6666/socket.io/?EIO=4&transport=websocket.
I manage my containers using docker-compose, I use the right port in my socket.io server and the frontend and backend containers are in the same docker network.
Here is my docker-compose:

version: '3.3'

services:

  backend:
    build: ./backend
    working_dir: /app
    environment:
      PORT: ${BACK_PORT}
      JWT_SECRET: ${JWT_SECRET}
    ports:
      - ${BACK_PORT}:${BACK_PORT}
    volumes:
      - ./backend:/app
      - ./shared_types:/app/src/shared_types
    networks:
      - all
    restart: unless-stopped
    command: sh start.sh

  frontend:
    depends_on:
      - backend
    image: node:lts-alpine
    working_dir: /app
    environment:
      PORT: ${FRONT_PORT}
      VITE_BACK_PORT: ${BACK_PORT}
    ports:
      - ${FRONT_PORT}:${FRONT_PORT}
    volumes:
      - ./frontend:/app
      - ./shared_types:/app/src/shared_types
    networks:
      - all
    restart: unless-stopped
    command: sh start.sh

networks:
  all:
    driver: bridge

The backend Dockerfile:

FROM node:lts-alpine

RUN npm i -g npm@latest

CMD ["npm", "run", "dev"]

the .env file:

BACK_PORT=6666
FRONT_PORT=8080
JWT_SECRET=ilovecandies

and I have a basic socket.io server like this:

import type { ServerOptions, Socket } from './types/server';
import { Server } from 'socket.io';
import http from 'http';
import { SignJWT, jwtVerify } from 'jose';
import express from 'express';

const createServerOptions = (options?: Partial<ServerOptions>): ServerOptions => {
  return {
    port: 3000,
    ...options,
  };
};

export const createServer = (options?: Partial<ServerOptions>) => {
  const serverOptions = createServerOptions(options);
  const app = express();
  const httpServer = http.createServer(app);
  const server: Socket = new Server(httpServer, {
    cors: {
      origin: '*',
    },
    transports: ['websocket'],
  });
  const secret = new TextEncoder().encode(process.env.JWT_SECRET!);

  server.on('connection', async socket => {
    console.log(`Client ${socket.id} connected!`);

    const verify = await jwtVerify(socket.handshake.auth.token, secret);

    console.log(verify);
    socket.on('disconnect', () => {
      console.log(`Client ${socket.id} disconnected!`);
    });
  });

  return {
    start: () => {
      console.log(`Server: listening on port ${serverOptions.port}.`);
      server.listen(serverOptions.port);
    }
  };
};

and the main.ts:

import { createServer } from './server';

(() => {
  const server = createServer({
    port: +process.env.PORT!,
  });
  server.start();
})();

(The Socket type is a custom socket.io Server with custom events map)

and in my frontend, when I’m trying to connect like this:

const socket = io(`http://localhost:${import.meta.env.VITE_BACK_PORT!}`, {
  transports: ["websocket"],
  reconnection: false,
  auth: {
    token:
      document.cookie
        .split("; ")
        .find(x => x.startsWith("snet-token="))
        ?.split("=")[1] ?? "",
  },
});

It doesn’t work, I don’t know why. Instead of localhost I also tried 0.0.0.0, and backend, and I also tried everything without the http:// prefix, but none of that worked.

2

Answers


  1. The frontend code you posted is running in the browser as far as I can see:

      const socket = io(`http://localhost:${import.meta.env.VITE_BACK_PORT!}`, {
      transports: ["websocket"],
      reconnection: false,
      auth: {
        token:
          document.cookie
            .split("; ")
            .find(x => x.startsWith("snet-token="))
            ?.split("=")[1] ?? "",
      },
    });
    

    But your browser does not eun within the docker container. Which means that it cannot resolve VITE_BACK_PORT.

    Chnage it to

      const socket = io(`http://localhost:6666...
    

    And you should be fine.

    Login or Signup to reply.
  2. When running applications in Docker containers, especially when using Docker Compose, containers do not know each other by localhost. They are aware of each other by their service names defined in docker-compose.yml.

    Try and replace the use of localhost in your frontend code, if it runs in a container:

    const socket = io(`http://backend:${import.meta.env.VITE_BACK_PORT!}`, { ... });
    

    Again, this assumes your frontend is running inside a container, and it tries to access the backend container.

    Make sure both frontend and backend are on the same network. Based on your docker-compose.yml, they are, but it is always good to double-check with:

    docker network inspect [NETWORK_NAME]
    

    Replace [NETWORK_NAME] with your network name, which should be the folder’s name followed by _all in your case, unless you changed the folder’s name.

    And check your CORS configurations on the backend does allow the frontend to connect. For the sake of debugging, you have set origin: '*' which should technically allow any origin, but once you have fixed the issue, you should restrict it to only the frontend’s domain or IP for security reasons.


    Note: if your frontend code runs in the browser, then localhost refers to the machine where the browser is running, not the Docker container. Meaning, if you are accessing the frontend from the browser on your host machine, the localhost correctly refers to the host machine indeed, not the container.
    Therefore, to access the backend (which is in a container), you would indeed use localhost:6666 as your address, assuming your port mapping (6666:6666) is set correctly in your docker-compose.yml.

    const socket = io(`http://localhost:6666`, {
        transports: ["websocket"],
        reconnection: false,
        auth: {
            token: document.cookie.split("; ").find(x => x.startsWith("snet-token="))?.split("=")[1] ?? "",
        },
    });
    

    But make sure you have set the environment variable correctly in Vite’s environment. If you are using the development environment, the environment variable should be in a file named env.dev (if that is how you have set up Vite, see "Env Variables and Modes ") or simply in the .env file.

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