How To Set Up Flask with MongoDB and Docker

 

Introduction

Web application development can be complex and time-consuming, especially when building and maintaining a multitude of different technologies. Therefore, it is recommended to consider lighter weight options that are designed to reduce time-to-production and complexity for your application, the adoption of which will result in a more scalable  and flexible solution. Flask  is a micro web framework based on Python, and it provides developers with a great option to grow their applications via extensions, which can be integrated into projects. MongoDB is a NoSQL database that can be used to continue the scalability of a developer’s tech stack. It is designed to work with frequent changes and scale. Also, Docker can be used as a tool for simplifying the process of deploying  and packaging their applications.

Moreover,  the development environment has been further simplified by Docker Compose, which allows developers to define the infrastructure in a single file, with network volumes, application services, and bind mounts. With Docker Compose running multiple docker container run commands is not needed anymore. By using this tool, developers are now able to run a single command, which will start aswell as create all the services from the configuration. They can also define all the services in a single Compose file, which ensures version control throughout the container infrastructure. Moreover, using a project name to isolate environments from each other, Docker Compose allows running multiple environments on a single host.

Using this guide, you will find out how to build, run, and package your to-do web application with MongoDB, Nginx, and Flask inside of Docker containers. You will also learn how to define the entire stack configuration in a file named docker-compose.yml, with configuration files for MongoDB, Python, and Nginx. You will also use Gunicorn, a Python WSGI HTTP Server, to serve the application since Flask needs a web server that would serve HTTP requests.

Step One — How to Write the Stack Configuration in Docker Compose


If you build the applications on Docker, you receive the ability to version infrastructure easily depending on changes in the configuration in Docker Compose. You can define the infrastructure in a single file and build it with a single command. Here, you will see how to set up the file docker-compose.yml to run your Flask application.

With the file docker-compose.yml, you can define the infrastructure of your application as individual services. Each of the services can have a volume attached to it for persistent storage; also, the services can be connected to each other. Volumes are kept in that part of the host filesystem, managed by Docker (on Linux, it is /var/lib/docker/volumes/).

First of all, in the home directory on your server, create a directory for the application:

$ mkdir flaskapp

Then move into the directory you've just created:

$ cd flaskapp

After that, create the file docker-compose.yml:

$ nano docker-compose.yml

The file docker-compose.yml starts with a version number, identifying the version of Docker Compose file. Docker Compose file version 3 targets a prerequisite for this setup, Docker Engine version 1.13.0+. Also, the tag services, which will be defined in the next step, will be added:

docker-compose.yml
version: '3'
services:

Now, flask will be defined as the first service in your file docker-compose.yml. In order to define the Flask service, add the following code:

The property environment contains the variables that are passed to the container. It is important to provide a strong password for the environment variable MONGODB_PASSWORD. The property volumes defines the volumes used by the service. In our example, the appdata volume is mounted inside the container at the directory /var/www. The property depends_on defines a service on which depends the proper functioning of Flask. Here, the service flask will depend on mongodb because the service mongodb acts as the database for your application. The property depends_on makes sure that the flask service runs only if the mongodb service is running.

The property build defines the context of the build, which, in our case, is the app folder that is going to contain the Dockerfile.

The property container_name is used to define a name for each container. The property image specifies the name of the image and how the Docker image is going to be tagged. The property restart defines how to restart the container — in this case it is unless-stopped. It means that your containers will be stopped only when the Docker Engine stops or restarts or when you stop the containers explicitly. The benefit of the property unless-stopped is that the containers will start automatically as soon as the Docker Engine restarts or if any error occurs.

The property environment contains the environment variables, which are passed to the container. A  secure password must be provided for the environment variable MONGODB_PASSWORD. The property volumes defines the volumes used by the service. In our case, the appdata volume is mounted inside the container at the directory /var/www. The property depends_on defines a service on which the proper functioning of Flask depends. In our case, the service flask will depend on mongodb because the service mongodb acts as the database for your application. The property depends_on ensures that the service flask runs only if the mongodb service is running.

The property networks specifies backend and frontend as the networks which the flask service will be able to access.

Now, having defined the service flask, it is time to add the MongoDB configuration to the file. Here, the official version 4.0.8 of mongo image will be used. Add the code below to the file docker-compose.yml after the service flask:

docker-compose.yml
. . .
  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

mongodb stands as a container_name for this service, with unless-stopped restart policy. The command property is used to define the command that will get executed when the container starts. The mongod --auth command  will disable the possibility to log into the MongoDB shell without credentials. This way, you will secure MongoDB with the need to authenticate.

