Docker Python ModuleNotFoundError: No module named in a container (tested)

When a Python app works locally but the container says ModuleNotFoundError: No module named ..., the build succeeded and the import failed at start-up. There are three usual reasons: the code was never copied into the image, the package was installed for a different Python, or the import root is not on sys.path. All three are reproduced below with a fix that was run.

1. Reproduce: three Dockerfiles that build fine and fail at run time

Three tiny projects. c1 has main.py next to a package app/; c3 imports six installed in a virtualenv in a multi-stage build; c4 uses a src/ layout with src/app/main.py importing app.util. Each has a broken Dockerfile and one or two fixed ones:

# ---- c1/Dockerfile
FROM python:3-alpine
WORKDIR /code
COPY main.py .
CMD ["python", "main.py"]
# ---- c1/Dockerfile.fixed
FROM python:3-alpine
WORKDIR /code
COPY . .
CMD ["python", "main.py"]
# ---- c3/Dockerfile
FROM python:3-alpine AS build
RUN python -m venv /opt/venv
COPY requirements.txt .
RUN /opt/venv/bin/pip install -q -r requirements.txt

FROM python:3-alpine
COPY --from=build /opt/venv /opt/venv
WORKDIR /code
COPY main.py .
CMD ["python", "main.py"]
# ---- c3/Dockerfile.fixed
FROM python:3-alpine AS build
RUN python -m venv /opt/venv
COPY requirements.txt .
RUN /opt/venv/bin/pip install -q -r requirements.txt

FROM python:3-alpine
COPY --from=build /opt/venv /opt/venv
WORKDIR /code
COPY main.py .
ENV PATH="/opt/venv/bin:$PATH"
CMD ["python", "main.py"]
# ---- c4/Dockerfile
FROM python:3-alpine
WORKDIR /code
COPY src/ src/
CMD ["python", "src/app/main.py"]
# ---- c4/Dockerfile.pythonpath
FROM python:3-alpine
WORKDIR /code
COPY src/ src/
ENV PYTHONPATH=/code/src
CMD ["python", "src/app/main.py"]
# ---- c4/Dockerfile.module
FROM python:3-alpine
WORKDIR /code/src
COPY src/ .
CMD ["python", "-m", "app.main"]

run.sh builds and runs each one and prints the last lines:

#!/bin/sh
# build each variant, run it, show the last lines of output
t() { dir=$1; df=$2
  echo "== $dir/$df"
  docker build -q -t forever-dm410:x -f $dir/$df $dir >/dev/null
  docker run --rm --name forever-dm410run forever-dm410:x 2>&1 | tail -n 3
}
t c1 Dockerfile
t c1 Dockerfile.fixed
t c3 Dockerfile
t c3 Dockerfile.fixed
t c4 Dockerfile
t c4 Dockerfile.pythonpath
t c4 Dockerfile.module

Output (Docker, python:3-alpine = Python 3.14.7):

== c1/Dockerfile
  File "/code/main.py", line 1, in <module>
    from app.util import greet
ModuleNotFoundError: No module named 'app'
== c1/Dockerfile.fixed
hello from app
== c3/Dockerfile
  File "/code/main.py", line 1, in <module>
    import six
ModuleNotFoundError: No module named 'six'
== c3/Dockerfile.fixed
six 1.17.0
== c4/Dockerfile
  File "/code/src/app/main.py", line 1, in <module>
    from app.util import greet
ModuleNotFoundError: No module named 'app'
== c4/Dockerfile.pythonpath
hello from app
== c4/Dockerfile.module
hello from app

2. The three causes

3. Checking inside the container

Run a shell in the broken image and ask which interpreter runs and where the package actually is. The last part shows that a .dockerignore line can also leave a package out even with COPY . .:

#!/bin/sh
# c3 broken image: which python runs, and where is six?
docker build -q -t forever-dm410:x -f c3/Dockerfile c3 >/dev/null
docker run --rm --name forever-dm410run forever-dm410:x sh -c '
which python
python -c "import sys; print(sys.prefix)"
ls /opt/venv/lib/python3*/site-packages | grep six
/opt/venv/bin/python main.py'
# .dockerignore that excludes the package
echo "== c1 with .dockerignore containing: app"
echo app > c1/.dockerignore
docker build -q -t forever-dm410:x -f c1/Dockerfile.fixed c1 >/dev/null
docker run --rm --name forever-dm410run forever-dm410:x 2>&1 | tail -n 1
rm c1/.dockerignore

Output:

/usr/local/bin/python
/usr/local
six-1.17.0.dist-info
six.py
six 1.17.0
== c1 with .dockerignore containing: app
ModuleNotFoundError: No module named 'app'

which python is /usr/local/bin/python (prefix /usr/local), while six.py sits in the venv, and the venv's own interpreter imports it fine. With app in .dockerignore, the "fixed" COPY . . image fails again with No module named 'app'.

Quick checklist: docker run --rm -it IMAGE sh, then ls the working directory, which python, python -c "import sys; print(sys.path)" and python -m pip list. A mounted volume (-v $PWD:/code) also replaces whatever the image had in that folder at run time.

Related: pip error: externally-managed-environment, docker build failed, Docker pip externally-managed-environment.

Frequently asked questions

Why do I get ModuleNotFoundError in Docker but not locally?

Usually the module is not in the image (COPY missed it or .dockerignore excluded it), it was installed into a different Python or venv than the one CMD runs, or the import root is not on sys.path.

How do I fix No module named in a Docker multi-stage build with a venv?

Copy the venv into the final stage and put it first on PATH with ENV PATH="/opt/venv/bin:$PATH", or call /opt/venv/bin/python directly.

How do I set PYTHONPATH in a Dockerfile?

Add ENV PYTHONPATH=/code/src (the folder that contains your top-level package), or set WORKDIR to it and run python -m package.module.

Written with AI assistance. Measured on 2026-10-23 with Docker and the python:3-alpine image (Python 3.14.7). The scripts ran verbatim and the output was pasted unedited. PlainTables is our extension; Docker and Python are free.