25

I want to create a Docker container with an instance of Mongo. In particular, I would like to create a replica set with only one node (since I'm interested in transactions and they are only available for replica sets).

Dockerfile

FROM mongo
RUN echo "rs.initiate();" > /docker-entrypoint-initdb.d/replica-init.js
CMD ["--replSet", "rs0"]

docker-compose.yml

version: "3"
services:
  db:
    build:
      dockerfile: Dockerfile
      context: .
    ports:
      - "27017:27017"

If I use the Dockerfile alone everything is fine, while if I use docker-compose it does not work: in fact if I then log to the container I got prompted as rs0:OTHER> instead of rs0:PRIMARY>.

I consulted these links but the solutions proposed are not working:

https://github.com/docker-library/mongo/issues/246#issuecomment-382072843 https://github.com/docker-library/mongo/issues/249#issuecomment-381786889

StackUser
  • 1,530
  • 3
  • 13
  • 17
  • Fun fact is that if I just use the Dockerfile with `docker build -t db .; docker run -p 27017:27017 -d db` everything is fine, but with docker-compose it does not work. – StackUser Apr 28 '20 at 19:23
  • Can update your post with the versions of `docker` and `docker-compose` that you're using along with details of the OS you're operating on? I have `docker-compose version 1.24.0, build 0aa59064` with `docker` client `20.10.5` / server `19.03.13` on Ubuntu 18.04 using your specs which doesn't seem to be an issue. – masseyb Dec 21 '21 at 11:29

7 Answers7

18

This is the compose file I have used for a while now for local development. You can remove the keyfile pieces if you don't need to connect via SSL.

version: "3.8"
services:
  mongodb:
    image : mongo:4
    container_name: mongodb
    hostname: mongodb
    restart: on-failure
    environment:
      - PUID=1000
      - PGID=1000
      - MONGO_INITDB_ROOT_USERNAME=mongo
      - MONGO_INITDB_ROOT_PASSWORD=mongo
      - MONGO_INITDB_DATABASE=my-service
      - MONGO_REPLICA_SET_NAME=rs0
    volumes:
      - mongodb4_data:/data/db
      - ./:/opt/keyfile/
    ports:
      - 27017:27017
    healthcheck:
      test: test $$(echo "rs.initiate().ok || rs.status().ok" | mongo -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq 1
      interval: 10s
      start_period: 30s
    command: "--bind_ip_all --keyFile /opt/keyfile/keyfile --replSet rs0"
volumes:
  mongodb4_data:

It uses Docker's health check (with a startup delay) to sneak in the rs.initiate() if it actually needs it after it's already running.

To create a keyfile.

Mac:

openssl rand -base64 741 > keyfile
chmod 600 keyfile

Linux:

openssl rand -base64 756 > keyfile
chmod 600 keyfile
sudo chown 999 keyfile
sudo chgrp 999 keyfile
Hermann Steidel
  • 1,000
  • 10
  • 18
  • 2
    I've tried this without the keyfile and `mongod` said it cannot start the replica set without the keyfile. After following your instructions it worked nicely. I was able to connect using `mongodb://username:password@localhost:27017/?authSource=admin&readPreference=primary&ssl=false&replicaSet=rs0` – luksfarris Mar 25 '22 at 12:06
  • Why is it 741 bytes for macOS and 756 for Linux? – damd Jun 05 '23 at 12:30
3

The top answer stopped working for me in later MongoDB and Docker versions. Particularly because rs.initiate().ok would throw an error if the replica set was already initiated, causing the whole command to fail. In addition, connecting from another container was failing because the replica set's sole member had some random host, which wouldn't allow the connection. Here's my new docker-compose.yml:

services:
  web:
    # ...
    environment:
      DATABASE_URL: mongodb://root:root@db/?authSource=admin&tls=false
  db:
    build:
      context: ./mongo/
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: root
    ports:
      - '27017:27017'
    volumes:
      - data:/data/db
    healthcheck:
      test: |
        test $$(mongosh --quiet -u root -p root --eval "try { rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'db' }] }).ok } catch (_) { rs.status().ok }") -eq 1
      interval: 10s
      start_period: 30s
volumes:
  data:

Inside ./mongo/, I have a custom Dockerfile that looks like:

FROM mongo:6
RUN echo "password" > /keyfile \
  && chmod 600 /keyfile \
  && chown 999 /keyfile \
  && chgrp 999 /keyfile
CMD ["--bind_ip_all", "--keyFile", "/keyfile", "--replSet", "rs0"]

This Dockerfile is suitable for development, but you'd definitely want a securely generated and persistent keyfile to be mounted in production (and therefore strike the entire RUN command).

TJ Mazeika
  • 942
  • 1
  • 9
  • 30
  • Tried this with mongo 6.0.3, but I'm receiving a loop of : "Authentication succeeded" / "replSetInitiate admin command received from client" / "Connection ended". Is there a problem ? And how can I set up automatically a db "dbName" ? Tried with MONGO_INITDB_DATABASE but seems to not working – MsieurKris Nov 28 '22 at 17:23
  • Precision : can't create manually with compass a new DB : "not primary". – MsieurKris Nov 28 '22 at 17:36
  • 3
    This only works for me when setting the name to `localhost` in the test command instead of `db` – firstdorsal Dec 11 '22 at 23:11
3

I couldn't get any of these examples to work, so after hours of googling I finally found something good enough for my local development which doesn't require auth. That may be a deal breaker for you but it does seem to work as a replicaset -- so transactions seem work as expected.

Anyway just replace the YOUR_DB_NAME_HERE with whatever you want your DB name to be. I create an init collection in this db on startup but you can remove that bit of code in the localdev-mongo service entrypoint if it doesn't serve your needs; just be sure not to remove the rs.initiate script / call as that's required to initialize the replicaset.

version: '3.7'
services:
  your-service:
    build:
      context: .
      dockerfile: Dockerfile
    links:
      - localdev-mongo
    container_name: your-service
    environment:
      MONGO_APP_DATABASE: ${YOUR_DB_NAME_HERE}
      MONGO_CONNECTION_STRING: mongodb://localdev-mongo:27017/?directConnection=true&tls=false
    command: ${your app run command}
    depends_on:
      - localdev-mongo
    restart: unless-stopped
  localdev-mongo:
    image: mongo:latest
    environment:
      MONGO_APP_DATABASE: ${YOUR_DB_NAME_HERE}
      MONGO_REPLICA_HOST: host.docker.internal
      MONGO_REPLICA_PORT: 27018
    container_name: localdev-mongo
    entrypoint: >
      /bin/bash -c '
      echo "rs.initiate()" > /docker-entrypoint-initdb.d/1-init-replicaset.js &&
      echo "db = db.getSiblingDB(process.env[$0]);" > /docker-entrypoint-initdb.d/2-init-db-collection.js &&
      echo "db.createCollection($1, { capped: false });" >> /docker-entrypoint-initdb.d/2-init-db-collection.js &&
      echo "db.init.insert([{ message: $2 }]);" >> /docker-entrypoint-initdb.d/2-init-db-collection.js &&
      /usr/local/bin/docker-entrypoint.sh mongod --replSet rs0 --bind_ip_all --noauth' "'MONGO_APP_DATABASE'" "'init'" "'db initialized successfully'"
    expose:
      - 27017
    ports:
      - "27017:27017"
    volumes:
      - mongodb-data:/data/db
    restart: unless-stopped

volumes:
  mongodb-data:
Matthew Madson
  • 1,643
  • 13
  • 24
  • 1
    Very nice. I have little to no understanding of what it's actually doing differently, but this is the only thing that I found to be working "out-of-the-box." – damd Jun 05 '23 at 12:56
0

You still need to issue replSetInitiate even if there's only one node in the RS.

See also here.

D. SM
  • 13,584
  • 3
  • 12
  • 21
  • I do it in the `RUN` part. – StackUser Apr 28 '20 at 19:13
  • In that case 1) ensure it's executed and 2) review the server logs for what is happening on the node. – D. SM Apr 28 '20 at 19:31
  • `Our replica set config is invalid or we are not a member of it` is the message I get if I log into the db container and use `rs.status()`. But I don't understand why it does not work only with docker-compose while with only Dockerfile it does. – StackUser Apr 28 '20 at 19:48
  • Can the node reach itself using the hostname used in the RS config? – D. SM Apr 28 '20 at 20:21
0

I had to do something similar to build tests around ChangeStreams which are only available when running mongo as a replica set. I don't remember where I pulled this from, so I can't explain it in detail but it does work for me. Here is my setup:

Dockerfile

FROM mongo:5.0.3
RUN echo "rs.initiate({'_id':'rs0', members: [{'_id':1, 'host':'127.0.0.1:27017'}]});" > "/docker-entrypoint-initdb.d/init_replicaset.js"
RUN echo "12345678" > "/tmp/key.file"
RUN chmod 600 /tmp/key.file
RUN chown 999:999 /tmp/key.file

CMD ["mongod", "--replSet", "rs0", "--bind_ip_all", "--keyFile", "/tmp/key.file"]

docker-compose.yml

version: '3.7'

services:
  mongo:
    build: .
    restart: always
    ports: 
      - 27017:27017
    healthcheck:
      test: test $$(echo "rs.initiate().ok || rs.status().ok" | mongo -u admin -p pass --quiet) -eq 1
      interval: 10s
      start_period: 30s
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: pass
      MONGO_INITDB_DATABASE: test

Run docker-compose up and you should be good.

Connection String: mongodb://admin:pass@localhost:27017/test

Note: You shouldn't use this in production obviously, adjust the key "12345678" in the Dockerfile if security is a concern.

88jayto
  • 659
  • 1
  • 5
  • 20
-1

If you just need single node replica set of MongoDB via docker-compose.yml you can simply use this:

mongodb:
  image: mongo:5
  restart: always
  command: ["--replSet", "rs0", "--bind_ip_all"]
  ports:
    - 27018:27017
  healthcheck:
    test: mongo --eval "rs.initiate()"
    start_period: 5s
Arman Ebrahimpour
  • 4,252
  • 1
  • 14
  • 46
-4

This one works fine for me:

version: '3.4'

services:
  ludustack-db:
      container_name: ludustack-db
      command: mongod --auth
      image: mongo:latest
      hostname: mongodb
      ports:
      - '27017:27017'
      env_file:
      - .env
      environment:
      - MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
      - MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
      - MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE}
      - MONGO_REPLICA_SET_NAME=${MONGO_REPLICA_SET_NAME}
      healthcheck:
        test: test $$(echo "rs.initiate().ok || rs.status().ok" | mongo -u $${MONGO_INITDB_ROOT_USERNAME} -p $${MONGO_INITDB_ROOT_PASSWORD} --quiet) -eq 1
        interval: 60s
        start_period: 60s
programad
  • 1,291
  • 3
  • 19
  • 38
  • 2
    I tried this code and it just created a standalone server, not a single-node replica set. I tested this by trying to run some commands in a transaction, and got this error: "Standalone servers do not support transactions". – John Knoop Feb 10 '21 at 15:15
  • I also get a standalone server without a replica set. – Elbbard Apr 27 '21 at 12:10