The environment variables MONGO_INITDB_ROOT_PASSWORD and MONGO_INITDB_ROOT_USERNAME  create a root user with the given credentials; therefore, create a strong password to replace the placeholder.

By default, MongoDB is set to store its data in /data/db; for this reason, the data in the folder /data/db will be written to the named volume mongodbdata. This will save your databases in case of a restart. The service mongoDB does not expose any ports, to make it accessible through the network backend.

The next step is to define the web server for your application. So, in order to configure Nginx, add the code below to the file docker-compose.yml:

docker-compose.yml
. . .
  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "false"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

 The context of the build was defined. Here, it is the folder nginx, which contains the Dockerfile. With the property image, the image used to run and tag the container is specified. The property ports make the Nginx service publicly accessible through ports :80 and :443. The property volumes mounts the volume nginxdata inside the container at the directory /var/log/nginx.

flask defines as the service for the web server service to depend on. And lastly, the property networks defines that the networks web server service will have access to the frontend.

The next step is to create bridge networks to ensure communication between the containers. Append the lines below to the end of your file:

docker-compose.yml
. . .
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

The two networks for the services to connect to have been defined above: backend and frontend. The front-end services, like Nginx, are set to connect to the network frontend because it has to be publicly accessible. At the same time, back-end services, like MongoDB, are set to connect to the network backend to prevent unauthorized access to the service.

The next thing to do is to use volumes to persist the configuration, database, and application files. Your application will use the files and databases; therefore, it is imperative to persist the changes made to them. Docker manages the volumes and stores them on the filesystem. Add the code below to the file docker-compose.yml to configure the volumes. Copy the code below:

docker-compose.yml
. . .
volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

The section volumes declares the volumes that will be used by the application to persist data. Such volumes as nginxdata, mongodbdata, and appdata have been defined above for persisting your Nginx web server logs, MongoDB databases and, Flask application data, respectively. For the local storage of the data local driver is used. This data is persisted with the use of volumes.

The complete file docker-compose.yml should look like the following:

docker-compose.yml
version: '3'
services:

  flask:
    build:
      context: app
      dockerfile: Dockerfile
    container_name: flask
    image: digitalocean.com/flask-python:3.6
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_DEBUG: "False"
      APP_PORT: 5000
      MONGODB_DATABASE: flaskdb
      MONGODB_USERNAME: flaskuser
      MONGODB_PASSWORD: your_mongodb_password
      MONGODB_HOSTNAME: mongodb
    volumes:
      - appdata:/var/www
    depends_on:
      - mongodb
    networks:
      - frontend
      - backend

  mongodb:
    image: mongo:4.0.8
    container_name: mongodb
    restart: unless-stopped
    command: mongod --auth
    environment:
      MONGO_INITDB_ROOT_USERNAME: mongodbuser
      MONGO_INITDB_ROOT_PASSWORD: your_mongodb_root_password
      MONGO_INITDB_DATABASE: flaskdb
      MONGODB_DATA_DIR: /data/db
      MONDODB_LOG_DIR: /dev/null
    volumes:
      - mongodbdata:/data/db
    networks:
      - backend

  webserver:
    build:
      context: nginx
      dockerfile: Dockerfile
    image: digitalocean.com/webserver:latest
    container_name: webserver
    restart: unless-stopped
    environment:
      APP_ENV: "prod"
      APP_NAME: "webserver"
      APP_DEBUG: "true"
      SERVICE_NAME: "webserver"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - nginxdata:/var/log/nginx
    depends_on:
      - flask
    networks:
      - frontend

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

volumes:
  mongodbdata:
    driver: local
  appdata:
    driver: local
  nginxdata:
    driver: local

Verify your configuration, save the file and exit the editor.

The file docker-compose.yml contains the definitions for Docker configuration for your application stack. The next step is to write the Dockerfiles for Flask and the web server.

Step Two — How to Write the Flask and Web Server Dockerfiles


When using Docker, containers can be built to run your applications from a Dockerfile file. It is a tool that enables one to create custom images, which can be used to configure your containers according to your requirements and install the software required by your application. The custom images can be pushed to Docker Hub or any private registry.

Now, we will show you how to write the Dockerfiles for the web server and Flask services. First of all, create the app directory for your Flask application. See below:

$ mkdir app

After that, in the app directory, create the Dockerfile for your Flask app:

$ nano app/Dockerfile

Customize your Flask container by adding the code to the file:

