In this post, I will introduce how to setup MongoDB database server with docker.

The post includes following sections

  • Setup a simple MongoDB database server (easy)
  • Setup a secured MongoDB database server with a password
  • Secure a currently running MongoDB database server with a password
  • Periodically backup MongoDB database to AWS s3
  • Connect to MongoDB database server using mongoose for nodejs users

This post require docker to be installed in the setup machine. Refer to the official guide to install docker for ubuntu, mac, windows, or read my article on how to install docker in ubuntu.

I have packed the whole command to an automatic script in this public repository. If you want a production-ready tools, just install docker, download my script, setup environment variables, then execute mongodb/run.sh.

The repository also provides scripts to install mysql, phpmyadmin, wordpress, redis, .. and going to be updated more.


Part 1: setup a simple MongoDB server without password (easy)

Execute the following command

docker container stop mongo; docker run --name mongo -p 27017:27017 -v $HOME/mongo:/data/db --rm -d mongo:4.0.4

Command and flags' explanation

  • docker container stop mongo: stop the current instance if exist. mongo is an arbitrary name, defined in the next command
  • docker run: initiate a docker instance
  • ---name mongo: name the docker instance in order of the convenience for the stop command, or, finding the previously executed instance to restart it.
  • -p 27017:27017: open port 27017 for other applications can access the database
  • -v $HOME/mongo:/data/db: map the data stored in the database instance to a host directory, to prevent data lost in next runs. Here, I chose $HOME/mongo
  • --rm remove the instance if is stopped. This flag is used almost everytime when I use docker run
  • -d: detach the process to background. Even if you execute the command in a ssh session and quit the ssh session, the docker command will continuously runs in background. In a case you want to watch for console output of the docker command, replace -d by -it, after that, you can use ctrl-p ctrl-q to move the process to background mode. The full command in this case
docker container stop mongo; docker run --name mongo -p 27017:27017 -v $HOME/mongo:/data/db --rm -it mongo:4.0.4
#ctrl-p ctrl-q to detach
  • it flag combines -i and -t, which means allocate a terminal ( -t)in interactive mode ( -i)
  • mongo:4.0.4: official docker image by mongo. I highly recommend use a specific version for production purpose. My above mentioned command can be copied and used directly in production if you want. You can replace 4.0.4 with the newer or your favorite version. To get the latest version, use mongo:latest

Part 2: Setup a secured mongodb database server with password

Similar as command in part 1, with the additional of user name and password configuration

 docker container stop mongo; docker run -e MONGO_INITDB_ROOT_USERNAME=carstay -e MONGO_INITDB_ROOT_PASSWORD=carstay-password --name mongo -p 27017:27017 -v $HOME/mongo:/data/db --rm -d mongo:4.0.4 --auth

Note:

  • This command only affects if you initiate the server for first time. In other words, the directoy $HOME/mongo in the host machine is empty. If you already have a running mongo server I want to secure it with a password, please refer to part 3 in this post
  • This command includes user and password in plain text. It can be revealed by others who can login to the machine and access the bash history. There are two solutions: put the password and user name in a file, refer to a guide here, in this post I will not introduce about this. You can also prepend the command with a space, which explicitly ignore this command in bash history.
  • User created in this command has the highest database privilege.

