Skip to content

How to run a Polygon (Matic) Mainnet Node with Docker

Running a Matic Node seems to consist of two four parts: The the PoS node, a REST API server, a RabbitMQ server, and an EVM node. The PoS node is called heimdall as is the REST API server, and the EVM Node is called bor. According to the documentation we need to run heimdall first and let it sync…so let’s start there.

Also, a word of warning, I’m gonna assume you’re behind some kinda firewall here, don’t expose your node’s RPC/API to the world, duh. I’ll call out what the ports are for below.

# Make a directory to work in
mkdir -p /data/github
cd /data/github

# Clone the repo
git clone
cd heimdall

# Checkout the mainnet release version
git checkout v0.2.1-mainnet

# Copy the Dockerfile up a directory
cp docker/Dockerfile .

# Build the docker image
docker build --tag heimdall:latest .

# Make a directory for heimdall data
mkdir -p /data/volumes/heimdall

# Init configs and stuff
docker run -it -v /data/volumes/heimdall:/root/.heimdalld heimdall:latest heimdalld init

# Overwrite the genesis.json file with the mainnet launch genesis.json file
wget -O /data/volumes/heimdall/config/genesis.json

You might say to yourself “I don’t want to checkout the mainnet release version, I want to be on the latest and greatest version!” but you’d be wrong because it doesn’t work…at all…at the time of writing. :shrug:

Now you need to modify some config files. There are 2 config files of importance here.

config.toml and heimdall-config.toml they’re both located at /data/volumes/heimdall/config/

The key parts to modify in config.toml are:

  • cors_allowed_origins
    • Set it to ["*"]
    • cors_allowed_origins = ["*"]
  • seeds
    • Set it to the list of seeds found HERE
    • seeds = "f4f605d60b8ffaaf15240564e58a81103510631c@,4fb1bc820088764a564d4f66bba1963d47d82329@"

The key parts to modify in the heimdall-config.toml file are:

  • amqp_url
    • Point this to RabbitMQ server (we’ll run a docker called rabbitmq) so set it to:
      amqp_url = "amqp://guest:guest@rabbitmq:5672"

There’s also two other variables that I suggest editing LATER, after you’re up and running and in sync:

  • eth_rpc_url
    • This should point to an ETH1 endpoint, local or remote like Infura
    • I don’t think this does anything if you’re not validating though
  • bor_rpc_url
    • You can point this to your bor node after your bor node is in sync
    • I don’t think this does anything if you’re not validating though

You can pretty much ignore everything else, we’ll set some other stuff via command-line options. And obviously you haven’t created your bor node yet so you can come back to it later.


A quick word on sync time…it’s not fast. If you want to validate the entire chain (PoS (heimdall) and EVM (bor)) more power to you. If however, you just want to get up and running as fast as possible, the community offers database snapshot over on their forums.

If you want to use a snapshot, now is the time to do that for heimdall. And while you’re waiting for heimdall to catch up, you can install the bor snapshot after you initialize it.

Running Heimdall

# Run it / Make sure it's working
docker run -it -p "26656:26656" -p "26657:26657" -v /data/volumes/heimdall:/root/.heimdalld heimdall:latest heimdalld start

Even without having set proper eth_rpc_url or bor_rpc_url variables (or running the RabbitMQ server yet) it should start to sync the PoS network. However, you do need the other 2 service (REST API and RabbitMQ) for bor to work, so let’s set those up inside a docker-compose file.

Docker Compose

