MongoServerSelectionError: connect ECONNREFUSED insdie docker container

I am upgrading mongo db driver from 2.3 to 4.13 and using mongo db and a node app with docker containers.
Docker-compose.yml
image: mongo:latest
and image for node is 18.12.1

After upgrading the driver i get error
MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
at Timeout._onTimeout (/srv/deployments/profile-service/node_modules/mongodb/lib/sdam/topology.js:285:38)
at listOnTimeout (node:internal/timers:564:17)
at process.processTimers (node:internal/timers:507:7) {
reason: TopologyDescription

{ type: ‘Unknown’, servers: Map(1) { ‘localhost:27017’ => [ServerDescription] }
,
stale: false,
compatible: true,
heartbeatFrequencyMS: 10000,
localThresholdMS: 15,
setName: null,
maxElectionId: null,
maxSetVersion: null,
commonWireVersion: 0,
logicalSessionTimeoutMinutes: null
},
code: undefined,
[Symbol(errorLabels)]: Set(0) {}
}

Please note it works fine outside docker container

Each container has its own network namespace. Using 127.0.0.1 as the mongo address is directing node to connect to the node container!

Given the compose file below the mongodb uri can be mongodb://mongo

version: '3'
services:
  mongo:
    image: mongo:6.0
    ... 
    ...

  nodeapp:
    image: nodeapp
    ...
    ...

Hi,
I am using mongo:latest image and for connection i am using the service name. Please find below my docker-compose file

version: '3.7'
services:
  mongo:
    image: mongo:latest
    networks:
      - Test
    ports:
      - "27017:27017"
    volumes: 
      - mongo_data:/data/db
main-api:
    restart: always
    build:
      context: /node-service
      dockerfile: /node-service/dev.Dockerfile
      args: 
        NPM_TOKEN: ${NPM_TOKEN}
    # command: npm start
    image: node-service-local
    environment:
      - NODE_ENV=qa
      - GRP_ID=${GRP_ID}
    ports:
      - "30002:30002"
    networks: 
      - Test
    volumes: 
      - ${HOME}/node-service:/srv/deployments/node-service
    depends_on:
      - mongo

My connection URL is mongodb://mongo:27017/db_name?directConnection=true&retryWrites=true&w=majority

I created a sample app that was connecting to mongo on 27017 since I exposed it. I did not use docker and directly run the app and everything works fine. Only when I run the app inside docker container, I am getting this error

Is your mongo container a single node replica set? You can use the directConnection option to the driver.

Given your driver upgrade and use of the service name in the mongo uri this make me think the auto discovery and unified topology in the 4.0 is detecting the replicaSet member hostname and then attempting connection.

If the directConnection works for you that may be satisfactory. The actual solution would be to update the replicaSet member to use the servicename.

Hi Chris,
It is a single node replica. What do you mean by You can use the directConnection option to the. I am passing this as part of connection string

So it is, something wrong with my eyes today! :thinking:

That should be working, and I just tried it myself on a single node replicaSet.

Is your code consuming the uri as-is or doing some processing on it first ?

dumb js code
const { MongoClient } = require("mongodb");

const uri = process.env.MONGO_URI;
const client = new MongoClient(uri);

async function run() {
  try {
    const fooColl = await client.db("test").collection("foo");
    const cur = fooColl.find({}).limit(10);
    for await (const doc of cur) {
      console.log(doc);
    }
  } finally {
    await client.close();
  }
}

run().catch(console.dir);

With directConnect:

MONGO_URI="mongodb://mongo/?serverSelectionTimeoutMS=3000&directConnection=true" node connectandprint.js 
{ _id: 0 }

Without directConnect:

