Secured MongoDB container

Leon Feng
3 min readJul 9, 2019

Recently I’m working on a project that has been containerized already. However, the database has not been integrated into this bundle yet. This time I will use the official mongodb image from docker hub and initialize a database, credentials for the app.

Goals:

  1. Start a mongodb container and initialize a mongodb database.
  2. Add a new user in the database with `readWrite` permission.
  3. Pass all credentials into the container securely.

Let’s check out the official mongodb image page first https://hub.docker.com/_/mongo. Fortunately, the image allows us to pass environment variables to initialize the root user of mongodb.

$ docker run -d --name some-mongo -e MONGO_INITDB_ROOT_USERNAME=mongoadmin -e MONGO_INITDB_ROOT_PASSWORD=secret mongo

This is not recommended. We should never pass sensitive information as plain text into the container. docker inspect <container id> command can show all environment variables you have defined in the run command.

Docker provides alternative way to safely pass sensitive information to containers.

As an alternative to passing sensitive information via environment variables, _FILE may be appended to the previously listed environment variables, causing the initialization script to load the values for those variables from files present in the container.

Let’s create a docker-compose.yml file.

version: "3.5"
services:
mongodb:
build: ./mongodb
ports:
- 27017:27017
environment:
MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/mongodb_root_password
MONGO_INITDB_ROOT_USERNAME_FILE: /run/secrets/mongodb_root_username
MONGO_INITDB_DATABASE: admin
MONGO_USERNAME_FILE: /run/secrets/mongodb_username
MONGO_PASSWORD_FILE: /run/secrets/mongodb_password
MONGO_DATABASE: parrot
secrets:
- mongodb_root_password
- mongodb_root_username
- mongodb_password
- mongodb_username
secrets:
mongodb_root_password:
file: /mongodb/.mongodb_root_password
mongodb_root_username:
file: /mongodb/.mongodb_root_username
mongodb_password:
file: /mongodb/.mongodb_password
mongodb_username:
file: /mongodb/.mongodb_username

Docker compose defines 4 secrets. You can either use docker secret create ... command or create 4 files in the same dictionary with the docker-compose file. Contents of 4 files are confidential strings.

.mongodb_root_password only includes password for root user of mongodb.

password_for_root

You can tell the contents of rest of files by their file names. Each file only has one line of string.

These files will be mounted into the container via docker TLS when we start the service. They can be found under /run/secrets/ in the running container. *_FILE variables under the environment have paths to those files. More details are here: https://docs.docker.com/engine/swarm/secrets/.

MONGO_INITDB_DATABASE allows you to specify the name of a database to be used for creation scripts in /docker-entrypoint-initdb.d/*.js

MONGO_DATABASE is the name of the database for the app. In this case it’s parrot.

Official mongodb image will only create a root user. We need to create a database parrot and a user who can only perform read and write operations on it.

To organize the files, we create a dictionary mongodb in the root dictionary and copy secret files into it.

Now create a script to initialize the mongodb for the app

./mongodb/mongo-init.sh

#!/bin/bash
file_env() {
local var="$1"
local fileVar="${var}_FILE"
local def="${2:-}"
if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then
echo >&2 "error: both $var and $fileVar are set (but are exclusive)"
exit 1
fi
local val="$def"
if [ "${!var:-}" ]; then
val="${!var}"
elif [ "${!fileVar:-}" ]; then
val="$(< "${!fileVar}")"
fi
export "$var"="$val"
unset "$fileVar"
}
file_env "MONGO_USERNAME"
file_env "MONGO_PASSWORD"
mongo -- ${MONGO_INITDB_DATABASE} <<EOF
const MONGO_INITDB_ROOT_USERNAME = '$MONGO_INITDB_ROOT_USERNAME';
const MONGO_INITDB_ROOT_PASSWORD = '$MONGO_INITDB_ROOT_PASSWORD';
const MONGO_DATABASE = '$MONGO_DATABASE';
const MONGO_USERNAME = '$MONGO_USERNAME';
const MONGO_PASSWORD = '$MONGO_PASSWORD';
db.auth(MONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD);
var db = db.getSiblingDB(MONGO_DATABASE)
db.createUser({user: MONGO_USERNAME, pwd: MONGO_PASSWORD, roles: [{role: 'readWrite', db: MONGO_DATABASE}]});
EOF

Let’s go through the script.

file_env will get value of "MONGO_USERNAME" from the secret file and export it into the environment of the container.

mongo command will execute the script inside the EOF block. The script helps log in as the root user, create parrot database, then create a user.

Last step, create the Dockerfile to pull the image and copy the mongo-init.sh to the container.

FROM mongo:latestADD ./mongo-init.sh /docker-entrypoint-initdb.d/

pull the latest mongodb image and copy the initialization script into the container. When the mongodb container is up, app have the access to the database.

MONGODB_URI=mongodb://<username>:<password>@mongodb:27017/parrot

mongodb before the port is the service name we defined in the docker-compose file.

docker-compose up will start a mongodb container which maps its 27017 to 27017 of the host.

--

--