version: '3.4'

    container_name: rabbitmq
    image: rabbitmq:3-alpine
      - "5672:5672" # RabbitMQ
    restart: unless-stopped
    container_name: heimdalld
    image: heimdall:latest
    build: /data/github/heimdall
    restart: unless-stopped
      - /data/volumes/heimdall:/root/.heimdalld
      - "26656:26656" # P2P (TCP)
      - "26657:26657" # RPC (TCP)
      - rabbitmq
      - heimdalld
      - start
      - --moniker=MyNodeName
      - --p2p.laddr=tcp://
      - --rpc.laddr=tcp://

    container_name: heimdallr
    image: heimdall:latest
    build: /data/github/heimdall
    restart: unless-stopped
      - /data/volumes/heimdall:/root/.heimdalld
      - "1317:1317" # Heimdall REST API
      - heimdalld
      - heimdalld
      - rest-server
      - --chain-id=137
      - --laddr=tcp://
      - --node=tcp://heimdalld:26657

Now if you run docker-compose up -d your docker container will start in the background. It’ll pull RabbitMQ and run that, then it will launch heimdalld the PoS node, and heimdallr the REST API server. Both heimdalld and heimdallr will read from the same config files so they’re mounted to the same location. As you can see from the config heimdallr points to heimdalld with the --node flag.

Wait until you’re in sync

How do you know if you’re in sync?

