How to Fix 'ModuleNotFoundError' When Deploying Python ML Apps with Docker

Stop wasting hours on Docker import errors. Fix ModuleNotFoundError in 15 minutes with this step-by-step guide from a frustrated ML engineer.

I spent 4 hours debugging this exact error at 11 PM before a demo. My FastAPI ML app worked perfectly locally, then Docker decided my modules "didn't exist."

What you'll fix: ModuleNotFoundError in Docker containers for Python ML apps
Time needed: 15 minutes (instead of hours)
Difficulty: Intermediate Python + basic Docker knowledge

Here's the exact workflow that saves me every single deployment.

Why I Built This Guide

Last month I was deploying a computer vision model to production. Everything worked on my MacBook:

My setup:

  • Python 3.9 with scikit-learn, pandas, FastAPI
  • Local development with pip install -e .
  • Docker Desktop on macOS Monterey
  • Project structure with src/ directory

What didn't work:

  • Copying requirements.txt only → missing local modules
  • Using COPY . . without .dockerignore → cached old files
  • Running pip install without understanding Docker's layer caching
  • Following outdated tutorials that skip the packaging step

The container kept throwing ModuleNotFoundError: No module named 'src' even though the files were clearly there.

The Real Problem (Not What You Think)

The problem: Docker doesn't know your local package structure

My solution: Proper Python packaging + strategic Dockerfile layers

Time this saves: 4+ hours of trial-and-error debugging

Most tutorials tell you to COPY . . and hope for the best. That's wrong. Here's what actually works.

Step 1: Set Up Proper Python Packaging

Your project needs to be installable as a package. This takes 2 minutes but saves hours.

# setup.py - create this in your project root
from setuptools import setup, find_packages

setup(
    name="your-ml-app",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "fastapi>=0.68.0",
        "scikit-learn>=1.0.0",
        "pandas>=1.3.0",
        "uvicorn>=0.15.0"
    ],
)

What this does: Makes your src/ directory importable from anywhere
Expected output: You can now run pip install -e . and import your modules

Project structure after adding setup.py
Your project should look like this - setup.py at the root level makes everything importable

Personal tip: "Use find_packages() instead of listing packages manually. It automatically finds your Python modules and saves you from import headaches."

Step 2: Create a Smart Dockerfile

Layer your Docker build to use caching effectively. This prevents rebuilding everything when you change one line of code.

# Dockerfile - optimized for ML apps
FROM python:3.9-slim

# Install system dependencies first (rarely change)
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy and install requirements first (leverage Docker cache)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy package setup (changes less frequently than source code)
COPY setup.py .

# Copy source code
COPY src/ ./src/

# Install your package in development mode
RUN pip install -e .

# Copy application files last (change most frequently)
COPY app/ ./app/

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

What this does: Installs your package so Python can find all modules
Expected output: No more ModuleNotFoundError when importing from src/

Docker build layers visualization
Smart layering: requirements and setup rarely change, so they get cached

Personal tip: "The pip install -e . line is the magic. It makes your src/ directory importable just like any other Python package."

Step 3: Fix Your Import Statements

Update your Python imports to use absolute imports instead of relative paths.

# ❌ Wrong - this breaks in Docker
from src.models.predictor import MLModel
from src.utils.preprocessing import clean_data

# ✅ Right - works everywhere after pip install -e .
from your_ml_app.models.predictor import MLModel
from your_ml_app.utils.preprocessing import clean_data

What this does: Uses your package name instead of file paths
Expected output: Imports work in local development AND Docker containers

Import statement comparison in VS Code
After the fix: clean absolute imports that work everywhere

Personal tip: "Match the package name in setup.py with your import statements. I use underscores in package names because hyphens break imports."

Step 4: Add Docker Ignore File

Prevent Docker from copying unnecessary files that can cause caching issues.

# .dockerignore - keeps your Docker context clean
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
.venv/
venv/
.git/
.gitignore
README.md
Dockerfile
.dockerignore
*.egg-info/
.pytest_cache/

What this does: Speeds up build time and prevents stale file issues
Expected output: Faster builds, no weird caching problems

Docker build time before and after dockerignore
Build time dropped from 45 seconds to 12 seconds on my M1 MacBook

Personal tip: "Always exclude .venv/ and pycache/. I've wasted hours debugging issues caused by copying local virtual environments into containers."

Step 5: Test Your Fix

Build and run your container to verify the fix works.

# Build your container
docker build -t your-ml-app .

# Run it locally to test
docker run -p 8000:8000 your-ml-app

# Test the API endpoint
curl http://localhost:8000/predict -X POST \
  -H "Content-Type: application/json" \
  -d '{"data": [1, 2, 3, 4, 5]}'

What this does: Proves your modules import correctly in the container
Expected output: Your API responds without ModuleNotFoundError

Successful Docker container logs
Victory: clean startup logs with no import errors

Personal tip: "Test with docker run locally before pushing to production. Saves you from debugging in staging environments."

What You Just Built

A Docker container that properly imports your Python ML modules without throwing ModuleNotFoundError.

Key Takeaways (Save These)

  • Package your code: Use setup.py and pip install -e . to make modules importable
  • Layer smartly: Put requirements.txt and setup.py before source code in Dockerfile
  • Import absolutely: Use package names, not relative file paths in imports
  • Ignore properly: Use .dockerignore to prevent build cache issues

Your Next Steps

Pick one:

  • Beginner: Learn about Docker multi-stage builds for smaller images
  • Intermediate: Add health checks and proper logging to your ML containers
  • Advanced: Set up CI/CD pipelines that test Docker builds automatically

Tools I Actually Use

  • Docker Desktop: Local development and testing
  • VS Code Docker extension: Visual container management and debugging
  • Python setuptools: The standard for making packages installable
  • Official Python Docker docs: Most reliable source for best practices

Common Variations of This Error

"No module named 'app'" - Same fix, your app directory isn't a package
"No module named 'models'" - Missing init.py files in your directories
"ImportError: attempted relative import" - Use absolute imports with your package name

The setup.py approach fixes all of these. Install your code as a package and Python knows exactly where to find everything.