Table of contents
Text Link

Building and Deploying a Flask To-Do App with Docker, MongoDB, and Redis

In this article, we will guide you through creating a simple To-Do application using Flask, MongoDB, and Redis. We will then Dockerize the application to make it easy to deploy and run consistently across different environments. Let's start!

Prerequisites

  • Docker installed on your system
  • Basic knowledge of Python and Flask
  • Basic understanding of Docker and Docker Compose

Project Structure

Here’s the project structure we will follow:

todo-app/
├── app/
│   ├── templates/
│   │   └── index.html
│   ├── static/
│   │   └── style.css
│   ├── app.py
│   ├── requirements.txt
│   └── Dockerfile
└── docker-compose.yml

Setting Up the Flask Application

First, create the Flask application.

app/app.py:

from bson import ObjectId
from flask import Flask, render_template, request, redirect, url_for
import os
import pymongo
import redis

app = Flask(__name__)

redis_url = os.getenv("REDIS_URL", "redis://localhost:6379")
redis_connection = redis.from_url(redis_url)

mongo_url = os.getenv("MONGO_URL", "mongodb://root:root@localhost:27017")
mongo_client = pymongo.MongoClient(mongo_url)
mongo_db = mongo_client["TaskManagerDB"]
mongo_collection = mongo_db["Tasks"]

@app.route('/')
def index():
    ip = request.remote_addr
    todo_list = mongo_collection.find({'ip': ip})
    return render_template('index.html', todo_list=todo_list)

@app.route('/add', methods=['POST'])
def add():
    ip = request.remote_addr
    task = request.form.get('task')
    if task:
        mongo_collection.insert_one({'task': task, "ip": ip})
    return redirect(url_for('index'))

@app.route('/delete/<task_id>')
def delete(task_id):
    try:
        task_id = ObjectId(task_id)
    except Exception as e:
        print("Invalid task_id:", e)
    result = mongo_collection.delete_one({'_id': task_id})
    if result.deleted_count == 1:
        print("Task deleted successfully.")
    else:
        print("Task not found or an error occurred.")
    return redirect(url_for('index'))

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080)

app/templates/index.html:


<!DOCTYPE html>
<html>
<head>
    <title>To-Do List</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
    <div class="container">
        <h1>To-Do List</h1>
        <form class="add-task-form" method="POST" action="/add">
            <input type="text" name="task" placeholder="Add a new task" required>
            <button type="submit">Add</button>
        </form>
        <ul class="todo-list">
            {% for task in todo_list %}
                <li>
                    {{ task.task }}
                    <a href="/delete/{{ task._id }}" class="delete-task"><i class="fa fa-trash" aria-hidden="true"></i></a>
                </li>
            {% endfor %}
        </ul>
    </div>
</body>
</html>

app/static/style.css:

body, h1, ul {
    margin: 0;
    padding: 0;
}

body {
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
}

