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
- The code is not in the image (
c1).COPY main.py .copies only that file, soapp/never reaches/code. The build succeeds because nothing imports anything until the container starts. Copy the whole project (COPY . .) or each package directory. - The package is installed for a different Python (
c3).sixis in/opt/venv, butCMD ["python", ...]finds/usr/local/bin/pythonfirst onPATH, which has its own empty site-packages.ENV PATH="/opt/venv/bin:$PATH"(or calling/opt/venv/bin/python) fixes it. The same thing happens withpip install --useras root and then running as another user. - The import root is not on
sys.path(c4).python src/app/main.pyputs the script's own folder,/code/src/app, onsys.path, not/code/src, soimport appfails even though the files are there. Either setENV PYTHONPATH=/code/src, or setWORKDIRto the import root and run it as a module withpython -m app.main. Both printedhello from app.
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.