Running libp2p in Docker containers

Hi, I have a bit of a question regarding setting up libp2p nodes inside Docker containers. The idea is that I wanted to prepare a Docker image with a simple application that would create a node, and running multiple Docker containers to have a multy node setup. Yet I’m struggling to get those nodes to connect to one another. I’ve started from the examples in the documentation that run multiple nodes on localhost, and I can get multiple “instances” of the example to connect to one another in local, but once I try Dockerizing it, using Docker compose to setup two containers running it, they won’t connect to each other.

The node I create like so:

const node = await createLibp2p({
  addresses: {
    listen: [listen_address]
  },
  transports: [new TCP(), new WebSockets()],
  streamMuxers: [new Mplex()],
  connectionEncryption: [new Noise()],
  dht: new KadDHT(),
  peerDiscovery: [
    new Bootstrap({
      interval: 60e3,
      list: bootstrapers
    }),
    new MulticastDNS({
      interval: 20e3
    })
  ],
  connectionManager: {
    autoDial: true
  },
  pubsub: new FloodSub(),
  relay: {
    enabled: true,
    hop: {
      enabled: true
    },
    advertise: {
      enabled: true,
    }
  }
})

And I create these handlers to have nodes talk to each other:

node.connectionManager.addEventListener('peer:connect', (evt) => {
    const connection = evt.detail
    console.log('Connection established to:', connection.remotePeer.toString())
  })

  node.addEventListener('peer:discovery', async(evt) => {
    const peer = evt.detail
    if (node.peerId.toString() == peer.id.toString()) {
      return
    }
    var peerId = node.peerStore.addressBook.get(peer.id)
    console.log('Discovered:', peer.id.toString())
    node.peerStore.addressBook.set(peer.id, peer.multiaddrs)
    node.dial(peer.id)
  })

Any advice would be highly appreciated.

Thank you

It might be that your docker network is not setup right, you should give your compose file. How are you expecting the peers to discover each other - Mdns, bootstrap, manually dialing or something else?

Also why are you manually dialing on peer discovery when you have auto dial on?

I’ve tried both Mdns and bootstrapping, neither worked. The docker compose file:

version: "3.7"
services:

  middleware1:
    build:
      context: ./middleware
      dockerfile: Dockerfile
    image: middleware:test
    networks:
      - envoymesh
    environment:
      - LISTEN_ADDRESS=/ip4/127.0.0.1/tcp/49662

  middleware2:
    build:
      context: ./middleware
      dockerfile: Dockerfile
    image: middleware:test
    networks:
      - envoymesh
    environment:
      - LISTEN_ADDRESS=/ip4/127.0.0.1/tcp/49662
      - NODE_BOOTSTRAPERS=["/dnsaddr/middleware1/tcp/49662"]

networks:
  envoymesh: {}

This is where I try bootstrapping manually.

Also, the manual dial was left by accident. My bad ^^

I have given your code a try and it works fine with mdns.

If you want bootstrap them together it is a bit more of a pain in docker - I think your NODE_BOOTSTRAPERS need peer IDs in the address (/p2p/...) which is currently getting generated during runtime so you will need to generate keys for it first so that each peer as a static peer ID. You may also want to set container_name: middleware1 to ensure container naming.

Also note that NODE_BOOTSTRAPERS is a string value not an array like you might be expecting.

It is also possible that the dnsaddrResolver cannot resolve docker names - the following code fails for me:

import { Multiaddr } from "@multiformats/multiaddr";
import { dnsaddrResolver } from "@multiformats/multiaddr/resolvers";

Multiaddr.resolvers.set("dnsaddr", dnsaddrResolver)

// process.env.NODE_BOOTSTRAPERS = "/dnsaddr/.../tcp/.../p2p/..."
const strs = process.env.NODE_BOOTSTRAPERS.split(",");
const mas = strs.map(str => new Multiaddr(str));
const rmas = await Promise.all(mas.map(ma => ma.resolve()));

(libp2p uses dnsaddrResolver under the hood.)

But you can create your own resolver to handle this properly or if you are lazy do something like this:

import { Multiaddr } from "@multiformats/multiaddr";
import dns from "dns/promises";

const strs = process.env.NODE_BOOTSTRAPERS.split(",");
const mas = strs.map(str => new Multiaddr(str));

const rmas = await Promise.all(mas.map(async ma => {
	const options = ma.toOptions();
	const lookup = await dns.lookup(options.host);

	return new Multiaddr(ma.toString().replace(options.host, lookup.address).replace("dnsaddr", "ip4"));
}));