A Short But Complete Guide To Python Flask App Development
By: Satyendra Kumar
Tags: Flask, Python, Python3, Flask Framework, Python Programming

Sixth Thing: Setting up actual app, and creating a deployable Docker image.

Here’s a list of things we are going to implement in this part:-

Make the application runnable, so that you can access it from browser, terminal (using curl etc) Connect the app to a real database (MySQL for now) Make the application deployable.

To make the app runnable we need to create an instance of application using create_app function. We do that in a new file <project_root>/autoapp.py and put this there:-

from flask.helpers import get_debug_flag



from app.configs import DevConfig, ProdConfig

from app.main import create_app



CONFIG = DevConfig if get_debug_flag() else ProdConfig



app = create_app(CONFIG)

For this we also need separate config objects which we create in a new file app/configs.py

import os



from dotenv import load_dotenv



load_dotenv()

# This will load .env file containing environment variables





class Config(object):

"""Base Configuration"""

APP_DIR = os.path.abspath(os.path.dirname(__file__)) # This directory

PROJECT_ROOT = os.path.abspath(os.path.join(APP_DIR, os.pardir))





class ProdConfig(Config):

ENV = 'prod'

DEBUG = False

SQLALCHEMY_DATABASE_URI = os.getenv("DB_URL")





class DevConfig(Config):

ENV = 'dev'

DEBUG = True

DB_NAME = 'dev.db'

DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME)

SQLALCHEMY_DATABASE_URI = os.getenv('DB_URL',

'sqlite:///{0}'.format(DB_PATH) # default

)

We have used Python-dotenv to place all environment variables in a file named .env

DB_URL=mysql+mysqlconnector://user:password@localhost:3306/db

Now, the app can be started using commands:-

export FLASK_APP=./autoapp.py

flask run

You can try accessing the URLs, you should see error. We still haven’t setup database.

You can install MySQL on your system, and create a database named db , but for now we are going to use docker image of mysql for that.

Create a file docker-compose.yml in project root directory:-

version: "3"



networks:

backend:

driver: bridge



volumes:

my-db:



services:

mysql:

image: mysql:8.0.22

container_name: mydb

ports:

- "3306:3306"

environment:

MYSQL_DATABASE: 'db'

# So you don't have to use root, but you can if you like

MYSQL_USER: 'user'

# You can use whatever password you like

MYSQL_PASSWORD: 'password'

# Password for root access

MYSQL_ROOT_PASSWORD: 'password'

networks:

- backend

expose:

- '3306'

volumes:

- my-db:/var/lib/mysql

You need DockerCompose installed on your system.

Run the command docker-compose up it would download the docker image of MySQL from DockerHub and create a container (which means the image is started/launched). Now you have a database running which our app can access.

But, if you run the app again and try to access the APIs, it would still throw an error, because we still haven’t created the tables in our database.

To do that we are going to use another library Flask-Migrate

There’s a crucial change that it requires (apart from adding it to requirements/production.txt file):-

We need to create an object of Migrate and connect it to our app, and database.

So, first add this line to app/extensions.py

migrate = Migrate()

Next, add this line to create_app function:-

migrate.init_app(_app, db=database)

Then, we have to use this migration framework to create tables in our database. Run the following commands:-

flask db init

flask db migrate

flask db upgrade

First command creates necessary files (in your project directory) for migration.

Second command detects changes in your application regarding tables, and creates a migration script file. The name of the file would be random hash.

Third command applies the migration script to the database, and creates/modifies tables etc.

(Note: The auto-detection of this framework is not 100% reliable, so make sure to verify the changes in the generated migration script file. For the first time there’s no need to check though, unless you want to customise)

Now if you re-run the application, everything should work fine.

Finally, we need to make the app deployable as a docker image.

Create a file named Dockerfile in your project directory:-

FROM python:3.8-slim-buster

# This will serve as base image on which other things can be added via commands, basically you get a lightweight operating system with Python installed on it



WORKDIR /src

#sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions, a src folder will be created in docker image



ARG BUILD_ENV

ENV FLASK_ENV $BUILD_ENV

ENV FLASK_APP autoapp.py



#RUN mkdir requirements



COPY app/ ./app

COPY autoapp.py .

COPY requirements/ ./requirements

COPY run_tests.sh .

# Copy required source code

COPY tests ./tests



RUN ls .

# just for debugging to see what's ther in the WORKDIR



RUN if [ "$BUILD_ENV" = "test" ]; then pip install -r requirements/development.txt ; fi

RUN if [ "$BUILD_ENV" = "test" ]; then export FLASK_DEBUG=true ; fi

RUN if [ "$BUILD_ENV" != "test" ]; then pip install -r requirements/production.txt ; fi



RUN chmod 777 run_tests.sh

RUN ./run_tests.sh

ENTRYPOINT flask run





# A brief about RUN, CMD, and ENTRYPOINT

# Use RUN to do the installation, making changes etc

# Use ENTRYPOINT to run a command used to start the service

# Use CMD to pass arguments to that command, they get appended automatically; You can override CMD while running docker image.

If you face difficulty understanding Dockerfile visit the official site, documentation is pretty good.

Also, add the following to the docker-compose file we created earlier:-

flask_app:

build:

dockerfile: Dockerfile

context: .

args:

BUILD_ENV: $BUILD_ENV

container_name: flask_app

expose:

- '5000'

environment:

- FLASK_DEBUG=$FLASK_DEBUG

- DB_URL=$DB_URL

ports:

- "5000:5000"

network_mode: bridge

Kill the previous running commands and run the compose command again:-

BUILD_ENV=test docker-compose up --build

Note: You might face difficulty accessing the app running in docker from browser, or terminal on Mac. I still haven’t figured out the problem.

Using the command above builds a docker image of your app and tags it as latest , you can view it using the following command:-

docker images

You can push this image to some repository (Docker Hub for example), and then pull it back on any server (another machine, or a VirtualMachine in Google Cloud for example), and then run the application using docker compose.

To push the image to Docker Hub

docker push sample-flask-project_flask_app:latest

Then, on the server where you want to run, you just have to copy the docker-compose file and comment out build section from it for flask_app, here’s the complete docker-compose.yml file for that:-

version: "3"



networks:

backend:

driver: bridge



volumes:

my-db:



services:

mysql:

image: mysql:8.0.22

container_name: mydb

ports:

- "3306:3306"

environment:

MYSQL_DATABASE: 'db'

# So you don't have to use root, but you can if you like

MYSQL_USER: 'user'

# You can use whatever password you like

MYSQL_PASSWORD: 'password'

# Password for root access

MYSQL_ROOT_PASSWORD: 'password'

networks:

- backend

expose:

- '3306'

volumes:

- my-db:/var/lib/mysql



flask_app:

# build:

# dockerfile: Dockerfile

# context: .

# args:

# BUILD_ENV: $BUILD_ENV

image: sample-flask-project_flask_app:latest

container_name: flask_app

expose:

- '5000'

environment:

- FLASK_DEBUG=$FLASK_DEBUG

- DB_URL=$DB_URL

ports:

- "5000:5000"

network_mode: bridge

Then you just have to run command docker-compose up -d

(-d means it will run in background as a daemon process).

Here’s a list of useful docker commands that you might use:-

# To see all the images on your system

docker images # To build the images (if a build config is specified in docker-compose.yml)

docker-compose build --no-cache # To login to a running container, let's say we wan to see our database in mysql container named 'mydb'

docker exec -it mydb /bin/bash

# then you can run mysql command i.e mysql -u user -p

Here’s the complete code so far.