Docker Containers failing to connect to each other – connection refused

Docker Containers failing to connect to each other – connection refused


1

I have a simple application orchestrated by Docker Compose. It consists of a simple frontend (built using NextJS), a GraphQL API (built with NestJS), and a microservice (built with NestJS).

The architecture is as follows:

  1. The frontend speaks directly to the GraphQL service.
  2. The GraphQL service communicates to the microservice via a NATS server that acts as a message broker.
  3. The microservice communicates directly with a MongoDB instance via the NodeJS driver library.

When I run everything locally, it all connects perfectly and the application works. However when I run everything in Docker containers (using Docker Compose) I find that the GraphQL API cannot connect to the NATS server and the microservice cannot connect with the MongoDB instance.

Here is my docker-compose.yml file:

version: '3'

services:
  nats:
    container_name: nats
    image: nats
    ports:
      - 4222:4222
      - 6222:6222
      - 8222:8222
    networks:
      - services

  mongo:
    container_name: mongo
    image: mongo
    restart: always
    ports:
      - 27017:27017
    networks:
      - services

  frontend:
    container_name: frontend
    build:
      context: ./apps/frontend/.
      dockerfile: Dockerfile
      args:
        - NODE_ENV=development
    ports:
      - 3000:3000
  
  gateway:
    container_name: gateway
    build:
      context: ./apps/gateway/
      dockerfile: Dockerfile
      args:
        - NODE_ENV=development
    environment:
      - NATS_URL="nats://nats:4222"
    ports:
      - 3001:3001
    networks:
      - services
    depends_on:
      - nats
      - mongo

  user-service:
    container_name: user-service
    build:
      context: ./apps/user-service
      dockerfile: Dockerfile
      args:
        - NODE_ENV=development
    environment:
      - NATS_URL="nats://nats:4222"
      - MONGO_URL="mongodb://localhost:27017"
    ports:
      - 3002:3002
    networks:
      - services
    depends_on:
      - nats
      - mongo

networks:
  services:
    driver: bridge

Here is the code that attempts to send a message to the NATS server:

import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { map, Observable, tap, toArray } from 'rxjs';
import { User } from './models/user.model';

@Injectable()
export class UsersService {
  constructor(@Inject('NATS_CLIENT') private nats: ClientProxy) {}

  findAll(): Observable<User[]> {
    return this.nats.send('users.get-users', {}).pipe(
      tap((result) => console.log('result', result)),
      map((user) => ({ name: user.name, email: user.email })),
      toArray(),
    );
  }
}

And here is the module that initialises the NATS client:

import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { UsersResolver } from './users.resolver';
import { UsersService } from './users.service';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'NATS_CLIENT',
        transport: Transport.NATS,
        options: {
          url: process.env.NATS_URL,
        },
      },
    ]),
  ],
  providers: [UsersService, UsersResolver],
})
export class UsersModule {}

As you can see the connection string comes through as an environmental variable that was previously set in the docker-compose.yml file.

When I send a GraphQL request to trigger the send message I get the following error:

Error: connect EADDRNOTAVAIL ::1:4222 - Local (:::0)
     at internalConnect (node:net:953:16)
     at defaultTriggerAsyncIdScope (node:internal/async_hooks:465:18)
     at node:net:1044:9
     at processTicksAndRejections (node:internal/process/task_queues:78:11) {
   errno: -99,
   code: 'EADDRNOTAVAIL',
   syscall: 'connect',
   address: '::1',
   port: 4222
}

Potential solutions tried:

  • I’ve tried different urls for the connection string including nats://nats:4222, nats://localhost:4222, nats://127.0.0.1:4222, nats://0.0.0.0:4222, nats://host.docker.internal:4222, etc. The end result of each of them is either the above error or a connection refused error.
  • I’ve tried to ensure that the containers can communicate by running docker exec [container1] ping [container2] -c2 where [container1]/[container2] are the respective container names. Doing this I always get a response.

It’s been a long time since I’ve used Docker containers so I’d be interested if anybody has any ideas as this is driving me crazy!!

2

  • Could this just be a race condition (something trying to connect to the nats container before it's ready to accept requests)? If the nats container is up and running, can you access the service on port 4222 from inside the container? What if you exec into another one of your containers and try it?

    – larsks

    May 9, 2022 at 17:55

  • @larsks I don't think so as I was trying to use the depends_on functionality from docker compose so that anything that utilises the NATS server would be created once the NATS server is established. Also there are communication issues from the user-service to MongoDB to so I'm leaning towards it being a network issue despite being able to ping between the containers.

    – Matt W

    May 11, 2022 at 8:03

1 Answer
1


0

I was having the same exact issue you were with almost the same configuration. What fixed it for me was looking at the NATS documentation for Nest JS https://docs.nestjs.com/microservices/nats. If you look at all the examples, they use the servers key to configure NATS instead of url. So try using the following inside your UsersModule:

ClientsModule.register([
      {
        name: 'NATS_CLIENT',
        transport: Transport.NATS,
        options: {
          servers: [process.env.NATS_URL],
        },
      },
    ]),



Leave a Reply

Your email address will not be published. Required fields are marked *