MONGO_URI="mongodb://mongo/?serverSelectionTimeoutMS=3000" node connectandprint.js 
MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
    at Timeout._onTimeout (/home/node/app/node_modules/mongodb/lib/sdam/topology.js:285:38)
    at listOnTimeout (node:internal/timers:564:17)
    at process.processTimers (node:internal/timers:507:7) {
  reason: TopologyDescription {
    type: 'ReplicaSetNoPrimary',
    servers: Map(1) { '127.0.0.1:27017' => [ServerDescription] },
    stale: false,
    compatible: true,
    heartbeatFrequencyMS: 10000,
    localThresholdMS: 15,
    setName: 's0',
    maxElectionId: ObjectId { [Symbol(id)]: [Buffer [Uint8Array]] },
    maxSetVersion: 5,
    commonWireVersion: 0,
    logicalSessionTimeoutMinutes: null
  },
  code: undefined,
  [Symbol(errorLabels)]: Set(0) {}
}

Hi Chris,
Thanks for the code. I was missing await keyword.
As per doc which is https://www.mongodb.com/docs/drivers/node/current/quick-start/

client.db('sample_mflix'); was missing await keyword. after adding await, I am able to get the connection.
But facing another issue which is
Unhandled rejection MongoError: Unsupported OP_QUERY command: find. The client driver may require an upgrade. For more details see https://dochub.mongodb.org/core/legacy-opcode-removal

Please note i am using the latest mongo driver which is 4.13.

try separating database and collection assignments:

    const database = client.db('sample_mflix');
    const movies = database.collection('movies');

and you are using await in the wrong place. these two lines prepare the connection but do not use the connection until you execute commands/queries on the collection. that part is the one that needs it.

    const movie = await movies.find(query);

Hi Yilmaz,
Error was resolved when started using await client.db(‘test’). If I do not use the await keyword, I get above mentioned error. I know that even mongo doc does not recommend using await but not sure what to do to resolve the issue.

I do not think this solves the problem because there is another problem masking its correctness.

By the way, I am now beginning to suspect that it was related to your docker setup all this time. You see, “depends_on” is mainly to check if the container is booted but you need to also wait for the mongodb to fully start up listening on the port. depending on the data size in its data volume, this will take longer than you expected thus refusing any connection. (or the host might be slow to boot up mongodb container).

revert to the previous version and try starting your containers separately:

docker compose start mongo
# and after 10 seconds for example
docker compose start nodeapp

and stop/remove only the application container, not the database, while you are still developing. (unless you need to reset it too)

you can later write scripts to tap into the container and check the health of the mongod process, so that the “depends_on” would correctly identify the readiness of it. check this thread:

mongodb - Why does the docker-compose healthcheck of my mongo container always fail? - Stack Overflow

Hi Yilmaz,
So i was using nodemon which detects any changes and restarts the node server. Now in my case when mongo was up and I tried restarting the node server using some file change, I was still getting this. Now I have not modified anything but used await and everything works fine. I agree that we should check for mongo db health before we bring up the node container

let me take you basics for the trio, mongodb, nodejs and docker (compose). please setup the following simple system, inspect, and try to check which part of your code deviates, then let me know the result.

1- create a new test folder, copy the following compose content to a “compose.yml” file

compose.yml
version: '3.7'
name: "mongo-node-docker"
services:
  mongo:
    image: mongo:latest
    ports:
      - "27017:27017"
    volumes: 
      - ./mongo_data:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  nodeapp:
    image: node:19-alpine
    working_dir: /app
    command: "npm start" # "npm install" then "npm start"
    ports:
      - "8080:8080"
    volumes: 
      - ./nodeapp:/app
    depends_on:
      - mongo
    environment:
      MONGODB_ADMINUSERNAME: root
      MONGODB_ADMINPASSWORD: example
      MONGODB_URL: mongodb://root:example@mongo:27017/

2- create “mongo_data” folder to bind to mongodb data folder. binds are faster to remove than a volumes

3- create “nodeapp” folder. inside create 2 files, “package.json” and “index.js”, then copy following contents

