Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

FastAPI Docker Setup: Should You Run main.py?

Wondering if starting FastAPI with main.py in Docker is a problem? Learn the pros and cons, environment variable handling, and deployment tips.
Thumbnail showing import error using uvicorn main.py vs correct Docker setup using module syntax for FastAPI Thumbnail showing import error using uvicorn main.py vs correct Docker setup using module syntax for FastAPI
  • 🐳 Running FastAPI with main.py in Docker can break relative imports due to Python's execution context.
  • 🔥 Uvicorn's module syntax (app.main:app) is the recommended way to launch FastAPI in Docker environments.
  • 🧊 The python:3.10-slim base image is better for FastAPI Docker builds than Alpine due to compatibility issues.
  • 🔐 Managing secrets via .env and environment variables prevents exposing credentials in Docker images.
  • 🚀 For production, use Gunicorn with UvicornWorker to ensure multiprocessing and scalability.

FastAPI's clean syntax and async capabilities make it a good backend framework. But deploying it correctly, especially with Docker, is important. This prevents issues like import errors, broken environment variables, or bad scalability. This article shows how to run FastAPI in Docker correctly. It covers using uvicorn and good methods for builds that scale, are secure, and ready for production.


Why FastAPI and Docker Work So Well Together

FastAPI is a modern Python backend. It offers features like automatic OpenAPI schema generation, asynchronous request handling, and strong typing with Pydantic. These features make it a good choice for building fast and reliable APIs.

Docker works very well with FastAPI. It provides containerized environments that make sure the software runs the same way in development, QA, and production. With Docker, all dependencies, from the Python version to OS packages, are put directly into the container image. This makes sure your FastAPI application works the same way if you are developing on macOS, deploying on Linux, or testing with a CI/CD pipeline. It gets rid of the "works on my machine" problem. It also makes it easy to bring new developers on board or set up test environments.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel


How Most People Start: uvicorn main.py

Beginners often start FastAPI using Uvicorn with a direct file name like this:

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

Sample Dockerfile for a quick start:

FROM python:3.10

WORKDIR /app

COPY . .

RUN pip install -r requirements.txt

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

This approach seems simple, and it works, but only for very basic applications. It works if you are testing locally or if your app has a simple file setup. But when your app grows to include many modules and packages, this shortcut causes big problems.


Why Running main.py Can Break Your App

When you run main.py directly, Python sees it as the main script. It does not see it as part of your package. This turns off relative imports and goes against how Python finds modules.

For example:

# app/main.py
from .models import User  # ❌ Raises ImportError if run directly

When main.py starts without context, like with python main.py or uvicorn main.py, Python turns off how it normally imports packages. This means from .models import User fails. And you will wonder why your imports are not working as they should.

Other consequences:

  • Docker's CMD command sees main.py as a separate file when it runs.
  • Unit tests that import files relatively might break.
  • Logging and config loading might not work if you use relative paths.

Rule: Do not run FastAPI apps using the filename. This causes builds to be unstable and creates problems with imports, especially in Docker.


Prefer Module Syntax: uvicorn app.main:app

Do not start your FastAPI app with a direct script filename. Instead, use Python’s module syntax. Here is the way to do it:

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

Your project structure should look like this:

/project
  /app
    __init__.py
    main.py
    models/
    routes/

Now Python and Uvicorn see the app directory as a real package. Relative imports now work correctly:

from .models import User  # ✅ Works reliably

This approach works well as your project grows. This is because it follows good practices from both the FastAPI Docker documentation and Uvicorn's guidelines.


Dockerfile Tips for FastAPI + Uvicorn

To build a Docker container that is small but strong, start with the right base image. Also, keep your code clean. Here is a better Dockerfile:

FROM python:3.10-slim

WORKDIR /code

COPY ./app /code/app
COPY requirements.txt /code/

RUN pip install --no-cache-dir -r requirements.txt

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

Why this works well:

  • Python Slim makes the image smaller than standard images.
  • --no-cache-dir stops pip from saving extra cache files.
  • Files are copied in separate steps. This helps Docker reuse cached parts.

Do not use Alpine for most FastAPI apps. This is unless you know a lot about its problems with Python wheels and C extensions.


Managing Environment Variables Safely

Environment variables are important for production settings. They let you keep secrets or specific data, like API keys or database addresses, separate. Here is a safe way to do it:

  1. Create a .env file:
