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
The frontend code you posted is running in the browser as far as I can see:
But your browser does not eun within the docker container. Which means that it cannot resolve
VITE_BACK_PORT
.Chnage it to
And you should be fine.
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 indocker-compose.yml
.Try and replace the use of
localhost
in your frontend code, if it runs in a container: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: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, thelocalhost
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 yourdocker-compose.yml
.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.