Debugging a Poetry virtualenv inside a DevContainer

Poetry creates its virtualenv in a cache directory by default, so when VS Code opens a DevContainer it often selects the system Python instead of the project environment — breakpoints never bind, imports show as unresolved, and python -m pytest works in the terminal but not the debugger. This page makes the virtualenv path deterministic and points the editor at it so debugging works on the first attach. It builds on the Python DevContainer Setup with Poetry & venv cluster.

Prerequisites

  • Poetry 1.8+ and Python 3.11+ in the container
  • The VS Code Python extension installed via customizations.vscode.extensions
  • A pyproject.toml with your dependencies

How-To Steps

  1. Force Poetry to create the virtualenv in the project directory so its path is predictable and survives editor restarts.

    {
      "image": "mcr.microsoft.com/devcontainers/python:3.11@sha256:5d1a...",
      "containerEnv": { "POETRY_VIRTUALENVS_IN_PROJECT": "true" },
      "postCreateCommand": "poetry install --no-interaction",
      "remoteUser": "vscode"
    }
    

    Verify: poetry env info --path should print /workspace/.venv.

  2. Point the Python extension at the in-project interpreter so it stops guessing:

    {
      "customizations": {
        "vscode": {
          "extensions": ["ms-python.python", "ms-python.debugpy"],
          "settings": {
            "python.defaultInterpreterPath": "${containerWorkspaceFolder}/.venv/bin/python",
            "python.terminal.activateEnvironment": true
          }
        }
      }
    }
    
  3. Add a debug configuration that uses the same interpreter. Create .vscode/launch.json:

    {
      "version": "0.2.0",
      "configurations": [
        {
          "name": "Debug pytest",
          "type": "debugpy",
          "request": "launch",
          "module": "pytest",
          "args": ["-x", "tests"],
          "console": "integratedTerminal",
          "justMyCode": false
        }
      ]
    }
    

    Verify: set a breakpoint and run the config — it should bind, not show a hollow red dot.

  4. Confirm the interpreter the extension resolved matches Poetry’s:

    poetry run which python
    # Compare with the Python extension's selected interpreter in the status bar
    

Common Pitfalls

SymptomRoot CauseRemediation
Breakpoints stay hollowDebugger using system Python, not the venvSet python.defaultInterpreterPath to .venv/bin/python
.venv missing after rebuildVirtualenv stored in the global cache, wiped on rebuildSet POETRY_VIRTUALENVS_IN_PROJECT=true
Imports unresolved in editor onlyExtension indexed before poetry install finishedRe-run “Python: Select Interpreter” after postCreateCommand
justMyCode hides library framesDefault debugpy settingSet "justMyCode": false in the launch config

Conclusion

The invariant: the virtualenv must live at a path the editor can name statically, and the Python extension must be told that exact path. Pin POETRY_VIRTUALENVS_IN_PROJECT=true and python.defaultInterpreterPath, and the debugger binds deterministically on every attach.

FAQ

Why does the terminal find the right Python but the debugger doesn’t? The terminal activates the venv via Poetry’s shell hooks; the debugger uses python.defaultInterpreterPath independently. Set that path explicitly so both agree.

Should I commit .venv? No. Keep it in the project for path predictability but gitignore it — it is rebuilt by poetry install in postCreateCommand.