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:
- The frontend speaks directly to the GraphQL service.
- The GraphQL service communicates to the microservice via a NATS server that acts as a message broker.
- 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
1 Answer
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],
},
},
]),
Could this just be a race condition (something trying to connect to the
nats
container before it's ready to accept requests)? If thenats
container is up and running, can you access the service on port 4222 from inside the container? What if youexec
into another one of your containers and try it?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.
May 11, 2022 at 8:03