package.json
{
  "name": "nodeapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mongodb": "^4.13.0"
  }
}
index.js
const { MongoClient } = require("mongodb");

let uri = process.env.MONGODB_URL;

const client = new MongoClient(uri);

async function run() {
  try {
    const conn = await client.connect()
    console.log("from: ",conn.s.url)

    const database = client.db("testdb");
    const coll = database.collection('testcoll');
    
    await coll.insertOne({"name":"testname"})

    const data = await coll.findOne({});
    console.log(data);

  } finally {
    await client.close();
  }
}
run().catch(console.dir);

4- run database server first, as a service: docker compose run -d mongo

5- then run app in foreground: docker compose run --rm nodeapp

  • IMPORTANT: you need to run this twice. I use “command” in the compose file, thus you need to first run with command: "npm install" for the first run.

6- you should see the following output (different id, though)

from:  mongodb://root:example@mongo:27017/
{ _id: new ObjectId("63c5dbffaef20bb1535ec6c1"), name: 'testname' }

you will immediately notice the use of await keyword; when I make the open queries. and app works fine.

if you followed all steps, now compare your app and compose file and see if you can find what differs in these basic steps.

EDIT: I forgot a step in running the app. since I don’t use an image creation step, you need to first run it with having command to be npm install then second time with npm start.

2 Likes

Hi again, regarding the OP_QUERY error, I have a resolution on it. I tried nodejs drivers 2.2.x, 3.0.0, 3.1.0 and above. this is related to versions before 3.1.0 and mongodb 5.1 and newer.

I have a bit longer answer about this here:

in case you are sure you tried to update to 4.13 and got this error, then be sure it did not. you need to rebuild the image but first remove its traces from your docker cache. docker records commands as layers, and if a command is not changed, here npm install, it may not be run again, and previous 2.3 driver layer might be in use.

Hi,

For some reason, if i have

 await client.connect()
 console.log("from: ",conn.s.url)
 db = client.db();

This works fine with a mongo db URL managed by mongo server which has conn URL mongo+srv/…
But same does not work when I run mongo inside docker container and try to connect
Error is

MongoServerSelectionError: connection timed out
main-api_1 | at Timeout._onTimeout (/srv/deployments/profile-service/node_modules/mongodb/src/sdam/topology.ts:582:30)
main-api_1 | at listOnTimeout (node:internal/timers:564:17)
main-api_1 | at processTimers (node:internal/timers:507:7) {
main-api_1 | reason: TopologyDescription {
main-api_1 | type: ‘Unknown’,
main-api_1 | servers: Map(1) { ‘mongo:27017’ => [ServerDescription] },
main-api_1 | stale: false,
main-api_1 | compatible: true,
main-api_1 | heartbeatFrequencyMS: 10000,
main-api_1 | localThresholdMS: 15,
main-api_1 | setName: null,
main-api_1 | maxElectionId: null,
main-api_1 | maxSetVersion: null,
main-api_1 | commonWireVersion: 0,
main-api_1 | logicalSessionTimeoutMinutes: null
main-api_1 | },
main-api_1 | code: undefined,
main-api_1 | [Symbol(errorLabels)]: Set(0) {}
main-api_1 | }

the mongo name in the connection mongo:27017 is the name of the service. I used mongo and nodeapp names in my example compose file.

you seem to change the app service’s name to main-api, so strongly possible you also changed the name of the database service. use that name in your connection string instead. (timeout occurs when a given name does not resolve to an address or the resolved address is not mapped to a running host. that is the difference from a refused connection)

If you later happen to completely separate services into two files, that may require extra work because docker may start them in separate networks. so heads up. (though it will be similar to connecting outside database if you choose this path)

I thought about editing my above response, but you might miss then if you already are around and read that.

The other possibility is just an honest mistake that happens to any of us: you forgot to start the database service. If your compose file still has both services in it, then this is the strongest possibility for the timeout. check the 4th step.