Docker

Running a Python App in a Docker Container

Hello in this tutorial, we will explain how to run a Python app in a Docker container. More specifically, we will create a python Flask application with SQLite and deploy it on Docker.

1. Introduction

SQLite is a software library that provides a relational database management system. It is lightweight in terms of setup, database administration, and required resources. It is self-contained, serverless, zero-configuration, transactional.

  • Self-contained means that it require minimal support from the operating system or any external library
  • Zero-configuration means that no external installation is required before using it
  • Transactional means it is fully ACID-compliant i.e. all queries and changes are atomic, consistent, isolated, and durable

1.1 Setting up Python

If someone needs to go through the Python installation on Windows, please watch this link. You can download the Python from this link.

1.2 What is Docker and Setting up Docker

In the present world, Docker is an important term,

  • Often used in CI/CD platform that packages and runs the application with its dependencies inside a container
  • Is a standard for Linux Containers
  • A Container is a runtime that runs under any Linux kernel and provides a private machine-like space under Linux

If someone needs to go through the Docker installation, please watch this video.

2. Running a Python App in a Docker Container

I am using JetBrains PyCharm as my preferred IDE. You’re free to choose the IDE of your choice. Fig. 1 represents the project structure for this tutorial.

docker python - app structure
Fig. 1: Application structure

The file named – songs.db will be generated dynamically during the application run.

2.1 Creating a requirements file

Add the below code to the requirements file. The file will be responsible to download and install the packages required for this tutorial. As sqlite is an in-memory database and is already available in the python package hence we don’t need to install it explicitly.

requirements.txt

Faker==10.0.0
Flask==1.1.4

2.2 Creating the database config

Create the database configuration file. The file will be responsible for handling the SQLite database connection and interacting with the songs database.

db.py

import sqlite3
from sqlite3 import Error

DATABASE_NAME = "songs.db"


def get_db():
    """ create a database connection to a SQLite database """
    try:
        conn = sqlite3.connect(DATABASE_NAME)
        return conn
    except Error as e:
        print(e)


def create_table():
    tables = [
        """
        CREATE TABLE IF NOT EXISTS songs(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, singer TEXT NOT NULL)
        """
    ]
    db = get_db()
    cursor = db.cursor()
    for table in tables:
        cursor.execute(table)

2.3 Creating the controller class

Create the controller class responsible for interacting with the database. The controller class methods are responsible for performing the SQL CRUD operations.

controller.py

from db import get_db


def get_row_count():
    return len(get_all())


def insert_song(name, singer):
    db = get_db()
    cursor = db.cursor()
    sql = "INSERT INTO songs(name, singer) VALUES (?, ?)"
    cursor.execute(sql, [name, singer])
    db.commit()
    return True


def get_by_id(key):
    db = get_db()
    cursor = db.cursor()
    sql = "SELECT id, name, singer FROM songs WHERE id = ?"
    cursor.execute(sql, [key])
    return cursor.fetchone()


def get_all():
    db = get_db()
    cursor = db.cursor()
    sql = "SELECT id, name, singer FROM songs"
    cursor.execute(sql)
    return cursor.fetchall()


def delete_by_id(key):
    db = get_db()
    cursor = db.cursor()

    item = get_by_id(key)
    if item is None:
        return False

    sql = "DELETE FROM songs WHERE id = ?"
    cursor.execute(sql, [key])
    db.commit()
    return True


def update_by_id(key, name, singer):
    db = get_db()
    cursor = db.cursor()

    item = get_by_id(key)
    if item is None:
        return False

    sql = "UPDATE songs SET name = ?, singer = ? WHERE id = ?"
    cursor.execute(sql, [name, singer, key])
    db.commit()
    return True

2.4 Creating the application class

Create the main class responsible for handling the incoming requests from the client and interact with the database to show the results.

main.py

from faker import Faker
from flask import Flask, jsonify, request

import controller
from db import create_table

app = Flask(__name__)
faker = Faker()

RESOURCE_NOT_FOUND = "RESOURCE_NOT_FOUND"


# http get endpoint= http://localhost:8000/song/all
@app.route("/song/all", methods=["GET"])
def get_all():
    songs = controller.get_all()
    items = []
    for song in songs:
        items.append({"id": song[0], "name": song[1], "singer": song[2]})

    return jsonify({"songs": items})


