Dockerize a Flask app and debug with VSCode
Tags: vscode, flask, docker, debug
Published: 2021-03-02 04:23:04

---
title: Dockerize a Flask app and debug with VSCode
published: true
description: Setup a dockerized Flask app and debug with VSCode
tags: VSCode, flask, docker, debug
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mqnu9sbfn9cguhbqyne0.jpg
---


## Introduction

If you had tried setting up the VSCode debugger with docker before, you might have seen that it can be a bit more complicated than it seems at first. So in this tutorial, I want to share a way to set up a development environment with Flask, Docker and VSCode.

You can find the source code [here](https://github.com/pachecoio/flask-api).


## Project setup


Let's start by creating the initial folders and installing the necessary dependencies:

```bash
mkdir flask-api
cd flask-api
python -m venv venv
```
Once created the files and initiated the virtual env, let's start the virtual env and install the necessary packages:

For Windows users:
```bash
./venv/Scripts/activate
```
For Mac and Linux users:
```
source venv/bin/activate
```
> The virtual env is used to isolate the installation of the packages, so whenever you try to install anything with pip these new dependencies are added in the `lib` folder inside `venv`.

With this we can go ahead and install the necessary dependencies:

```bash
pip install flask black
pip freeze > requirements.txt
```


Now we can create a basic flask api, starting by defining the base folder and some simple routes.
Let's open the `flask-api` project with VSCode and create a package called `api` at the root of the project:

```
code flask-api
mkdir api
touch api/__init__.py
```

As we installed black as our file formatter, let's setup a configuration to let VSCode make use of it. Create a `.vscode` folder add a file called `settings.json` with the following content:

```json
// .vscode/settings.json
{
    "python.formatting.provider": "black"
}
```


At this point, your project structure should look like the following:
![Initial setup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oh0iocw2bpxyoyfj49m8.png)


We can now include the content of the `__init__.py` file:

```py
from flask import Flask, jsonify


def create_app():
    app = Flask(__name__)

    @app.route("/api/test", methods=["GET"])
    def sample_route():
        return jsonify({"message": "This is a sample route"})

    return app
```

> Here we have a simple method that creates a flask app and includes a sample route. This method can be useful for extending this and mocking it in the future for tests.

Include a file called `app.py` at the root of the project with the following content:
```py
from api import create_app

app = create_app()

if __name__ == "__main__":
    app.run(
        debug=True,
        host='0.0.0.0',
        port=5000
    )
```
> This file will be the entry point of our app.

At this point, we are able to press `F5` in VSCode, select python Flask and use app.py as the entrypoint. The application should run successfully.
You can check the results in the URL: `http://127.0.0.1:5000/api/test`

## Dockerize the app

We already have an application up and running, so now let's stop it and get started with dockerizing it.

The first step is to create our `Dockerfile` at the root of the project with the content:

```Dockerfile
FROM python:3.6 as base
# Base image to be reused
LABEL maintainer "Thiago Pacheco <hi@pacheco.io>"
RUN apt-get update
WORKDIR /usr/src/app
COPY ./requirements.txt ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
ENV FLASK_ENV="docker"
ENV FLASK_APP=app.py
EXPOSE 5000

FROM base as debug
# Debug image reusing the base
# Install dev dependencies for debugging
RUN pip install debugpy
# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1

FROM base as prod
# Production image
RUN pip install gunicorn
COPY . .
CMD ["gunicorn", "--reload", "--bind", "0.0.0.0:5000", "app:app"]
```

> This Dockerfile takes care of creating three image targets.
> The `base` is where we put the necessary config for all the following targets. Here we create the work directory folder, install all the project dependencies, and set up the flask environment variables.
> The `debug` will be used in the docker-compose.yaml file to setup our dev environment and it covers installing the debugpy dependency and the dev environment variables.
> And finally the `prod` target will be used for the production release, which will cover installing gunicorn, copying all the project files, and setting up the entry point.


Create a `docker-compose.yaml` file with the following content:

```yaml
version: "3.7"

services:
  flask-api:
    image: thisk8brd/flask-api
    container_name: flask-api
    build:
      context: .
      target: debug
    ports:
      - 5000:5000
      - 5678:5678
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
    entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app",  "--wait-for-client", "--multiprocess", "-m", "flask", "run", "-h", "0.0.0.0", "-p", "5000" ]
    networks:
      - flask-api

networks:
  flask-api:
    name: flask-api

```

With this `docker-compose.yaml` file we are configuring the `flask-api` service. Let's break this down to explain it:

- Lines 5 to 9
```yaml
    image: thisk8brd/flask-api
    container_name: flask-api
    build:
      context: .
      target: debug
```
> Here we define that we want to build the image based on our Dockerfile config and set the target as debug.

- Lines 10 to 12
```yaml
    ports:
      - 5000:5000
      - 5678:5678
```
> Define the exposed ports, where `5000` is the application port and `5678` is de debug port to connect with vscode.

- Lines 13 and 14
```yaml
    volumes:
      - .:/usr/src/app
```
> Here we point the container working dir to our local project dir, which allows us to use the flask hot-reload functionality.

- Line 18
```yaml
entrypoint: [ "python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "app",  "--wait-for-client", "--multiprocess", "-m", "flask", "run", "-h", "0.0.0.0", "-p", "5000" ]
```

> This is a very important one!
> In this line, we run a multiprocess script where first we run the `debugpy` that starts listening to the port `5678` and after this, we run the flask application. This listener is used to connect the container with the VSCode and use its debug functionality.

Now we are already able to run the application.

```bash
docker-compose up -d
```

The application should be up and running at `http://localhost:5000/api/test`


## Setup vscode debug

We are almost done with the setup.
Now for the last step, we can create the VSCode launch script to connect with our container.

Create a file called `launch.json` under the `.vscode` folder with the following content:

```json
{
  "configurations": [
    {
      "name": "Python: Remote Attach",
      "type": "python",
      "request": "attach",
      "port": 5678,
      "host": "0.0.0.0",
      "pathMappings": [
        {
          "localRoot": "${workspaceFolder}",
          "remoteRoot": "/usr/src/app"
        }
      ]
    }
  ]
}
```

With your application running, just press `F5` in the VSCode. You should get the debug menu at the top of your editor and be able to start coding and debugging.


Go to your `api/__init__.py` file and add a breakpoint in the return of the `create_app` method, like so:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c5lqqkbwxnhxt49mquj5.png)


Now if you try to access the URL `http://localhost:5000/api/test` it should stop in the breakpoint you included.
![Debug vscode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zpnwrlvdp3uuy7bnxhb1.png)

Yay, we did it!

![Chandler success](https://media.giphy.com/media/JPsFUPp3vLS5q/giphy.gif)

Now we are able to debug and use the hot-reload functionality while working on a dockerized Flask application!

If you find some issues with the hot-reload functionality, you may need to disable the `Uncaught Exceptions` checkbox under the Run tab in VSCode, in the Breakpoints section.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kx1v06h9xe83m7547w2r.png)


I hope you may find this tutorial useful. Let me know in the comments if that helped you or if you have any doubts.

You can find the source code [here](https://github.com/pachecoio/flask-api).