DATABASE_URL=postgresql://user:password@db:5432/mydb
DEBUG=False
  1. Run the container with --env-file:
docker run --env-file=.env fastapi-image
  1. Access inside your FastAPI app:
import os

DEBUG = os.getenv("DEBUG", "False") == "True"
DATABASE_URL = os.getenv("DATABASE_URL")

You can use python-dotenv to load .env files automatically in development:

pip install python-dotenv

And in your code:

from dotenv import load_dotenv
load_dotenv()

Never put sensitive .env contents directly into your image with COPY. Always pass them securely when the app runs.


Debugging and Logs Inside Docker

To see what is happening inside your FastAPI app in Docker:

  • Use stdout for logs:

    import logging
    
    logging.basicConfig(level=logging.INFO)
    
  • Do not write logs directly to files. Only do this if you are managing disk space or sending logs with tools like Fluentd.

  • During development, you can use Uvicorn’s reload feature:

    CMD ["uvicorn", "app.main:app", "--reload", "--host", "0.0.0.0"]
    

But remember, --reload is:

  • Slower
  • Not safe for production
  • Only use it during local development.

Setting Up for Production Deployment

Uvicorn is great, but it runs with only one thread by default. For production, use Gunicorn with Uvicorn for multiple workers:

gunicorn app.main:app -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000

Gunicorn works as an app server. It sends tasks to Uvicorn workers to handle ASGI processes. Benefits:

  • Process management
  • Better performance with multiple workers
  • Handles signals and restarts smoothly.

Also consider:

  • Add a reverse proxy like Nginx or Traefik for TLS.
  • Use Kubernetes probes for /health and /ready.
  • Use --config flags to keep server settings outside the code.

Hardening Your Docker Container

Secure your container before deploying to production:

  • Create a non-root user:

    RUN adduser --disabled-password fastapi
    USER fastapi
    
  • Use multi-stage Docker builds. This helps remove build dependencies and makes the final image smaller.

  • Turn off FastAPI debug mode in .env.

  • Clearly state the exposed port:

    EXPOSE 8000
    

Making your container secure helps in the long run. It makes it harder for attacks and helps meet security rules.


Avoid These Common Mistakes

Avoid these common mistakes with FastAPI and Docker:

  • Do not use uvicorn main.py instead of module syntax.
  • Do not forget __init__.py. Python will not see folders as packages without it.
  • Do not forget to rebuild the Docker image after you change code.
  • Do not copy your .env file into the image.
  • Do not set --reload in production builds.

Check your Dockerfile and scripts for these issues before deploying.


CI/CD and Docker Registry Integration

Automate your builds and deployments using CI/CD pipelines. Here is what a basic GitHub Actions workflow might look like:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: docker build -t myfastapi:latest .
      - run: docker tag myfastapi myregistry.com/my-fastapi:latest
      - run: docker push myregistry.com/my-fastapi:latest

Extra tips:

  • Tag images using Git commit SHA or version numbers.
  • Use Docker's BuildKit for layer caching.
  • Run vulnerability scans with docker scan, Snyk, or Trivy.
  • Set up multi-stage builds to keep the final image secure and small.

Example Directory Layout for FastAPI

A clean project layout makes it much easier to test, read, and extend your code. A good structure is:

/project-root
  /app
    __init__.py
    main.py
    routes/
    models/
    services/
  /config
    settings.py
  /tests
  requirements.txt
  Dockerfile
  .env

Putting components into routes/, models/, and services/ helps keep different parts of the code separate. The config/settings.py file can put all environment-based config loading in one place.


Debugging Python Import Errors in Docker

If you get ImportError or ModuleNotFoundError, try this first:

Run the app with module syntax:

python -m app.main

Instead of:

python app/main.py

Check paths inside Docker using:

RUN pwd && ls -la

When you add folders or use COPY commands, it is easy for your working directories to be wrong.

More tips to find the problem:

  • Print sys.path.
  • Log the actual os.getcwd().
  • Use docker exec to check file paths inside a running container.

To finish, if you are building or deploying FastAPI in Docker, do not start it with main.py. This works for basic uses, but it breaks imports and fails without a warning in more complex builds. Instead, use module syntax (uvicorn app.main:app). It is simple, strong, and follows proven production standards.

Ready to do even more with your FastAPI deployment? In future articles, we will cover setting up autoscaling in Kubernetes, caching with Redis, and building dashboards for monitoring with Prometheus and Grafana.


Citations

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading