One place for hosting & domains

      Flask

      How To Set Up Flask with MongoDB and Docker


      The author selected the Internet Archive to receive a donation as part of the Write for DOnations program.

      Introduction

      Developing web applications can become complex and time consuming when building and maintaining a number of different technologies. Considering lighter weight options designed to reduce complexity and time-to-production for your application can result in a more flexible and scalable solution. As a micro web framework built on Python, Flask provides an extensible way for developers to grow their applications through extensions that can be integrated into projects. To continue the scalability of a developer’s tech stack, MongoDB is a NoSQL database is designed to scale and work with frequent changes. Developers can use Docker to simplify the process of packaging and deploying their applications.

      Docker Compose has further simplified the development environment by allowing you to define your infrastructure, including your application services, network volumes, and bind mounts, in a single file. Using Docker Compose provides ease of use over running multiple docker container run commands. It allows you to define all your services in a single Compose file, and with a single command you create and start all the services from your configuration. This ensures that there is version control throughout your container infrastructure. Docker Compose uses a project name to isolate environments from each other, this allows you to run multiple environments on a single host.

      In this tutorial you will build, package, and run your to-do web application with Flask, Nginx, and MongoDB inside of Docker containers. You will define the entire stack configuration in a docker-compose.yml file, along with configuration files for Python, MongoDB, and Nginx. Flask requires a web server to serve HTTP requests, so you will also use Gunicorn, which is a Python WSGI HTTP Server, to serve the application. Nginx acts as a reverse proxy server that forwards requests to Gunicorn for processing.

      Prerequisites

      To follow this tutorial, you will need the following:

      Step 1 — Writing the Stack Configuration in Docker Compose

      Building your applications on Docker allows you to version infrastructure easily depending on configuration changes you make in Docker Compose. The infrastructure can be defined in a single file and built with a single command. In this step, you will set up the docker-compose.yml file to run your Flask application.

      The docker-compose.yml file lets you define your application infrastructure as individual services. The services can be connected to each other and each can have a volume attached to it for persistent storage. Volumes are stored in a part of the host filesystem managed by Docker (/var/lib/docker/volumes/ on Linux).

      Volumes are the best way to persist data in Docker, as the data in the volumes can be exported or shared with other applications. For additional information about sharing data in Docker, you can refer to How To Share Data Between the Docker Container and the Host.

      To get started, create a directory for the application in the home directory on your server:

      Move into the newly created directory:

      Next, create the docker-compose.yml file:

      The docker-compose.yml file starts with a version number that identifies the Docker Compose file version. Docker Compose file version 3 targets Docker Engine version 1.13.0+, which is a prerequisite for this setup. You will also add the services tag that you will define in the next step:

      docker-compose.yml

      version: '3'
      services:
      

      You will now define flask as the first service in your docker-compose.yml file. Add the following code to define the Flask service:

      docker-compose.yml

      . . .
        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
      

      The build property defines the context of the build. In this case, the app folder that will contain the Dockerfile.

      You use the container_name property to define a name for each container. The image property specifies the image name and what the Docker image will be tagged as. The restart property defines how the container should be restarted—in your case it is unless-stopped. This means your containers will only be stopped when the Docker Engine is stopped/restarted or when you explicitly stop the containers. The benefit of using the unless-stopped property is that the containers will start automatically once the Docker Engine is restarted or any error occurs.

      The environment property contains the environment variables that are passed to the container. You need to provide a secure password for the environment variable MONGODB_PASSWORD. The volumes property defines the volumes the service is using. In your case the volume appdata is mounted inside the container at the /var/www directory. The depends_on property defines a service that Flask depends on to function properly. In this case, the flask service will depend on mongodb since the mongodb service acts as the database for your application. depends_on ensures that the flask service only runs if the mongodb service is running.

      The networks property specifies frontend and backend as the networks the flask service will have access to.

      With the flask service defined, you’re ready to add the MongoDB configuration to the file. In this example, you will use the official 4.0.8 version mongo image. Add the following code to your docker-compose.yml file following the flask service:

      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
      

      The container_name for this service is mongodb with a restart policy of unless-stopped. You use the command property to define the command that will be executed when the container is started. The command mongod --auth will disable logging into the MongoDB shell without credentials, which will secure MongoDB by requiring authentication.

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

      MongoDB stores its data in /data/db by default, therefore the data in the /data/db folder will be written to the named volume mongodbdata for persistence. As a result you won’t lose your databases in the event of a restart. The mongoDB service does not expose any ports, so the service will only be accessible through the backend network.

      Next, you will define the web server for your application. Add the following code to your docker-compose.yml file to configure Nginx:

      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
      

      Here you have defined the context of the build, which is the nginx folder containing the Dockerfile. With the image property, you specify the image used to tag and run the container. The ports property will configure the Nginx service to be publicly accessible through :80 and :443 and volumes mounts the nginxdata volume inside the container at /var/log/nginx directory.

      You’ve defined the service on which the web server service depends_on as flask. Finally the networks property defines the networks web server service will have access to the frontend.

      Next, you will create bridge networks to allow the containers to communicate with each other. Append the following lines to the end of your file:

      docker-compose.yml

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

      You defined two networks—frontend and backend—for the services to connect to. The front-end services, such as Nginx, will connect to the frontend network since it needs to be publicly accessible. Back-end services, such as MongoDB, will connect to the backend network to prevent unauthorized access to the service.

      Next, you will use volumes to persist the database, application, and configuration files. Since your application will use the databases and files, it is imperative to persist the changes made to them. The volumes are managed by Docker and stored on the filesystem. Add this code to the docker-compose.yml file to configure the volumes:

      docker-compose.yml

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

      The volumes section declares the volumes that the application will use to persist data. Here you have defined the volumes mongodbdata, appdata, and nginxdata for persisting your MongoDB databases, Flask application data, and the Nginx web server logs, respectively. All of these volumes use a local driver to store the data locally. The volumes are used to persist this data so that data like your MongoDB databases and Nginx webserver logs could be lost once you restart the containers.

      Your complete docker-compose.yml file will look like this:

      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
      

      Save the file and exit the editor after verifying your configuration.

      You’ve defined the Docker configuration for your entire application stack in the docker-compose.yml file. You will now move on to writing the Dockerfiles for Flask and the web server.

      Step 2 — Writing the Flask and Web Server Dockerfiles

      With Docker, you can build containers to run your applications from a file called Dockerfile. The Dockerfile is a tool that enables you to create custom images that you can use to install the software required by your application and configure your containers based on your requirements. You can push the custom images you create to Docker Hub or any private registry.

      In this step, you’ll write the Dockerfiles for the Flask and web server services. To get started, create the app directory for your Flask application:

      Next, create the Dockerfile for your Flask app in the app directory:

      Add the following code to the file to customize your Flask container:

      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/
      

      In this Dockerfile, you are creating an image on top of the 3.6.8-alpine3.9 image that is based on Alpine 3.9 with Python 3.6.8 pre-installed.

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

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

      Add the following code block to copy the Flask application into the container and install the necessary dependencies:

      app/Dockerfile

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

      The following code will use the ADD directive to copy files from the local app directory to the /var/www directory on the container. Next, the Dockerfile will use the RUN directive to install Gunicorn and the packages specified in the requirements.txt file, which you will create later in the tutorial.

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

      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"]
      

      By default, Docker containers run as the root user. The root user has access to everything in the system, so the implications of a security breach can be disastrous. To mitigate this security risk, this will create a new user and group that will only have access to the /var/www directory.

      This code will first use the addgroup command to create a new group named www. The -g flag will set the group ID to the ENV GROUP_ID=1000 variable that is defined earlier in the Dockerfile.

      The adduser -D -u $USER_ID -G www www -s /bin/sh lines creates a www user with a user ID of 1000, as defined by the ENV variable. The -s flag creates the user’s home directory if it does not exist and sets the default login shell to /bin/sh. The -G flag is used to set the user’s initial login group to www, which was created by the previous command.

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

      Finally, the CMD [ "gunicorn", "-w", "4", "--bind", "0.0.0.0:5000", "wsgi"] line runs the command to start the Gunicorn server with four workers listening on port 5000. The number should generally be between 2–4 workers per core in the server, Gunicorn documentation recommends (2 x $num_cores) + 1 as the number of workers to start with.

      Your completed Dockerfile will look like the following:

      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"]
      

      Save the file and exit the text editor.

      Next, create a new directory to hold your Nginx configuration:

      Then create the Dockerfile for your Nginx web server in the nginx directory:

      Add the following code to the file to create the Dockerfile that will build the image for your Nginx container:

      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;"]
      

      This Nginx Dockerfile uses an alpine base image, which is a tiny Linux distribution with a minimal attack surface built for security.

      In the RUN directive you are installing nginx as well as creating symbolic links to publish the error and access logs to the standard error (/dev/stderr) and output (/dev/stdout). Publishing errors to standard error and output is a best practice since containers are ephemeral, doing this the logs are shipped to docker logs and from there you can forward your logs to a logging service like the Elastic stack for persistance. After this is done, commands are run to remove the default.conf and /var/cache/apk/* to reduce the size of the resulting image. Executing all of these commands in a single RUN decreases the number of layers in the image, which also reduces the size of the resulting image.

      The COPY directive copies the app.conf web server configuration inside of the container. The EXPOSE directive ensures the containers listen on ports :80 and :443, as your application will run on :80 with :443 as the secure port.

      Finally, the CMD directive defines the command to start the Nginx server.

      Save the file and exit the text editor.

      Now that the Dockerfile is ready, you are ready to configure the Nginx reverse proxy to route traffic to the Flask application.

      Step 3 — Configuring the Nginx Reverse Proxy

      In this step, you will configure Nginx as a reverse proxy to forward requests to Gunicorn on :5000. A reverse proxy server is used to direct client requests to the appropriate back-end server. It provides an additional layer of abstraction and control to ensure the smooth flow of network traffic between clients and servers.

      Get started by creating the nginx/conf.d directory:

      To configure Nginx, you need to create an app.conf file with the following configuration in the nginx/conf.d/ folder. The app.conf file contains the configuration that the reverse proxy needs to forward the requests to Gunicorn.

      • nano nginx/conf.d/app.conf

      Put the following contents into the app.conf file:

      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;
          }
      }
      

      This will first define the upstream server, which is commonly used to specify a web or app server for routing or load balancing.

      Your upstream server, app_server, defines the server address with the server directive, which is identified by the container name flask:5000.

      The configuration for the Nginx web server is defined in the server block. The listen directive defines the port number on which your server will listen for incoming requests. The error_log and access_log directives define the files for writing logs. The proxy_pass directive is used to set the upstream server for forwarding the requests to http://app_server.

      Save and close the file.

      With the Nginx web server configured, you can move on to creating the Flask to-do API.

      Step 4 — Creating the Flask To-do API

      Now that you’ve built out your environment, you’re ready to build your application. In this step, you will write a to-do API application that will save and display to-do notes sent in from a POST request.

      Get started by creating the requirements.txt file in the app directory:

      • nano app/requirements.txt

      This file is used to install the dependencies for your application. The implementation of this tutorial will use Flask, Flask-PyMongo, and requests. Add the following to the requirements.txt file:

      app/requirements.txt

      Flask==1.0.2
      Flask-PyMongo==2.2.0
      requests==2.20.1
      

      Save the file and exit the editor after entering the requirements.

      Next, create the app.py file to contain the Flask application code in the app directory:

      In your new app.py file, enter in the code to import the dependencies:

      app/app.py

      import os
      from flask import Flask, request, jsonify
      from flask_pymongo import PyMongo
      

      The os package is used to import the environment variables. From the flask library you imported the Flask, request, and jsonify objects to instantiate the application, handle requests, and send JSON responses, respectively. From flask_pymongo you imported the PyMongo object to interact with the MongoDB.

      Next, add the code needed to connect to MongoDB:

      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 Flask(__name__) loads the application object into the application variable. Next, the code builds the MongoDB connection string from the environment variables using os.environ. Passing the application object in to the PyMongo() method will give you the mongo object, which in turn gives you the db object from mongo.db.

      Now you will add the code to create an index message:

      app/app.py

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

      The @application.route('/') defines the / GET route of your API. Here your index() function returns a JSON string using the jsonify method.

      Next, add the /todo route to list all to-do’s:

      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 @application.route('/todo') defines the /todo GET route of your API, which returns the to-dos in the database. The db.todo.find() method returns all the to-dos in the database. Next, you iterate over the _todos to build an item that includes only the id and todo from the objects appending them to a data array and finally returns them as JSON.

      Next, add the code for creating the to-do:

      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 @application.route('/todo') defines the /todo POST route of your API, which creates a to-do note in the database. The request.get_json(force=True) gets the JSON that you post to the route, and item is used to build the JSON that will be saved in the to-do. The db.todo.insert_one(item) is used to insert one item into the database. After the to-do is saved in the database you return a JSON response with a status code of 201 CREATED.

      Now you add the code to run the application:

      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)
      

      The condition __name__ == "__main__" is used to check if the global variable, __name__, in the module is the entry point to your program, is "__main__", then run the application. If the __name__ is equal to "__main__" then the code inside the if block will execute the app using this command application.run(host='0.0.0.0', port=ENVIRONMENT_PORT, debug=ENVIRONMENT_DEBUG).

      Next, we get the values for the ENVIRONMENT_DEBUG and ENVIRONMENT_PORT from the environment variables using os.environ.get(), using the key as the first parameter and default value as the second parameter. The application.run() sets the host, port, and debug values for the application.

      The completed app.py file will look like this:

      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)
      

      Save the file and exit the editor.

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

      The wsgi.py file creates an application object (or callable) so that the server can use it. Each time a request comes, the server uses this application object to run the application’s request handlers upon parsing the URL.

      Put the following contents into the wsgi.py file, save the file, and exit the text editor:

      app/wsgi.py

      from app import application
      
      if __name__ == "__main__":
        application.run()
      

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

      The to-do app is now in place, so you’re ready to start running the application in containers.

      Step 5 — Building and Running the Containers

      Now that you have defined all of the services in your docker-compose.yml file and their configurations, you can start the containers.

      Since the services are defined in a single file, you need to issue a single command to start the containers, create the volumes, and set up the networks. This command also builds the image for your Flask application and the Nginx web server. Run the following command to build the containers:

      When running the command for the first time, it will download all of the necessary Docker images, which can take some time. Once the images are downloaded and stored in your local machine, docker-compose will create your containers. The -d flag daemonizes the process, which allows it to run as a background process.

      Use the following command to list the running containers once the build process is complete:

      You will see output similar to the following:

      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

      The CONTAINER ID is a unique identifier that is used to access containers. The IMAGE defines the image name for the given container. The NAMES field is the service name under which containers are created, similar to CONTAINER ID these can be used to access containers. Finally, the STATUS provides information regarding the state of the container whether it’s running, restarting, or stopped.

      You’ve used the docker-compose command to build your containers from your configuration files. In the next step, you will create a MongoDB user for your application.

      Step 6 — Creating a User for Your MongoDB Database

      By default, MongoDB allows users to log in without credentials and grants unlimited privileges. In this step, you will secure your MongoDB database by creating a dedicated user to access it.

      To do this, you will need the root username and password that you set in the docker-compose.yml file environment variables MONGO_INITDB_ROOT_USERNAME and MONGO_INITDB_ROOT_PASSWORD for the mongodb service. In general, it’s better to avoid using the root administrative account when interacting with the database. Instead, you will create a dedicated database user for your Flask application, as well as a new database that the Flask app will be allowed to access.

      To create a new user, first start an interactive shell on the mongodb container:

      • docker exec -it mongodb bash

      You use the docker exec command in order to run a command inside a running container along with the -it flag to run an interactive shell inside the container.

      Once inside the container, log in to the MongoDB root administrative account:

      You will be prompted for the password that you entered as the value for the MONGO_INITDB_ROOT_PASSWORD variable in the docker-compose.yml file. The password can be changed by setting a new value for the MONGO_INITDB_ROOT_PASSWORD in the mongodb service, in which case you will have to re-run the docker-compose up -d command.

      Run the show dbs; command to list all databases:

      You will see the following output:

      Output

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

      The admin database is a special database that grants administrative permissions to users. If a user has read access to the admin database, they will have read and write permissions to all other databases. Since the output lists the admin database, the user has access to this database and can therefore read and write to all other databases.

      Saving the first to-do note will automatically create the MongoDB database. MongoDB allows you to switch to a database that does not exist using the use database command. It creates a database when a document is saved to a collection. Therefore the database is not created here; that will happen when you save your first to-do note in the database from the API. Execute the use command to switch to the flaskdb database:

      Next, create a new user that will be allowed to access this database:

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

      This command creates a user named flaskuser with readWrite access to the flaskdb database. Be sure to use a secure password in the pwd field. The user and pwd here are the values you defined in the docker-compose.yml file under the environment variables section for the flask service.

      Log in to the authenticated database with the following command:

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

      Now that you have added the user, log out of the database.

      And finally, exit the container:

      You’ve now configured a dedicated database and user account for your Flask application. The database components are ready, so now you can move on to running the Flask to-do app.

      Step 7 — Running the Flask To-do App

      Now that your services are configured and running, you can test your application by navigating to http://your_server_ip in a browser. Additionally, you can run curl to see the JSON response from Flask:

      • curl -i http://your_server_ip

      You will receive the following response:

      Output

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

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

      To test everything out, create a to-do note using the Flask API. You can do this with a POST curl request to the /todo route:

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

      This request results in a response with a status code of 201 CREATED when the to-do item is saved to MongoDB:

      Output

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

      You can list all of the to-do notes from MongoDB with a GET request to the /todo route:

      • curl -i http://your_server_ip/todo

      Output

      {"data":[{"id":"5c9fa25591cb7b000a180b60","todo":"Dockerize Flask application with MongoDB backend"}],"status":true}

      With this, you have Dockerized a Flask API running a MongoDB backend with Nginx as a reverse proxy deployed to your servers. For a production environment you can use sudo systemctl enable docker to ensure your Docker service automatically starts at runtime.

      Conclusion

      In this tutorial, you deployed a Flask application with Docker, MongoDB, Nginx, and Gunicorn. You now have a functioning modern stateless API application that can be scaled. Although you can achieve this result by using a command like docker container run, the docker-compose.yml simplifies your job as this stack can be put into version control and updated as necessary.

      From here you can also take a look at our further Python Framework tutorials.



      Source link

      Deploy a Flask Application on Ubuntu


      Updated by abalarin Contributed by Austin Balarin

      Flask is a light-weight web framework for Python that includes several utilities and libraries you can use to create a web application. Once you have developed a Flask application in a local environment, you will need to prepare the application’s production environment in order to run the application and serve it to your application’s users via the internet.

      This guide will walk you through the steps to deploy a Flask application to a production environment running on a Linode. Your production environment will use NGINX as the web server and reverse proxy, Gunicorn as the web server gateway interface (WSGI) application server, and Supervisor for monitoring and auto-reloading Gunicorn should it go down. This guide will not cover creating a Flask application or related Python concepts.

      In this guide you will complete the following:

      Before You Begin

      1. Create a Flask Application or use this Example Blog Application. Clone and run it on your local machine using GitHub.

        git clone https://github.com/abalarin/Flask-on-Linode.git flask_app_project
        

        Note

      2. If you’re not using the example application, host your Flask application code on a remote version control system, like GitHub. This guide will use GitHub for all examples.

      3. Create a 1GB Linode to host your Flask web application. Depending on the size of your application and the amount of users you expect to visit it, you may consider a large Linode plan.

      4. Familiarize yourself with our Getting Started guide and complete the steps for updating your system’s software, setting your Linode’s hostname and timezone.

      5. This guide will use sudo wherever possible. Complete the sections of our Securing Your Server to create a limited user account, harden SSH access and remove unnecessary network services.

      Copy Your Flask App to Your Linode

      After creating your Flask application in your local development environment, you are now ready to deploy it to a production environment. You will need to copy your local Flask application code to your Linode. You can accomplish this by either cloning your GitHub project to your Linode using Git or by using the secure copy method to directly transfer your application files to your Linode. This section will provide steps for both options.

      Note

      This guide’s examples will transfer your Flask application files to your Linode’s /home directory. If you prefer, you can store your application files in a different directory, however, ensure you run the examples using your own app’s directory location.

      Clone Your App From Source Control

      1. Ensure your latest Flask application code is available in your project’s remote repository.

      2. SSH into your Linode. Replace the example IP address with your Linode’s IP address:

        ssh [email protected]
        
      3. Navigate to your Linode’s home directory:

        cd /home
        
      4. Clone your project from your remote version control system. If you are not using the example repository, Example Flask Blog Application, replace the example repo with your own:

        git clone https://github.com/abalarin/Flask-on-Linode.git flask_app_project
        
          
        [email protected]:/home# git clone https://github.com/abalarin/Flask-on-Linode.git
        Cloning into 'Flask-on-Linode'...
        remote: Enumerating objects: 79, done.
        remote: Counting objects: 100% (79/79), done.
        remote: Compressing objects: 100% (57/57), done.
        remote: Total 79 (delta 26), reused 66 (delta 16), pack-reused 0
        Unpacking objects: 100% (79/79), done.
        Checking connectivity... done.
        
        

      Secure Copy your App From a Local Machine

      1. From your local machine, secure copy (SCP) your project into your Linode’s /home directory. Replace the example IP address with your Linode’s IP address and flask_app with the name of your project’s root directory:

        scp -r flask_app_project/ [email protected]:/home
        
      2. Once complete, navigate to your Linode and view the contents of your copied directory:

        ls flask_app_project
        

        You should see a similar output:

          
            flask_app  FlaskDeployment.md  README.md
            
        

        Now that your Linode contains your application files, you can move on to preparing your production environment.

      Prepare Your Production Environment

      Install and Configure NGINX

      NGINX is open-source software that can be used as a high-performance web server, reverse proxy, load-balancer, and more. In this section you will configure NGINX as a web server and reverse proxy for your Flask application. This means that NGINX will sit between your Flask application and external clients and forward all client requests to your running Flask application.

      1. Install NGINX:

        sudo apt install nginx
        
      2. Using your editor of choice, create an NGINX configuration file for your app with the example content and save it. This example uses the nano text editor. Replace flask_app with your applications name and 192.0.2.0 with your Linode’s IP or your fully qualified domain name (FQDN):

        sudo nano /etc/nginx/sites-enabled/flask_app
        
        /etc/nginx/sites-enabled/flask_app
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        
        server {
            listen 80;
            server_name 192.0.2.0;
        
            location / {
                proxy_pass http://127.0.0.1:8000;
                proxy_set_header Host $host;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            }
        }
      3. Disable the NGINX’s default configuration file by removing its symlink:

        sudo unlink /etc/nginx/sites-enabled/default
        
      4. Reload your NGINX configuration file:

        sudo nginx -s reload
        
      5. Navigate to your Linode’s IP address in a web browser. You should see a similar NGINX Gateway error. This error appears because you have not set up the WSGI application server yet. You will set up your application server in the Install and Configure Gunicorn section of the guide.

        502Gateway

      Install Python and Packages

      To run your Flask application, you will need to install Python, Flask, pip3 and any other required package dependencies on your Linode.

      Note

      This guide was created using Python 3.6.8

      1. In your Linode’s /home directory, install Python 3:

        sudo apt install python3
        
      2. Install pip3, the standard package manager for Python:

        sudo apt install python3-pip
        
      3. Navigate to your project’s root directory:

        cd /home/flask_app_project
        
      4. Install Flask packages and libraries using pip3. If you are using the Example Flask Blog Application, then the packages your application will need are listed in the /home/flask_app_project/flask_app/requirements.txt file. You can use pip to install all listed packages.

        pip3 install -r flask_app/requirements.txt
        

        While the packages and libraries are being installed, you will see a similar output:

          
        [email protected]:/home/Flask-on-Linode# pip3 install -r flask_app/requirements.txt
        Collecting flask-sqlalchemy (from -r flask_app/requirements.txt (line 1))
          Downloading https://files.pythonhosted.org/packages/08/ca/582442cad71504a1514a2f053006c8bb128844133d6076a4df17117545fa/Flask_SQLAlchemy-2.4.0-py2.py3-none-any.whl
        Collecting sqlalchemy (from -r flask_app/requirements.txt (line 2))
          Downloading https://files.pythonhosted.org/packages/55/98/56b7155bab287cd0c78dee26258835db36e91f2efef41f125ed6f6f1f334/SQLAlchemy-1.3.6.tar.gz (5.9MB)
            100% |████████████████████████████████| 5.9MB 218kB/s
        Collecting flask-security (from -r flask_app/requirements.txt (line 3))
          Downloading https://files.pythonhosted.org/packages/88/47/4908a5040120768ff4fb2465c7eeafeb9239c27d2919bd67c4ccc1b43e14/Flask_Security-3.0.0-py2.py3-none-any.whl (68kB)
            100% |████████████████████████████████| 71kB 8.7MB/s
        Collecting flask-wtf (from -r flask_app/requirements.txt (line 4))
          Downloading https://files.pythonhosted.org/packages/60/3a/58c629472d10539ae5167dc7c1fecfa95dd7d0b7864623931e3776438a24/Flask_WTF-0.14.2-py2.py3-none-any.whl
        ...
        
        

        Now that your Linode has all required dependencies, you will configure Flask in the next section.

      Configure Flask

      Depending on your Flask application’s environment, there are different settings you may need to configure, like toggling the debug mode, setting the secret key, setting the database URI, etc. For more information on Flask’s available configuration options see Flask’s configuration docs.

      In this section, you will create a JSON file to store your environment configurations and then load that configuration into your Flask app. The configuration created in this section is a basic example of some Flask environment variables you might include in your application.

      Caution

      You should keep sensitive configuration files outside of source control. If you source control your configuration file, which contains sensitive values, in a remote repository, then someone could access it and use that information to compromise your Linode or application. To keep your configuration file out of your Git repository, add it to your .gitignore file.

      1. Create a JSON configuration file with the text editor of your choice:

        sudo nano /etc/config.json
        
      2. Store your application’s environment variables in your JSON configuration file with the example content:

        /etc/config.json
        1
        2
        3
        4
        
        {
          "SECRET_KEY": "1A37BbcCJh67",
          "SQLALCHEMY_DATABASE_URI": "sqlite:///site.db"
        }
        • The SECRET_KEY is used to keep client-side sessions secure using a session cookie that can only be modified if the secret key is known and used for signing. Replace the value included in the example with your own randomly generated value.

        • The SQLALCHEMY_DATABASE_URI defines the database URI that should be used for the application’s connection to SQLite.

      3. Modify your Flask app’s __init__.py file to import the newly created JSON configuration:

        /home/flask_app_project/flask_app/__init__.py
         1
         2
         3
         4
         5
         6
         7
         8
         9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        
        from flask import Flask
        from flask_sqlalchemy import SQLAlchemy
        from flask_login import LoginManager
        import json
        import urllib3
        
        app = Flask(__name__)
        
        with open('/etc/config.json') as config_file:
          config = json.load(config_file)
        
        app.config['SECRET_KEY'] = config.get('SECRET_KEY')
        app.config['SQLALCHEMY_DATABASE_URI'] = config.get('SQLALCHEMY_DATABASE_URI')
        db = SQLAlchemy(app)
        
        login_manager = LoginManager()
        login_manager.init_app(app)
        
        from flask_app import routes
            

      Install and Configure Gunicorn

      Gunicorn, Green Unicorn, is a Python web server gateway interface (WSGI) HTTP Server for UNIX. It will be used to forward requests from your NGINX web server to your Flask application.

      1. Install Gunicorn on your Linode:

        sudo apt install gunicorn3
        
      2. Run Gunicorn from your application’s root directory, flask_app_project. The command tells Gunicorn to look for the WSGI instance named app in the flask_app directory. In our example project, the WSGI instance named app is located in /home/Flask-on-Linode/flask_app/__init__.py.

        gunicorn3 --workers=3 flask_app:app
        
          
        [email protected]:/home/Flask-on-Linode# gunicorn -w 3 flask_app:app
        [2019-07-25 15:09:04 +0000] [32421] [INFO] Starting gunicorn 19.9.0
        [2019-07-25 15:09:04 +0000] [32421] [INFO] Listening at: http://127.0.0.1:8000 (32421)
        [2019-07-25 15:09:04 +0000] [32421] [INFO] Using worker: sync
        [2019-07-25 15:09:04 +0000] [32424] [INFO] Booting worker with pid: 32424
        [2019-07-25 15:09:04 +0000] [32425] [INFO] Booting worker with pid: 32425
        [2019-07-25 15:09:04 +0000] [32426] [INFO] Booting worker with pid: 32426
        
        

        Note

        You can specify the amount of workers you want Gunicorn to use with the --workers flag. A good rule of thumb to determine worker count is to double your system’s CPU cores and add 1. For a Nanode with 1 CPU core you should use 3 workers.
      3. After running Gunicorn, your Flask application should be live and available over the internet. Open a web browser and enter your Linode’s IP address to access your application. If you used the example Flask blog application, you should see the following:

        Your App

        Continue on to the next section to configure Supervisor to monitor and control your Flask app.

      Install and Configure Supervisor

      Supervisor is a client/server system that allows its users to monitor and control a number of processes on UNIX-like operating systems. Supervisor can handle auto-reloading Gunicorn if it crashes or if your Linode is rebooted unexpectedly. In this section, you will install and configure Supervisor.

      1. Open a new shell session and SSH into your Linode:

        ssh [email protected]
        
      2. Install Supervisor:

        sudo apt install supervisor
        
      3. Create a Supervisor script. Replace any instances of flask_app with the name of your application:

        sudo nano /etc/supervisor/conf.d/flask_app.conf
        
        /etc/supervisor/conf.d/flask_app.conf
        1
        2
        3
        4
        5
        6
        7
        8
        9
        
        [program:flask_app]
        directory=/home/flask_app_project
        command=gunicorn3 -workers=3 flask_app:app
        autostart=true
        autorestart=true
        stopasgroup=true
        killasgroup=true
        stderr_logfile=/var/log/flask_app/flask_app.err.log
        stdout_logfile=/var/log/flask_app/flask_app.out.log
      4. Create the log directories and files listed in the flask_app.conf file. Make sure to replace flask_app if it was modified in the Supervisor script above:

        sudo mkdir /var/log/flask_app
        sudo touch /var/log/flask_app/flask_app.out.log
        sudo touch /var/log/flask_app/flask_app.err.log
        
      5. Reload Supervisor to apply your changes:

        sudo supervisorctl reload
        

        You should see a similar output:

          
        Restarted supervisord
            
        

        Note

        Your application should now be accessible again via your Linode’s IP. If you are unable to access your application or receive a bad gateway error, Gunicorn is likely not running. Check your log files to further investigate the issue.

        cat /var/log/flaskapp/flaskapp.err.log
        cat /var/log/flaskapp/flaskapp.out.log
        

        Your Flask application is now deployed to your production environment and available to anyone for viewing. You can follow a similar workflow to deploy any Flask application to a Linode.

      More Information

      You may wish to consult the following resources for additional information on this topic. While these are provided in the hope that they will be useful, please note that we cannot vouch for the accuracy or timeliness of externally hosted materials.

      Find answers, ask questions, and help others.

      This guide is published under a CC BY-ND 4.0 license.



      Source link

      How To Build and Deploy a Flask Application Using Docker on Ubuntu 18.04


      The author selected the Tech Education Fund to receive a donation as part of the Write for DOnations program.

      Introduction

      Docker is an open-source application that allows administrators to create, manage, deploy, and replicate applications using containers. Containers can be thought of as a package that houses dependencies that an application requires to run at an operating system level. This means that each application deployed using Docker lives in an environment of its own and its requirements are handled separately.

      Flask is a web micro-framework that is built on Python. It is called a micro-framework because it does not require specific tools or plug-ins to run. The Flask framework is lightweight and flexible, yet highly structured, making it preferred over other frameworks.

      Deploying a Flask application with Docker will allow you to replicate the application across different servers with minimal reconfiguration.

      In this tutorial, you will create a Flask application and deploy it with Docker. This tutorial will also cover how to update an application after deployment.

      Prerequisites

      To follow this tutorial, you will need the following:

      Step 1 — Setting Up the Flask Application

      To get started, you will create a directory structure that will hold your Flask application. This tutorial will create a directory called TestApp in /var/www, but you can modify the command to name it whatever you’d like.

      • sudo mkdir /var/www/TestApp

      Move in to the newly created TestApp directory:

      Next, create the base folder structure for the Flask application:

      • sudo mkdir -p app/static app/templates

      The -p flag indicates that mkdir will create a directory and all parent directories that don't exist. In this case, mkdir will create the app parent directory in the process of making the static and templates directories.

      The app directory will contain all files related to the Flask application such as its views and blueprints. Views are the code you write to respond to requests to your application. Blueprints create application components and support common patterns within an application or across multiple applications.

      The static directory is where assets such as images, CSS, and JavaScript files live. The templates directory is where you will put the HTML templates for your project.

      Now that the base folder structure is complete, create the files needed to run the Flask application. First, create an __init__.py file inside the app directory. This file tells the Python interpreter that the app directory is a package and should be treated as such.

      Run the following command to create the file:

      • sudo nano app/__init__.py

      Packages in Python allow you to group modules into logical namespaces or hierarchies. This approach enables the code to be broken down into individual and manageable blocks that perform specific functions.

      Next, you will add code to the __init__.py that will create a Flask instance and import the logic from the views.py file, which you will create after saving this file. Add the following code to your new file:

      /var/www/TestApp/__init__.py

      from flask import Flask
      app = Flask(__name__)
      from app import views
      

      Once you've added that code, save and close the file.

      With the __init__.py file created, you're ready to create the views.py file in your app directory. This file will contain most of your application logic.

      Next, add the code to your views.py file. This code will return the hello world! string to users who visit your web page:

      /var/www/TestApp/app/views.py

      from app import app
      
      @app.route('/')
      def home():
         return "hello world!"
      

      The @app.route line above the function is called a decorator. Decorators modify the function that follows it. In this case, the decorator tells Flask which URL will trigger the home() function. The hello world text returned by the home function will be displayed to the user on the browser.

      With the views.py file in place, you're ready to create the uwsgi.ini file. This file will contain the uWSGI configurations for our application. uWSGI is a deployment option for Nginx that is both a protocol and an application server; the application server can serve uWSGI, FastCGI, and HTTP protocols.

      To create this file, run the following command:

      Next, add the following content to your file to configure the uWSGI server:

      /var/www/TestApp/uwsgi.ini

      [uwsgi]
      module = main
      callable = app
      master = true
      

      This code defines the module that the Flask application will be served from. In this case, this is the main.py file, referenced here as main. The callable option instructs uWSGI to use the app instance exported by the main application. The master option allows your application to keep running, so there is little downtime even when reloading the entire application.

      Next, create the main.py file, which is the entry point to the application. The entry point instructs uWSGI on how to interact with the application.

      Next, copy and paste the following into the file. This imports the Flask instance named app from the application package that was previously created.

      /var/www/TestApp/main.py

      from app import app
      

      Finally, create a requirements.txt file to specify the dependencies that the pip package manager will install to your Docker deployment:

      • sudo nano requirements.txt

      Add the following line to add Flask as a dependency:

      /var/www/TestApp/app/requirements.txt

      Flask==1.0.2
      

      This specifies the version of Flask to be installed. At the time of writing this tutorial, 1.0.2 is the latest Flask version. You can check for updates at the official website for Flask.

      Save and close the file. You have successfully set up your Flask application and are ready to set up Docker.

      Step 2 — Setting Up Docker

      In this step you will create two files, Dockerfile and start.sh, to create your Docker deployment. The Dockerfile is a text document that contains the commands used to assemble the image. The start.sh file is a shell script that will build an image and create a container from the Dockerfile.

      First, create the Dockerfile.

      Next, add your desired configuration to the Dockerfile. These commands specify how the image will be built, and what extra requirements will be included.

      /var/www/TestApp/Dockerfile

      FROM tiangolo/uwsgi-nginx-flask:python3.6-alpine3.7
      RUN apk --update add bash nano
      ENV STATIC_URL /static
      ENV STATIC_PATH /var/www/app/static
      COPY ./requirements.txt /var/www/requirements.txt
      RUN pip install -r /var/www/requirements.txt
      

      In this example, the Docker image will be built off an existing image, tiangolo/uwsgi-nginx-flask, which you can find on DockerHub. This particular Docker image is a good choice over others because it supports a wide range of Python versions and OS images.

      The first two lines specify the parent image that you'll use to run the application and install the bash command processor and the nano text editor. It also installs the git client for pulling and pushing to version control hosting services such as GitHub, GitLab, and Bitbucket. ENV STATIC_URL /static is an environment variable specific to this Docker image. It defines the static folder where all assets such as images, CSS files, and JavaScript files are served from.

      The last two lines will copy the requirements.txt file into the container so that it can be executed, and then parses the requirements.txt file to install the specified dependencies.

      Save and close the file after adding your configuration.

      With your Dockerfile in place, you're almost ready to write your start.sh script that will build the Docker container. Before writing the start.sh script, first make sure that you have an open port to use in the configuration. To check if a port is free, run the following command:

      • sudo nc localhost 56733 < /dev/null; echo $?

      If the output of the command above is 1, then the port is free and usable. Otherwise, you will need to select a different port to use in your start.sh configuration file.

      Once you've found an open port to use, create the start.sh script:

      The start.sh script is a shell script that will build an image from the Dockerfile and create a container from the resulting Docker image. Add your configuration to the new file:

      /var/www/TestApp/start.sh

      #!/bin/bash
      app="docker.test"
      docker build -t ${app} .
      docker run -d -p 56733:80 
        --name=${app} 
        -v $PWD:/app ${app}
      

      The first line is called a shebang. It specifies that this is a bash file and will be executed as commands. The next line specifies the name you want to give the image and container and saves as a variable named app. The next line instructs Docker to build an image from your Dockerfile located in the current directory. This will create an image called docker.test in this example.

      The last three lines create a new container named docker.test that is exposed at port 56733. Finally, it links the present directory to the /var/www directory of the container.

      You use the -d flag to start a container in daemon mode, or as a background process. You include the -p flag to bind a port on the server to a particular port on the Docker container. In this case, you are binding port 56733 to port 80 on the Docker container. The -v flag specifies a Docker volume to mount on the container, and in this case, you are mounting the entire project directory to the /var/www folder on the Docker container.

      Execute the start.sh script to create the Docker image and build a container from the resulting image:

      Once the script finishes running, use the following command to list all running containers:

      You will receive output that shows the containers:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 58b05508f4dd docker.test "/entrypoint.sh /sta…" 12 seconds ago Up 3 seconds 443/tcp, 0.0.0.0:56733->80/tcp docker.test

      You will find that the docker.test container is running. Now that it is running, visit the IP address at the specified port in your browser: http://ip-address:56733

      You'll see a page similar to the following:

      the home page

      In this step you have successfully deployed your Flask application on Docker. Next, you will use templates to display content to users.

      Step 3 — Serving Template Files

      Templates are files that display static and dynamic content to users who visit your application. In this step, you will create a HTML template to create a home page for the application.

      Start by creating a home.html file in the app/templates directory:

      • sudo nano app/templates/home.html

      Add the code for your template. This code will create an HTML5 page that contains a title and some text.

      /var/www/TestApp/app/templates/home.html

      
      <!doctype html>
      
      <html lang="en-us">   
        <head>
          <meta charset="utf-8">
          <meta http-equiv="x-ua-compatible" content="ie=edge">
          <title>Welcome home</title>
        </head>
      
        <body>
          <h1>Home Page</h1>
          <p>This is the home page of our application.</p>
        </body> 
      </html>
      

      Save and close the file once you've added your template.

      Next, modify the app/views.py file to serve the newly created file:

      First, add the following line at the beginning of your file to import the render_template method from Flask. This method parses an HTML file to render a web page to the user.

      /var/www/TestApp/app/views.py

      from flask import render_template
      ...
      

      At the end of the file, you will also add a new route to render the template file. This code specifies that users are served the contents of the home.html file whenever they visit the /template route on your application.

      /var/www/TestApp/app/views.py

      ...
      
      @app.route('/template')
      def template():
          return render_template('home.html')
      

      The updated app/views.py file will look like this:

      /var/www/TestApp/app/views.py

      from flask import render_template
      from app import app 
      
      @app.route('/')
      def home():
          return "Hello world!"
      
      @app.route('/template')
      def template():
          return render_template('home.html')
      

      Save and close the file when done.

      In order for these changes to take effect, you will need to stop and restart the Docker containers. Run the following command to rebuild the container:

      • sudo docker stop docker.test && sudo docker start docker.test

      Visit your application at http://your-ip-address:56733/template to see the new template being served.

      homepage

      In this you've created a Docker template file to serve visitors on your application. In the next step you will see how the changes you make to your application can take effect without having to restart the Docker container.

      Step 4 — Updating the Application

      Sometimes you will need to make changes to the application, whether it is installing new requirements, updating the Docker container, or HTML and logic changes. In this section, you will configure touch-reload to make these changes without needing to restart the Docker container.

      Python autoreloading watches the entire file system for changes and refreshes the application when it detects a change. Autoreloading is discouraged in production because it can become resource intensive very quickly. In this step, you will use touch-reload to watch for changes to a particular file and reload when the file is updated or replaced.

      To implement this, start by opening your uwsgi.ini file:

      Next, add the highlighted line to the end of the file:

      /var/www/TestApp/uwsgi.ini

      module = main
      callable = app
      master = true
      touch-reload = /app/uwsgi.ini
      

      This specifies a file that will be modified to trigger an entire application reload. Once you've made the changes, save and close the file.

      To demonstrate this, make a small change to your application. Start by opening your app/views.py file:

      Replace the string returned by the home function:

      /var/www/TestApp/app/views.py

      • from flask import render_template
      • from app import app
      • @app.route('/')
      • def home():
      • return "<b>There has been a change</b>"
      • @app.route('/template')
      • def template():
      • return render_template('home.html')

      Save and close the file after you've made a change.

      Next, if you open your application’s homepage at http://ip-address:56733, you will notice that the changes are not reflected. This is because the condition for reload is a change to the uwsgi.ini file. To reload the application, use touch to activate the condition:

      Reload the application homepage in your browser again. You will find that the application has incorporated the changes:

      Homepage Updated

      In this step, you set up a touch-reload condition to update your application after making changes.

      Conclusion

      In this tutorial, you created and deployed a Flask application to a Docker container. You also configured touch-reload to refresh your application without needing to restart the container.

      With your new application on Docker, you can now scale with ease. To learn more about using Docker, check out their official documentation.



      Source link