app/Dockerfile
FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

You are creating an image in the Dockerfile. It is created on top of the 3.6.8-alpine3.9 image , which is based on Alpine 3.9 with pre-installed Python 3.6.8.

The directive ENV defines the environment variables for our user and group ID.
LSB (Linux Standard Base) specifies that GIDs and UIDs 0-99 are allocated by the system statically. UIDs 100-999 are to be allocated dynamically for groups and system users. UIDs 1000-59999 are to be dynamically allocated for user accounts. Remember this to safely assign a GID and UID of 1000; moreover, you can change the GID/UID by updating the USER_ID and GROUP_ID to match your requirements.

The directive WORKDIR defines the working directory for the container. Do not forget to replace the field LABEL MAINTAINER with your email address and name.

To install the necessary dependencies and copy the Flask application into the container, add the code block provided below:

app/Dockerfile
. . .
ADD ./requirements.txt /var/www/requirements.txt
RUN pip install -r requirements.txt
ADD . /var/www/
RUN pip install gunicorn

The code below will use the directive ADD to copy files from the local directory app to the directory /var/www on the container. After that, the Dockerfile will use the directive RUN to install Gunicorn, as well as the packages specified in the file requirements.txt, which will be created later in this tutorial.

The following code block initializes the application and adds a new user and group:

app/Dockerfile
. . .
RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

Docker containers, by default, run as the root user. The root user has unrestricted access to everything in the system; therefore, ensuring the security of this data is particularly important since the implications of a security breach can be fatal. To mitigate this security risk, a new user and group will be created, with access only to the directory /var/www.

First of all, this code will use the command addgroup to create a new group named www. With the -g  flag, the group ID will be set to the variable ENV GROUP_ID=1000 , which was defined in the Dockerfile earlier.

The lines adduser -D -u $USER_ID -G www www -s /bin/sh create a www user. Its  user ID is 1000, which is defined by the variable ENV. The -s flag creates the home directory for the user, if it does not exist yet. The default login shell is changed to /bin/sh. With -G flag, the initial login group is changed to www, which was created by the command we issued previously.

The command USER defines that the programs that are run in the container will use the www user. The EXPOSE command will open the port :5000 since Gunicorn will listen on this port.

Lastly, the line CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"] runs the command, thus starting  the Gunicorn server with four workers that listen on port 5000. Generally, the number should be between 2–4 workers per core in the server. The recommendation documentation of Gunicorn recommends starting with (2 x $num_cores) + 1 as the number of workers.

The final Dockerfile will look as follows:

app/Dockerfile
FROM python:3.6.8-alpine3.9

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

ENV GROUP_ID=1000 \
    USER_ID=1000

WORKDIR /var/www/

ADD . /var/www/
RUN pip install -r requirements.txt
RUN pip install gunicorn

RUN addgroup -g $GROUP_ID www
RUN adduser -D -u $USER_ID -G www www -s /bin/sh

USER www

EXPOSE 5000

CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"]

Now you can save the file and exit the text editor.

After that, a new directory that will hold your Nginx configuration must be created:

$ mkdir nginx

In the nginx directory create the Dockerfile for your Nginx web server:

$ nano nginx/Dockerfile

In order to create the Dockerfile that will build the image for your Nginx container, add the code below to the file:

nginx/Dockerfile
FROM digitalocean.com/alpine:latest

LABEL MAINTAINER="FirstName LastName <example@domain.com>"