$ curl http://localhost:26657/status
  "jsonrpc": "2.0",
  "id": "",
  "result": {
    "node_info": {
      "protocol_version": {
        "p2p": "7",
        "block": "10",
        "app": "0"
      "id": "d09d79f111a56284495dec4762056f801bdafd17",
      "listen_addr": "tcp://",
      "network": "heimdall-137",
      "version": "0.32.7",
      "channels": "4020212223303800",
      "moniker": "MysticRyuujin",
      "other": {
        "tx_index": "on",
        "rpc_address": "tcp://"
    "sync_info": {
      "latest_block_hash": "D081C8AEA93F78526E6088379819548D4ED780B4D2D24E405D744CB368817510",
      "latest_app_hash": "F43512329EC6E77C9FABC8D38FFD75747C4C787631829676AB55DB787399570D",
      "latest_block_height": "342083",
      "latest_block_time": "2020-06-21T09:43:49.695593592Z",
      "catching_up": true
    "validator_info": {
      "address": "5FC7043EECEA93C19B55FD33760E45CE246DCB93",
      "pub_key": {
        "type": "tendermint/PubKeySecp256k1",
        "value": "BA5qrjvWfVGHa8TWH47gMIq3O0dlIl+VWENts7kN7fZcP06A7y6Lf/yXuFxdcQ6sfEX8eFsv6MmttQ2eG9XA3qk="
      "voting_power": "0"

Looking at the above information we can see that our node has this info:

      "latest_block_time": "2020-06-21T09:43:49.695593592Z",
      "catching_up": true

When that says false and the timestamp makes sense, you’re in sync

bor? Boring

Bor is basically just a Geth clone, the setup is similar

# Re-use our github folder
cd /data/github

# Clone the repo
git clone
cd bor

# This is the current recommended version
# git checkout v0.2.6

# Build the docker image
docker build --tag bor:latest .

# Make a directory for bor data
mkdir -p /data/volumes/bor

# We need to download the genesis.json file FIRST this time
wget -O /data/volumes/bor/genesis.json

# Initialize bor with genesis file
docker run -it -v /data/volumes/bor:/datadir bor:latest bor --datadir /datadir init /datadir/genesis.json

Now would be a good time to install the bor snapshot if you’re so inclined.

Now it’s a matter of running bor node the same way you would any other Geth node, with a couple of extras.

  • They provide you with a list of bootnodes HERE
  • You need to provide the --bor.heimdall flag and point it to the REST API server
    • --bor.heimdall=http://heimdallr:1317
  • You need to specifically include --networkid=137
  • Turn on the bor API under --http.api and --ws.api if you use websocket
  • Set gas targets and limits (idk why but their documentation does this everywhere)

Here’s what that all looks like in a Docker-Compose. Remember: HEIMDALLD NEEDS TO BE IN SYNC BEFORE YOU RUN BOR.

Docker Compose

version: '3.4'

    container_name: bor
    image: bor:latest
    build: /data/github/bor
        - /data/volumes/bor:/datadir
        - "8545:8545" # RPC
        - "30303:30303" # Peers (TCP)
        - "30303:30303/udp" # Peers (UDP)
        - bor
        - --syncmode=full
        - --datadir=/datadir
        - --networkid=137
        - --bor.heimdall=http://heimdallr:1317
        - --miner.gaslimit=200000000
        - --miner.gastarget=20000000
        - --http
        - --http.addr=
        - --http.port=8545
        - --http.api=eth,net,web3,admin,debug,bor
        - --http.corsdomain=*
        - --http.vhosts=*
        - --ws
        - --ws.addr=
        - --ws.port=8545
        - --ws.api=eth,net,web3,admin,debug,bor
        - --ipcdisable
        - --nousb
        - --bootnodes=enode://0cb82b395094ee4a2915e9714894627de9ed8498fb881cec6db7c65e8b9a5bd7f2f25cc84e71e89d0947e51c76e85d0847de848c7782b13c0255247a6758178c@,enode://88116f4295f5a31538ae409e4d44ad40d22e44ee9342869e7d68bdec55b0f83c1530355ce8b41fbec0928a7d75a5745d528450d30aec92066ab6ba1ee351d710@

You can customize this to support whatever security you need or don’t need. Just remember to take care when exposing RPC and API ports.

Published inTech


  1. lcgogo lcgogo

    i met followed error when run heimdalld

    E[2021-05-26|11:19:51.371] Error dialing seed module=p2p err=”incompatible: Peer is on a different network. Got heimdall-137, expected heimdall-yMfbLt” seed=f4f605d60b8ffaaf15240564e58a81103510631c@

    • lcgogo lcgogo

      This is caused bynot replace the correct genesis and toml after bor init.

  2. LiChao LiChao

    Hi Chase,

    you metioned that,
    But in you bor docker-compose file, you don’t write syncmode full, but in bor –help

    –syncmode value Blockchain sync mode (“fast”, “full”, “snap” or “light”) (default: fast)

    Li Chao

  3. Nicola Nicola

    Hi, get error:

    root@Ubuntu-2004-focal-64-minimal ~ # docker run -it -v /data/volumes/heimdall:/root/.heimdalld heimdall:latest heimdalld init
    docker: Error response from daemon: OCI runtime create failed: invalid mount {Destination:logs Type:bind Source:/var/lib/docker/volumes/1096744ba8775701bb4706919ed3cf4f5a34ac6b894c6a82e92cc4b2c340c4a6/_data Options:[rbind]}: mount destination logs not absolute: unknown.
    ERRO[0000] error waiting for container: context canceled
    root@Ubuntu-2004-focal-64-minimal ~ #

    • Does the folder you’re trying to mount exist? Try `mkdir -p /data/volumes/heimdall`

      • Nicola Nicola

        Yes sure, the directory exist following your instructions.

        What distro did you use? I can’t fix this error.

        By the way have you understand what is a difference between a validator with or without sentry node?

    • jack jack

      just change the docker version,use old version is ok.

  4. jack jack

    if i want to enable the bor ipc ,what should i do?

  5. Vali Vali

    Anything to do to speedup the heimdall sync? It is running for 2 days now and still “catching up”.?

    • Not that I know of. I assume you did the snapshots? Nothing to do but let it run. The Polygon chain has block times of like 1 second and millions of tx per day. It is what it is.

  6. Aris Aris

    thanks for sharing! I have two questions:
    1) Do we run all the scripts in root? (That’s what I did, just to confirm)
    2) In the “Running Heimdall” section is the /data/volumes/heimdalld correct? or is this supposed to be /data/volumes/heimdall without the d?


    • Aris Aris

      Hello again,

      I managed to run both nodes, however bor seems to be out of sync. I made sure that heimdall was in full sync before running bor. I suspect that bor is in sync up to the snapshot. Any idea on why bor may stop syncing after snapshot?

Leave a Reply

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