.container {
    max-width: 400px;
    margin: 0 auto;
    padding: 70px;
    background-color: #ffffff;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

h1 {
    text-align: center;
    margin-bottom: 20px;
    color: #333;
}

.add-task-form {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #f0f0f0;
    padding: 10px;
    border-radius: 5px;
}

.add-task-form input[type="text"] {
    flex-grow: 1;
    padding: 8px;
    border: none;
    border-radius: 3px;
    font-size: 16px;
}

.add-task-form button {
    background-color: #007bff;
    color: #fff;
    border: none;
    border-radius: 3px;
    padding: 8px 15px;
    cursor: pointer;
    font-size: 16px;
    transition: background-color 0.2s;
}

.add-task-form button:hover {
    background-color: #0056b3;
}

.todo-list {
    list-style-type: none;
    padding: 0;
    margin-top: 20px;
}

.todo-list li {
    background-color: #ffffff;
    border: 1px solid #ddd;
    margin-bottom: 10px;
    padding: 10px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-radius: 3px;
    transition: box-shadow 0.2s;
}

.todo-list li:hover {
    box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}

.delete-task {
    text-decoration: none;
    color: #ff0000;
    margin-left: 10px;
    padding: 2px 5px;
    border: 1px solid #ff0000;
    border-radius: 3px;
}

.delete-task:hover {
    background-color: #ff0000;
    color: #fff;
}

app/requirements.txt:

Flask==2.0.2
redis==3.5.3
pymongo==3.12.0
Werkzeug==2.0.2

Create the Dockerfile

app/Dockerfile:

FROM python:3.9.7-alpine3.14

ENV TODO /todo-list
WORKDIR $TODO

COPY . .
RUN pip install --upgrade pip
RUN pip install -r requirements.txt

ENTRYPOINT ["python"]
CMD ["app.py"]

Create the Docker Compose Configuration

docker-compose.yml:

version: "3"
services:
  webapp:
    build: ./app
    container_name: "webapp"
    ports:
      - "8080:8080"
    environment:
      REDIS_URL: redis://redis:6379
      MONGO_URL: mongodb://mongo:27017
    depends_on:
      - redis
      - mongo

  redis:
    image: "redis:6.2.6-buster"
    ports:
      - "6379:6379"

  mongo:
    image: "mongo:4.4-rc-focal"
    ports:
      - "27017:27017"

Building and Running the Docker Containers

Now that we have everything set up, let's build and run our Docker containers.

1. Navigate to the project root directory (todo-app) and build the Docker images:

docker-compose build

2. Start the Docker containers:

docker-compose up

This command will build the Docker images for your Flask app, MongoDB, and Redis, and start the containers. You should see an output indicating that each service is starting.

3. Access the Flask application:

Open your browser and navigate to http://localhost:8080/. You should see the To-Do application interface. Congratulations 🎉

Docker Commands Overview

Here are some basic Docker commands you might find useful:

Build Docker images:

docker-compose build

Start Docker containers:

docker-compose up

Stop Docker containers:

docker-compose down

Building and Pushing the Docker Image to Docker Hub

After creating your Dockerfile and docker-compose.yml, the next step is to build the Docker image and push it to your Docker Hub registry. Follow these steps:

1. Log in to Docker Hub:

docker login

You will be prompted to enter your Docker Hub username and password.

2. Build the Docker Image:

Navigate to the directory containing your Dockerfile (app directory) and build the Docker image. Replace ghadeer99/todo-app with your Docker Hub username and desired repository name.

docker build -t ghadeer99/todo-app:latest .

3. Tag the Docker Image:

docker tag ghadeer99/todo-app:latest ghadeer99/todo-app:v1.0

4. Push the Docker Image to Docker Hub:

docker push ghadeer99/todo-app:latest

If you tagged your image, push the tagged version as well:

docker push ghadeer99/todo-app:v1.0

Full Dockerfile and Docker Compose Workflow

Here's a full walkthrough of building, tagging, and pushing your Docker image:

1. Build the Docker Image:

cd app
docker build -t ghadeer99/todo-app:latest .

2. Tag the Docker Image:

docker tag ghadeer99/todo-app:latest ghadeer99/todo-app:v1.0

3. Log in to Docker Hub:

docker login

3. Push the Docker Image:

docker push ghadeer99/todo-app:latest
docker push ghadeer99/todo-app:v1.0

Final Docker Compose Configuration

Make sure your docker-compose.yml is configured to use the image you pushed to Docker Hub:

docker-compose.yml:

version: "3"
services:
  webapp:
    image: "ghadeer99/todo-app:latest"
    container_name: "webapp"
    ports:
      - "8080:8080"
    environment:
      REDIS_URL: redis://redis:6379
      MONGO_URL: mongodb://root:root@mongo:27017
    depends_on:
      - redis
      - mongo

  redis:
    image: "redis:6.2.6-buster"
    ports:
      - "6379:6379"

  mongo:
    image: "mongo:4.4-rc-focal"
    ports:
      - "27017:27017"

Running the Docker Compose

Now you can run your application using Docker Compose with the image from Docker Hub:

docker-compose up

This command will pull the image from Docker Hub and start the containers.

Share this article
Get more articles
like this
Thank you! Your submission has been received!
Oops! Something went wrong.

Create a free account to access the full topic

Wide range of learning tracks for beginners and experienced developers
Study at your own pace with your personal study plan
Focus on practice and real-world experience
Andrei Maftei
It has all the necessary theory, lots of practice, and projects of different levels. I haven't skipped any of the 3000+ coding exercises.