Command explanation

  • -e MONGO_INITDB_ROOT_USERNAME=carstay -e MONGO_INITDB_ROOT_PASSWORD=carstay-password: setup user and password. Replace carstay and carstay-password with appropriate values
  • --auth: this flag can be dropped if `-e MONGO_INITDB_ROOT_USERNAME=carstay -e MONGO_INITDB_ROOT_PASSWORD=carstay-password is presented. However, I recommend explicitly set this --auth flag to ensure the secured mode

Part 3: secure a currently running mongodb server with a password

3.1 Setup password security

Assume that there is a mongodb server is running without authentication (password protected). For e.g. use command in part 1 to create an instance

Firstly, access to the database to setup password authentication

docker exec -it mongo sh -c 'exec mongo'
  • exec: execute a command in a running instance
  • -it: initiate a terminal which can interact with user (interactive mode + tty)
  • mongo: name of the instance specified in part 1
  • sh -c 'exec mongo': the command to be executed in the instance

After executing this command, you will get in to the mongo instance environment

There will be some warnings appear as you have not setup authentication for the database server. After going through this part, there should have zero warning.

Run following command in mongo environment

use admin
db.createUser({
  user: 'carstay',
  pwd: '6wryfFadhGB57xhlTY63Gook',
  roles: [{
    role: 'readWrite',
    db: 'carstay'
  }]
})

In the above snippet, the user name carstay with password 6wryfFadhGB57xhlTY63Gook had been created with readWrite role for a database named carstay. This configuration prevents the created user having too high privilege.

The roles parameter in the above command can be changed to assign appropriate role to the user. The list of roles can be referred from here. For example, root role is used to create a root user, and is used with roles: ['root].

3.2. Confirm the authentication configuration

Exit mongo environment with Ctrl-D, stop the database server and start it again with --auth flag appended

 docker container stop mongo; docker run --name mongo -p 27017:27017 -v $HOME/mongo:/data/db --rm -d mongo:4.0.4 --auth

Re-access the database to confirm the authentication configuration

docker exec -it mongo sh -c 'exec mongo'
There should be no warning now

In mongo environment, execute these commands

use carstay
show collections

If the authentication configuration has been setup correctly, there will be a warning Warning: unable to run listCollections, attempting to approximate collection names by parsing connectionStatus.

Continue with following command in mongo environment to try using authentication information setup in the previous step

use admin
db.auth('carstay', 'carstay-password')
use carstay
show collections

The list of collections will appear without any warning.

3.3. Update authentication information

It is not rarely that one wants to update setup authentication information (user name or password, update privilege, forget current password), for e.g. when the current password is exposed, periodically change password to improve security, ...

In these cases, you can remove the user and setup a new one with the updated information. To remove an user use

dropUser('carstay')

To execute this command, one of 2 following conditions must be satisified

  • Mongodb server starts in non-secured mode. i.e. without --auth or none of two flags -e MONGO_INITDB_ROOT_USERNAME and -e MONGO_INITDB_ROOT_PASSWORD. If the instance is started by docker, this can be achieved by stopping the current server and re-starting with appropriate flags.
  • The current running mongo environment is accessed with admin privilege (root user)

Part 4: periodically backup mongodb database to AWS s3

4.1 Backup

This section introduces the way to periodically backup a collection to AWS s3, send slack message if there is error occurs while the backup.

The idea behind this process is very simple. Uploading to AWS s3 can be replaced by pushing backed up data to a github repo, copy to another server, ...

In order to prevent too long post, I will not show how to setup AWS s3 account, slack incoming web hook. Readers should setup them yourself. Note that it is highly recommend to enable versioning in AWS s3 config to keep multiple version of a same file (similar to git).

According to size and importance of the databasee, the backup rate should be set appropriately. My personal advice for small and important database is once per 15 minutes. Here is the configuration

Run command crontab -e from terminal. Editor will appear, go to the end of the file and append following content

*/15 * * * * docker exec mongo sh -c 'exec mongodump -d <db-name> --archive=/mongodump.bson --authenticationDatabase admin --username <username> --password <password>' && docker cp mongo:/mongodump.bson $HOME/mongodump.bson && aws s3 cp $HOME/mongodump.bson s3://<bucket-name> ||  curl -X POST -H 'Content-type: application/json' --data '{"text":"Can not backup db"}' <slack-hook-url>

Replace variables <db-name>, <username>, <password>, <bucket-name>, <slack-hook-url> by correct values.
File mongodump.bson will be created in $HOME directory, then be uploaded to AWS s3.
If the database is not secured with authentication, you should remove the --authenticationDatabase admin --username <> --password <> flag.

4.2. Restore from backup files

Assume that you already have mongodump.bson file from previous backup at $HOME directory.

Copy this backup file to the docker instance docker cp $HOME/mongodump.bson mongo:/mongodump.bson.

Note: the following commands will erase everything in the target database. With any mistake, there will be a possible that any database can be erased completely.

I highly recommend backing up all current data before executing the restoration. With docker this can be accomplished trivially with sudo cp -Rf $HOME/mongo $HOME/mongo-bak.

4.2.1 If new database has same name as the old's

docker exec mongo sh -c 'exec mongorestore --drop --nsInclude "<db name>.*" --archive=/mongodump.bson'

Replace <db name> with database name.

4.2.2 If new database has different name with the old's

docker exec mongo sh -c 'exec mongorestore --drop --nsFrom "<source db>.*" --nsTo "<target db>.*" --archive=/mongodump.bson'

Where <source db> is the backed up database's name, <target db> is the name of the new database.

Another note: the --drop flag in above commands only helps removing collections from the new database whose names exist in the backed up database. In other words, if there is a collection named users in the new database while there is not any collection with a same name in the backed up database, this users collection will not be removed from the new database.


Part 5: Connect to mongo database server via mongoose for nodejs users

A note for mongoose users, beside user and pass options, auth: {authdb: 'admin'} is also required to avoid unexpected error.

import mongoose from 'mongoose'
import chalk from 'chalk'

// mongoose.set('useNewUrlParser', true)
(async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      autoReconnect: true,
      family: 4,
      user: process.env.MONGODB_USER,
      pass: process.env.MONGODB_PASS,
      auth: {
        authdb: 'admin'
      }
    })
  } catch (err) {
    logger.error(err)
    logger.log('%s MongoDB connection error. Please make sure MongoDB is running.', chalk.red('✗'))
    process.exit()
  }
})()

This post, I have introduced how to setup a mongo database server by docker with configuration which can be used in production. From setup, periodical backup, restoration, connect from nodejs. I also referred link to my repo which includes a script to simply many steps.

At the end, I re-paste some useful commands for latter reference.

  • To add new role to existing user
  • Stop current mongo instance: docker stop mongo
  • Stop other processes which are using mongo (e.g.: nodejs server ...). Otherwise, Topology was destroyed error will occur after running the next command
  • Re-start mongo server without authentication docker run --name mongo -v $HOME/mongo:/data/db --rm -it -d mongo
  • Open mongo shell: docker exec -it mongo sh -c "exec mongo"
  • Open admin database use admin
  • Add role to existing user: db.grantRolesToUser('carstay', [{role: 'readWrite', db: 'carstay-log'}])
  • Quit mongo shell and restart server with authentication: docker container stop mongo; docker wait mongo; docker run -e MONGO_INITDB_ROOT_USERNAME=carstay -e MONGO_INITDB_ROOT_PASSWORD=root_pass --name mongo -p 27017:27017 -v $HOME/mongo:/data/db --rm -it -d mongo --auth