Search code examples
node.jsmongodbdockerconnectionreplicaset

How to connect to Docker replica set with node.js mongodb 4+ driver where MongoDB is set up with Docker hostnames?


I successfully set up MongoDB replica set with docker-compose using Docker hostnames db1, db2, db3. However, these hostnames are of course not available outside of Docker unless I edit e.g. /etc/hosts.

The MongoDB servers use port 27017 inside the container but are exposed on ports 27017, 27027, 27037, respectively, and MongoDB servers are started with --bind_ip_all (I also tried --bind_ip localhost,db1,127.0.0.1 etc.).

Now I want to connect a non-Docker node.js application to MongoDB.

Connecting to my local replica set in Docker worked with the node.js mongodb@^3.7.3 driver but now fails with mongodb@^4, including 4.0.0.

I'm using this snippet adapted from the MongoDB driver documentation page, adding only the useUnifiedTopology flag to be able to connect:

const { MongoClient } = require("mongodb");

// Connection URI
const uri = "mongodb://user:pass@localhost/db";
// Create a new MongoClient
const client = new MongoClient(uri, { useUnifiedTopology: true });

async function run() {
    try {
        // Connect the client to the server
        await client.connect();
        // Establish and verify connection
        await client.db("admin").command({ ping: 1 });
        console.log("Connected successfully to server");
    } finally {
        // Ensures that the client will close when you finish/error
        await client.close();
    }
}

run().catch(console.dir);

useUnifiedTopology seems to have disappeared with mongodb driver version 4.

I tried different single server connection strings such as only with localhost as in the example (i.e. connecting to the primary only), 127.0.0.1, with and without port spec :27017 as well as replica set connection strings with localhost:27017,localhost:27027,localhost:27037 and appended ?replicaSet=rs0 and/or specified replicaSet: "rs0", readPreference: "primary" (or "nearest" etc.) in the connection options but all fail like this:

MongoServerSelectionError: getaddrinfo ENOTFOUND db1
    at Timeout._onTimeout (/dev/x/m/node_modules/mongodb/lib/sdam/topology.js:310:38)
    at listOnTimeout (internal/timers.js:557:17)
    at processTimers (internal/timers.js:500:7) {
  reason: TopologyDescription {
    type: 'ReplicaSetNoPrimary',
    servers: Map(3) {
      'db1:27017' => [ServerDescription],
      'db2:27017' => [ServerDescription],
      'db3:27017' => [ServerDescription]
    },
    stale: false,
    compatible: true,
    heartbeatFrequencyMS: 10000,
    localThresholdMS: 15,
    setName: 'rs0',
    maxSetVersion: 2,
    maxElectionId: ObjectId { [Symbol(id)]: [Buffer [Uint8Array]] },
    commonWireVersion: 9,
    logicalSessionTimeoutMinutes: undefined
  }
}

How can I connect to a Dockerized MongoDB replica set that is using Docker-internal hostnames?

Do I really need "public" hostnames that are valid both inside and outside of Docker for this to work?

Looking at the question Connect to MongoDB Replica Set running inside Docker with Java (Windows), this seems to be an unsupported case, however, it was working with the old mongodb 3 driver.

From that question:

In conclusion, to support key features of replica sets, we require that the hostnames used in a replica set config are reachable from the client.


Solution

  • It seems specifying directConnection: true in the connection options makes it work, preventing server delegation / redirection:

    // Connection URI
    const uri = "mongodb://user:pass@localhost/db";
    // Create a new MongoClient
    const client = new MongoClient(uri, { directConnection: true });