# http get endpoint= http://localhost:8000/song/1
@app.route("/song/", methods=["GET"])
def get_by_id(key):
    song = controller.get_by_id(key)
    if song is None:
        return jsonify({"msg": RESOURCE_NOT_FOUND})

    return jsonify({"id": song[0], "name": song[1], "singer": song[2]})


# http delete endpoint= http://localhost:8000/song/1
@app.route("/song/", methods=["DELETE"])
def delete_by_id(key):
    result = controller.delete_by_id(key)
    if not result:
        return jsonify({"msg": RESOURCE_NOT_FOUND})

    return jsonify(result)


# http put endpoint= http://localhost:8000/song/1
@app.route("/song/", methods=["PUT"])
def update_by_id(key):
    details = request.get_json()
    name = details["name"]
    singer = details["singer"]
    result = controller.update_by_id(key, name, singer)
    if not result:
        return jsonify({"msg": RESOURCE_NOT_FOUND})

    return jsonify(result)


if __name__ == '__main__':
    create_table()
    print("Table created")

    if controller.get_row_count() == 0:
        for x in range(1, 6):
            controller.insert_song(faker.word(), faker.name())

        print("Sample records inserted")
    else:
        print("Skipping insert")

    server_port = os.environ.get('PORT_NUMBER', '8000')
    app.run(debug=False, port=server_port, host='0.0.0.0')

3. Setting up requirements for Docker

To deploy this application on Docker we will create two files responsible for having this application up and running in a container. We will also use the .dockerignore file so that we only add the required files to the docker image and the same file can be downloaded from the Downloads section.

3.1 Creating a Dockerfile

Add the below code to the Dockerfile responsible for creating the Docker image. The image name will be driven from the docker-compose.yml file which will be created in the next step.

Dockerfile

# Python image to use
FROM python:3.10-slim

# Author of the image
MAINTAINER geeks@helloworld.com

# Set the working directory to /app
WORKDIR /app

# Copy the requirements file used for dependencies
COPY requirements.txt .

# Install the needed packages specified in requirements.txt
RUN pip install --no-cache-dir -r requirements.txt

# Copy rest of the working directory contents into the container at /app
COPY . .

# Run app.py when the container launches
ENTRYPOINT ["python3", "main.py"]

3.2 Creating the docker-compose yml

Add the below code to the docker-compose file responsible for bundling the code, creating the docker image, and starting the container on a port number – 8000 from the created image. You are free to change the details as per your wish but remember that if you change the port number hit the application endpoints on the same port. For this tutorial, I am setting the port number to 8000.

Dockerfile

version: "3.7"

services:
  helloworld:
    build:
      context: .
      dockerfile: Dockerfile
    image: "pyflasksqliteassignment:latest"
    container_name: "pyflasksqliteassignment"
    ports:
      - "8000:8000"

4. Run the application

If you working on the IDE and want to run the application. Simply head over to the main.py python script and run it. But if you would like to run the application on the docker container follow the below steps. Open the terminal and navigate to the project directory containing the Dockerfile.

App run

-- To build the docker image and start the container –
docker-compose up -d --build

-- To stop the remove the container –
docker-compose down

-- To verify that the container is running successfully or not –
docker ps -a

Once the up command is successful you can hit the endpoints on a port number – 8000 to confirm that the application is running fine. To stop the application from running on docker and automatically remove the container fire the down command.

5. Demo

To play around with the application endpoints open up the postman tool and hit the endpoints.

Application endpoints

-- get all songs
-- http get method
http://localhost:8000/song/all

-- get a song by id
-- http get method
http://localhost:8000/song/1

-- delete song by id
-- http delete method
http://localhost:8000/song/1

-- update song by id
-- http put method
http://localhost:8000/song/1

That is all for this tutorial and I hope the article served you with whatever you were looking for. Happy Learning and do not forget to share!

6. Summary

In this tutorial, we learned about the sqlite implementation in a python application and hosted the same app on docker.

We tried to build the python application on the flask framework thereby giving the reader a flavor of how to use the different rest api verbs like put, get, and delete. We haven’t exposed the http post endpoint as during the application start we are injecting the data however you are free to play with the application as per your wish.

You can download the source code of this tutorial from the Downloads section.

7. Download the Project

This was a tutorial on how to run a python application in a Docker container.

Download
You can download the full source code of this example here: Running a Python App in a Docker Container

Yatin

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button