Andrew Whipple

The most sensational, inspirational, celebrational, muppetational

How To Debug Docker-ized Python Apps in Visual Studio Code

Published 2/7/23

This is absolutely just a post written for me so I have a reference point to come back to when I forget how to do this in like 2 weeks!


I'm currently a backend engineer, working in Python, using Visual Studio Code as my editor of choice. And at the company I work at, my team has already done the hard work of Docker-izing all of it's applications and leveraging docker-compose files for local development. Additionally, our team is very live-and-let-live when it comes to each team member picking their own personal IDEs and setup, so I use VS Code, others use VS Code + devcontainers, others use Pycharm, others use emacs, etc etc etc.

Fortunately, VS Code provides great debugging tooling for Python and for Docker! Unfortunately, all of my googling so far led to docs about how to create dockerfiles or docker-compose files for use in debugging, and typically for standalone applications. I couldn't find anything to clearly lay out how to start debugging on an already-existing docker-compose file that may contain many different services and containers other than the app you're trying to debug (like dependencies or databases) and that has to be maintained for all the other people working on the application alongside you.

So, that's what this is! It works! Maybe there's a better way, but this is My Way!

1: Install debugpy

debugpy is the Python debugging tool that VS Code uses, so you need to make sure it's installed on the container that will run the application. If you have a requirements file (requirements.in or requirements.txt) include debugpy in that. If you separate out requirements files for local, dev, or test environments separate from your production requirements, I'd highly recommend only including it in your local/dev/test!

Alternatively, make sure that when your Docker container is created you at some point run pip install debugpy prior to starting up your actual application.

2: Expose port 5678

In your docker-compose file, for the service you want to debug, make sure to expose port 5678. This involves adding a line something like this:

services:
  my-cool-service-name:
    ...
    ports:
      # any existing port mappings you have
      - "5678:5678"
...

This tells Docker to expose the container's local 5678 port as port 5678 on the Docker network (which best I can tell will default to localhost)

3: Start the app in the context of debugpy

In your docker-compose file there's probably some command that is given to container on startup and that probably starts the application. At the very least, all the examples I have to deal with do!

If so, one of those commands will probably either be a python command to start the application (ex for django something like python manage.py runserver) or, if you're running a web application, some command to start up a webserver (ex for a FastAPI app using uvicorn, uvicorn app.main:app)

If it's the former, then modify that command to start up debugpy as part of it. If for example it's a django app with the command python manage.py runserver, change that to python -m debugpy --wait-for-client --listen 0.0.0.0:5678 manage.py runserver

This tells python to start up debugpy, have it halt execution of the subsequent script until a debugger is connected, and to listen on port 5678 of host 0.0.0.0

If you aren't already starting your app via a python call, then you can turn it into one with python -m. If for example you have a FastAPI app with a startup command like uvicorn app.main:app then you can modify it to python -m debugpy --wait-for-client --listen 0.0.0.0:5678 -m uvicorn app.main:app

This does the same thing as the django example, except instead of debugpy overseeing the running of a manage.py script, it oversees python running the uvicorn command.

4: Add a launch.json configuration

In .vscode/launch.json add the following to the configurations array:

{
    "configurations": [
        {
            "name": "Attach (remote debug)",
            "type": "python",
            "request": "attach",
            "port": 5678,
            "host": "127.0.0.1",
            "pathMappings": [
                {
                  "localRoot": "${workspaceFolder}",
                  "remoteRoot": "."
                }
              ]
        }
    ]
}

This creates a command for VS Code called "Attach (remote debug)" that will tell it to attach a python debugger to 127.0.0.1:5678. If you've been paying attention, that should mean the debugger is now attaching to the port exposed by the application container that is being listened to by debugpy.

5: Start debugging!

Run docker-compose up to start up your application like normal. It should hang on startup because we passed that --wait-for-client flag in the initial debugpy command.

Then in VS Code go to "Run and Debug" (cmd-shift-D), select "Attach (remote debug)", and press the green triangle.

From there, your debugger should attach to the running container, the rest of the application startup should continue, and you should be able to do all the cool fun stuff the VS Code debugger allows!