RUN apk --update add nginx && \
    ln -sf /dev/stdout /var/log/nginx/access.log && \
    ln -sf /dev/stderr /var/log/nginx/error.log && \
    mkdir /etc/nginx/sites-enabled/ && \
    mkdir -p /run/nginx && \
    rm -rf /etc/nginx/conf.d/default.conf && \
    rm -rf /var/cache/apk/*

COPY conf.d/app.conf /etc/nginx/conf.d/app.conf

EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]

An alpine base image is used by this Nginx Dockerfile. This image is a small Linux distribution with a minimal attack surface, ensuring greater security.

In the directive RUN, nginx is being installed. Also, you install symbolic links to publish the access and error logs to the standard error (/dev/stderr) and output (/dev/stdout). It is the best practice to publish errors to standard error and output because containers are ephemeral. The logs get shipped to docker logs, and from docker logs they can be forwarded to a logging service for persistence (for example, to the Elastic stack). After this, the default.conf and /var/cache/apk/* should be removed by the commands to reduce the size of the resulting image. Its size is also reduced by executing all of these commands in a single RUN, which reduces the layers in the image.

The directive COPY copies the web server configuration app.conf  inside of the container. The directive EXPOSE serves to ensure that the containers listen on ports :80 and :443 because your application will use :443 as the secure port and run on :80.

Lastly, the directive CMD defines the command that will start the Nginx server.

When finished, save the file and exit the text editor.

With the Dockerfile ready, it is time to configure the Nginx reverse proxy that will route traffic to the Flask application.

Step Three — How to Configure the Nginx Reverse Proxy


You are going to learn how to configure Nginx as a reverse proxy in order to forward requests to Gunicorn on :5000. Client requests are forwarded to  the appropriate back-end server by a reverse proxy. A back-end server provides additional control and abstraction to ensure the smooth flow of network traffic between servers and clients.

Create the nginx/conf.d directory to get started:

$ mkdir nginx/conf.d

Then create the file app.conf with the following configuration in the folder nginx/conf.d/ in order to configure Nginx. The file app.conf contains the configuration needed by the reverse proxy to forward the requests to Gunicorn.

$ nano nginx/conf.d/app.conf

Insert the below contents into the file app.conf:

nginx/conf.d/app.conf
upstream app_server {
    server flask:5000;
}

server {
    listen 80;
    server_name _;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    client_max_body_size 64M;

    location / {
        try_files $uri @proxy_to_app;
    }

    location @proxy_to_app {
        gzip_static on;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_buffering off;
        proxy_redirect off;
        proxy_pass http://app_server;
    }
}

First of all, the upstream server will be defined. It is commonly used to specify an app or web server for load balancing or routing.

The server address with the server directive, identified by the container name flask:5000, is defined by app_server - your upstream server.

 The server block contains the configuration for the Nginx web server. The port number used by your server to listen for incoming requests is defined by listen option. The directives access_log and error_log define the files for writing logs. The directive proxy_pass sets the upstream server to forward the requests to http://app_server.

When finished, save and close the file.

Having configured the Nginx web server, proceed to create the Flask to-do API.

Step Four — How to Create the Flask To-do API


WIth the environment built, it is time to build your application. You are going to learn how to write a to-do API application, which will display and save to-do notes from a POST request.

Create the file requirements.txt in the app directory:

$ nano app/requirements.txt

The dependencies for your application are kept in this file. In this tutorial, we will use Flask-PyMongo, Flask, and requests. Insert the following into the file requirements.txt:

app/requirements.txt
Flask==1.0.2
Flask-PyMongo==2.2.0
requests==2.20.1

When finished, save the file and exit the editor.

After that, in the app directory, create the file app.py for it to contain the Flask application code:

$ nano app/app.py

In the app.py file you've just created, import the dependencies by entering the following code:

app/app.py
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

The environment variables are imported by the os package. The Flask, jsonify, and request objects are imported from the flask library to instantiate the application, send JSON responses, and handle requests, respectively. The PyMongo object to interact with the Mongo was imported from DBFrom flask_pymongo.

You need a code to connect to MongoDB. Add the following code:

app/app.py
. . .
application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

The application object is loaded into the application variable by the Flask(__name__). Then, os.environ is used to build the connection string of MongoDB from the environment variables. Passing the object application in to the method PyMongo() will result in you the mongo object. The latter returns the object db from mongo.db.

After that, create an index message by adding the code below:

app/app.py
. . .
@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

The / GET route of your API is defined by @application.route('/'). By using the jsonify method,  your index() function returns a JSON string.

After that, list all to-do’s by adding the /todo route:

app/app.py
. . .
@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

The /todo GET route of your API is defined by the @application.route('/todo'). The /todo GET route returns the to-dos in the database. The method db.todo.find() returns all the to-dos in the database. After that, there is the need to iterate over the _todos in order to build an item, including only the todo and id from the objects. They will be appended to an array data and returned as JSON.

Create the to-do by adding the following code:

app/app.py
. . .
@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

The /todo POST route of your API is defined by the @application.route('/todo'). The POST route creates a to-do note in the database. Then, the request.get_json(force=True) gets the JSON that is posted to the route. At the same time, item is used to build the JSON, which will be saved in the to-do. A single item is inserted into the database with databasedb.todo.insert_one(item). As soon as the to-do is saved in the database, a JSON response with 201 CREATED status code is returned.

To run the application, add the following code:

app/app.py
. . .
if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

Checking if the global variable  __name__ in the module is the entry point to your program is made by the condition __name__ == "__main__" . If the __name__ equals "__main__" , the app will be executed by the code inside the block if. It will use the application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG) command.

After that, the values for the ENVIRONMENT_PORT and ENVIRONMENT_DEBUG are obtained from the environment variables using os.environ.get(). The key is used as the first parameter, while default value is used as the second parameter. The command application.run() sets the debug, port, and host values for the application.

When completed, the file app.py will look as follows:

app/app.py
import os
from flask import Flask, request, jsonify
from flask_pymongo import PyMongo

application = Flask(__name__)

application.config["MONGO_URI"] = 'mongodb://' + os.environ['MONGODB_USERNAME'] + ':' + os.environ['MONGODB_PASSWORD'] + '@' + os.environ['MONGODB_HOSTNAME'] + ':27017/' + os.environ['MONGODB_DATABASE']

mongo = PyMongo(application)
db = mongo.db

@application.route('/')
def index():
    return jsonify(
        status=True,
        message='Welcome to the Dockerized Flask MongoDB app!'
    )

@application.route('/todo')
def todo():
    _todos = db.todo.find()

    item = {}
    data = []
    for todo in _todos:
        item = {
            'id': str(todo['_id']),
            'todo': todo['todo']
        }
        data.append(item)

    return jsonify(
        status=True,
        data=data
    )

@application.route('/todo', methods=['POST'])
def createTodo():
    data = request.get_json(force=True)
    item = {
        'todo': data['todo']
    }
    db.todo.insert_one(item)

    return jsonify(
        status=True,
        message='To-do saved successfully!'
    ), 201

if __name__ == "__main__":
    ENVIRONMENT_DEBUG = os.environ.get("APP_DEBUG", True)
    ENVIRONMENT_PORT = os.environ.get("APP_PORT", 5000)
    application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG)

When finished, save the file and exit the text editor.

Then, in the app directory, create the file wsgi.py.

$ nano app/wsgi.py

The file wsgi.py creates an application object (or callable) for the server to use it. This application runs the application’s request handlers upon parsing the URL every time a request comes.

Insert the contents below into the wsgi.py file, save it, and exit the text editor. Copy the following contents into the file:

app/wsgi.py
from app import application

if __name__ == "__main__":
  application.run()

This file imports the application object from the file app.py and then creates an application object for the Gunicorn server.

With the to-do app configured, let's proceed to run the application in containers.

Step Five — How to Build and Run the Containers


With the services and their configurations defined in the file docker-compose.yml, the containers can be started.

Since a single file is used to define all the services, you have to issue a single command that will start the containers, set up the networks, and create the volumes. Also, the command is used to build the image for your Nginx web server and Flask application. You can build the containers with the command below:

$ docker-compose up -d

If it is run for the first time, the command will download all the necessary Docker images. This process can take some time. When downloaded, the images will be stored in your local machine; after that, docker-compose will create your containers. The process will be daemonized by the  -d flag, allowing it to run as a background process.

Once the build process is complete, use the command below to list the running containers:

$ docker ps

A similar output should be returned:

Output
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                                      NAMES
f20e9a7fd2b9        digitalocean.com/webserver:latest   "nginx -g 'daemon of…"   2 weeks ago         Up 2 weeks          0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp   webserver
3d53ea054517        digitalocean.com/flask-python:3.6   "gunicorn -w 4 --bin…"   2 weeks ago         Up 2 weeks          5000/tcp                                   flask
96f5a91fc0db        mongo:4.0.8                     "docker-entrypoint.s…"   2 weeks ago         Up 2 weeks          27017/tcp                                  mongodb

From the output, the unique identifier CONTAINER ID is used to access containers. The notion IMAGE is used to define the image name for the particular container. The field NAMES is the name of the service, used for the created containers, which is similar to CONTAINER ID and can be used to access containers. Lastly, the field STATUS provides information concerning the state of the container: is it running, stopped, or restarting.

Concluding the above step, the command docker-compose was used to build containers from the configuration files. Now, a MongoDB user for your application will be created.

Step Six — How to Create a User for Your MongoDB Database


MongoDB is set by default to grant unlimited privileges to all users who can log in without credentials. Thus, a dedicated user should be created to secure your MongoDB database.

To create a dedicated user, you need the root password and username that were set in the environment variables MONGO_INITDB_ROOT_PASSWORD  and MONGO_INITDB_ROOT_USERNAME in the file docker-compose.yml for the mongodb service. It is recommended to avoid using the root administrative account in the interactions with the database. Thus, a new database will be created for Flask app to access, along with the dedicated database user.

An interactive shell should be started on the mongodb container to create a new user:

$ docker exec -it mongodb bash

As you can see, the command docker exec was used with the -it flag to run a command and interactive shell  inside a running container.

Inside the container, log in to the root administrative account of MongoDB:

root@96f5a91fc0db:/# mongo -u mongodbuser -p

You will need to provide a password that you entered in the docker-compose.yml file as the value for the variable MONGO_INITDB_ROOT_PASSWORD. You can change the password by setting a new value for the MONGO_INITDB_ROOT_PASSWORD in the service mongodb. However, you will need to re-run the command docker-compose up -d.

 List all databases by running the show dbs; command:

mongodb> show dbs;

The following output will be returned:

Output
admin    0.000GB
config   0.000GB
local    0.000GB
5 rows in set (0.00 sec)

From the output, there is a special admin database that grants users with administrative permissions. If a user is granted with read access to the admin database, they will be granted with read and write permissions to all other databases. The admin database is listed in the output, which means the user has access to this database and, respectively, the read and write permission to all other databases.

By saving the first to-do note, you will create the MongoDB database automatically. You can switch to a database that does not exist in MongoDB by running the use database command. As soon as a document gets saved to a collection, the command creates a database.That is why the database is not created here; it will be created when the first to-do note will be saved in the database from the API. Run the use command to switch to the database flaskdb:

mongodb> use flaskdb

After that, create a new user that will have access to this database. Use the following command:

mongodb> db.createUser({user: 'flaskuser', pwd: 'your password', roles: [{role: 'readWrite', db: 'flaskdb'}]})
mongodb> exit

With this command, a user named flaskuser is created. It has readWrite access to the flaskdb database. In the pwd field, provide a secure password. Here, pwd and user are the values that have been defined in the file docker-compose.yml under the section of environment variables for the flask service.

Use the following command to log in to the authenticated database:

$ mongo -u flaskuser -p your password --authenticationDatabase flaskdb

Log out of the database after creating the user:

mongodb> exit

After that, exit the container:

root@96f5a91fc0db:/# exit

A user account  and the dedicated database for your Flask application have been created. The components of the database are ready, it is time to proceed to running the Flask to-do app. See the next step for the instructions.

Step Seven — Running the Flask To-do App


With all of the services configured and running, you can use your browser to test the application. Navigate to http://your_server_ip. Also, to see the JSON response from Flask, run curl:

$ curl -i http://your_server_ip

The following response will be returned:

Output
{"message":"Welcome to the Dockerized Flask MongoDB app!","status":true}

 The MONGODB_* variables defined in the environment section of the flask service set the configuration of the database connection. Flask application configuration is passed to the application from the file docker-compose.yml.

Using the Flask API to create a to-do note and test everything. Make a POST curl request to the /todo route to do that:

$ curl -i -H "Content-Type: application/json" -X POST -d '{"todo": "Dockerize Flask application with MongoDB backend"}' http://your_server_ip/todo

As soon as the to-do item is saved to MongoDB, the request returns a response with a code 201 CREATED of the status:

Output
{"message":"To-do saved successfully!","status":true}

A GET request to the /todo route will list all of the to-do notes from MongoDB:

$ curl -i http://your_server_ip/todo
Output
{"data":[{"id":"5c9fa25591cb7b000a180b60","todo":"Dockerize Flask application with MongoDB backend"}],"status":true}

The command has Dockerized a Flask API running a MongoDB backend with a reverse proxy Nginx, deployed to your servers. sudo systemctl enable docker can be used for a production environment to make sure your Docker service starts at runtime automatically.

  • NGINX, MONGODB, PYTHON, DOCKER, PYTHON FRAMEWORKS
  • 0 Users Found This Useful
Was this answer helpful?

Related Articles

How To Install Linux, Apache, MySQL, PHP (LAMP) stack on Ubuntu 18.04

Introduction ‘LAMP” is a stack of open-source software. Typically, it is installed together in...

How To Install Linux, Nginx, MySQL, PHP (LEMP stack) on Ubuntu 18.04

Introduction LEMP is a pack of software. It is generally used to serve dynamic web applications...

How To Install Nginx on CentOS 7

What is Nginx Nginx is server software that is characterized by high performance, flexibility,...

How To Install Node.js on Debian 10

Introduction Node.js a Javascript platform that allows easy creation of networked applications...

How To Install Node.js on Ubuntu 18.04

Introduction Node.js is a JavaScript platform that is used to allow users to build network...