This example will build a basic REST server based on Flask. Flask is a “micro” web application framework written in Python. It allows writing simple and lightweight REST APIs.

The server we will design will handle a set of sensors, their creation, visualization, and update using an SQLite database.

We’ll do this by first pulling together the components for a REST based server built with Python Flask, then dockerizing it by writing a Dockerfile. Finally, we’ll build the image, and then run it.

The Python Flask app

We have to create the following files:

File api.py

#!/usr/bin/python
import sqlite3
from flask import Flask, request, jsonify

def connect_to_db():
    conn = sqlite3.connect('database.db')
    return conn

def insert_sensor(sensor):
    inserted_sensor = {}
    try:
        conn = connect_to_db()
        conn.row_factory = sqlite3.Row
        cur = conn.cursor()
        cur.execute("INSERT INTO sensors (tstamp, label, value, unity) VALUES (?, ?, ?, ?)", 
                    (sensor['tstamp'], sensor['label'], sensor['value'], sensor['unity']) )
        conn.commit()
        inserted_sensor = get_sensor_by_id(cur.lastrowid)
    except:
        conn().rollback()
    finally:
        conn.close()
    return inserted_sensor

def get_sensors():
    sensors = []
    try:
        conn = connect_to_db()
        conn.row_factory = sqlite3.Row
        cur = conn.cursor()
        cur.execute("SELECT * FROM sensors")
        rows = cur.fetchall()

        # convert row objects to dictionary
        for i in rows:
            sensor = {}
            sensor["label"]  = i["label"]
            sensor["value"]  = i["value"]
            sensor["unity"]  = i["unity"]
            sensor["tstamp"] = i["tstamp"]
            sensors.append(sensor)
    except:
        sensors = []
    return sensors

def get_sensor_by_id(sensor_id):
    sensors = []
    try:
        conn = connect_to_db()
        conn.row_factory = sqlite3.Row
        cur = conn.cursor()
        cur.execute("SELECT * FROM sensors WHERE label = ?", (sensor_id,))
        rows = cur.fetchall()

        # convert row objects to dictionary
        for i in rows:
            sensor = {}
            sensor["label"]  = i["label"]
            sensor["value"]  = i["value"]
            sensor["unity"]  = i["unity"]
            sensor["tstamp"] = i["tstamp"]
            sensors.append(sensor)
    except:
        sensors = []
    return sensors

def create_db_table():
    initsensors = [
    {
        "tstamp": "2009-06-15T13:45:30",
        "label": "arduino_nano33",
        "value": 25,
        "unity": "Celsius degrees"
    },
    {
        "tstamp": "2010-06-15T13:45:30",
        "label": "portenta_h7",
        "value": 0.123,
        "unity": "mWatts"
    },
    {
        "tstamp": "2011-06-15T13:45:30",
        "label": "raspberry4",
        "value": 4,
        "unity": "roentgen"
    }
    ]

    try:
        conn = connect_to_db()
        conn.execute('''DROP TABLE sensors''')
        print("connected to db successfully")
    except:
        print("well... almost")
    try:
        conn.execute('''
            CREATE TABLE sensors (
                tstamp TEXT PRIMARY KEY NOT NULL,
                label  TEXT NOT NULL,
                value  REAL NOT NULL,
                unity  TEXT NOT NULL
            );
        ''')

        conn.commit()
        print("sensor table created successfully")
    except:
        print("sensor table creation failed - CREATE TABLE sensors")
    finally:
        conn.close()

    for i in initsensors:
        insert_sensor(i)

if __name__ == "__main__":

    app = Flask(__name__)

    @app.route('/api/initdb', methods=['GET'])
    def api_initdb():
        create_db_table()
        return jsonify(get_sensors())

    @app.route('/api/sensors', methods=['GET'])
    def api_get_sensors():
        return jsonify(get_sensors())

    @app.route('/api/sensors/<id>', methods=['GET'])
    def api_get_sensor(id):
        return jsonify(get_sensor_by_id(id))

    @app.route('/api/sensors/add',  methods = ['POST'])
    def api_add_sensor():
        sensor = request.get_json()
        return jsonify(insert_sensor(sensor))

    #app.debug = True
    app.run(host="0.0.0.0")

the “Dockerfile”

We want to create a Docker image with this web app. As mentioned above, all user images are based on a base image. Since our application is written in Python, we will build our own Python image based on Alpine.

The Dockerfile looks like this:

FROM alpine

RUN apk update && apk add --no-cache python3 py3-pip

RUN apk add py3-flask

WORKDIR /home/

COPY api.py .

EXPOSE 5000

CMD ["python3", "/home/api.py"]

Building the image

Now that we have the Dockerfile, we can build the image. The docker build command does the heavy-lifting of creating a docker image from a Dockerfile.

When you run the docker build command given below, make sure to replace <YOUR_USERNAME> with your Docker username. This username should be the same one you created when registering on Docker Hub.

The docker build command is quite simple - it takes an optional tag name with the -t flag, and the location of the directory containing the Dockerfile - the . indicates the current directory:

docker build -t <YOUR_USERNAME>/restapp .

the generated output is something similar to: