2025, Dec 31 05:00

Solve 'No module named django' in Docker: PDM, Docker Compose hot reload, and the .venv volume fix

Troubleshooting Django in Docker with PDM and Docker Compose hot reload: fix 'No module named django' by isolating dependencies in a named .venv volume. Guide.

Running Django in Docker with hot reload sounds straightforward until your app starts up and Python suddenly can’t find Django. If your stack uses PDM for dependency management and Compose for development, a bind mount of the source tree can unintentionally shadow your container’s environment. The result is the dreaded “No module named 'django'” at runtime, even though the image built cleanly.

Minimal setup that reproduces the issue

The development setup relies on Docker Compose for mounting the codebase into the container to enable hot reload and debugging. The service builds fine, but starting it with detached Compose reveals a dependency resolution problem.

services:
  svc:
    build:
      dockerfile: Dockerfile
    command: pdm run python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    volumes:
      - .:/app
    env_file:
      - .env

The Dockerfile installs PDM and project dependencies during build:

FROM python:3.13.2-slim-bullseye

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

RUN apt-get update && apt-get install -y \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir pdm

COPY . /app

ARG BUILD_ENV=prod

RUN pdm install --prod --no-lock --no-editable

On container startup the logs look like this:

INFO: The saved Python interpreter does not exist or broken. Trying to find another one. INFO: __pypackages__ is detected, using the PEP 582 mode

ModuleNotFoundError: No module named 'django'

What’s going on

The image builds with dependencies installed via PDM, but the Compose bind mount replaces the container’s /app directory at runtime. That effectively hides whatever environment PDM prepared during the build. The message about PEP 582 and the missing interpreter aligns with this behavior: PDM tries to use what it finds under the mounted project path, but Django is not available there, so Python cannot import it.

The fix

The reliable way to prevent the bind mount from overwriting the container’s environment is to keep PDM strictly in the build stage and isolate the runtime environment path from the source tree mount. In practice, that means using PDM only during build (a multistage approach) and mapping a separate, named volume to the container’s .venv directory so it survives the bind mount of the codebase.

With this change, hot reload remains intact, and the container continues to see the installed dependencies at runtime. Here is the adjusted Compose configuration:

services:
  svc:
    build:
      dockerfile: Dockerfile
    command: pdm run python manage.py runserver 0.0.0.0:8000
    ports:
      - 8000:8000
    volumes:
      - .:/app
      - siteenv:/app/.venv
    env_file:
      - .env

volumes:
  siteenv:

This keeps the code mount for development while making sure the container’s virtual environment is stored separately and not replaced by the host filesystem.

Why this matters

Binding the project folder into the container is convenient for Django hot reload, but it can unintentionally mask the runtime environment prepared during the image build. When PDM signals PEP 582 mode and Python can’t import Django, it’s a strong hint that the container’s installed dependencies aren’t the ones being used. Decoupling the environment from the code mount preserves repeatable builds and predictable runtime behavior, without sacrificing the developer experience.

Takeaways

Keep PDM as a build-time tool and avoid letting a bind mount override the container’s environment. Use a dedicated, named volume for /app/.venv so your Django app consistently sees the dependencies installed at build time, while your source code remains hot-reloadable. This small structural change eliminates the “No module named 'django'” surprise and keeps Docker, PDM, and Django working together smoothly during development.