# Containerizing FastAPI Applications with Docker

Docker packages your app with everything it needs, so it runs the same anywhere. FastAPI lets you build fast, efficient APIs with Python.

Together, they make development simple and deployment reliable. FastAPI keeps things fast and easy, while Docker ensures consistency across environments.

In this guide, you'll learn how to containerize a FastAPI application with Docker.

[ad-uptime]

## Prerequisites

Before starting, make sure you have:

- [Python](https://www.python.org/downloads/) 3.8 or newer
- [Docker](https://docs.docker.com/get-docker/) Desktop or Engine installed
- [Docker Compose](https://docs.docker.com/compose/install/) also installed
- Basic understanding of FastAPI concepts

## Setting up the project structure

Let's create a straightforward FastAPI project demonstrating key features while being Docker-friendly.

First, create a new directory for your project:

```command
mkdir fastapi-docker-app && cd fastapi-docker-app
```

Set up a virtual environment to isolate your dependencies:

```command
python3 -m venv venv
```

Activate the virtual environment:

```command
source venv/bin/activate
```

Install FastAPI and Uvicorn (an ASGI server required by FastAPI):

```command
pip install fastapi uvicorn
```

Create a requirements file to track dependencies:

```command
pip freeze > requirements.txt
```

Now, create a simple `main.py` file as your application entry point:

```python
[label main.py]
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI(title="FastAPI Docker Example")

class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None

@app.get("/")
def read_root():
    return {"message": "Welcome to FastAPI in Docker!"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

@app.post("/items/")
def create_item(item: Item):
    return {"item_name": item.name, "price_with_tax": item.price * 1.1}

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
```

This FastAPI application includes:

- A root endpoint returning a welcome message
- A health check endpoint for container monitoring
- A POST endpoint demonstrating data validation with Pydantic models
- Configuration to listen on all interfaces (needed for Docker)

Let's test the application before containerizing it:

```command
uvicorn main:app --reload
```

```text
[output]
INFO:     Will watch for changes in these directories: ['/Users/stanley/fastapi-docker-app']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [28190] using StatReload
INFO:     Started server process [28192]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
```

Visit `http://127.0.0.1:8000/` in your browser to see the welcome message:

![Screenshot of the welcome message in the browser](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/64ab74eb-8bed-42d2-4118-6fe2c47ac000/lg1x =3248x1996)

You can also check out the automatic API documentation at `http://127.0.0.1:8000/docs` - one of FastAPI's great features:

![Screenshot of the FastAPI automatic API documentation](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/763869c4-2855-424f-f529-f3d5caba1800/lg1x =3248x1996)

After confirming it works, stop the server with `CTRL`+ `C`.

## Creating a Docker container for FastAPI

Now let's containerize our FastAPI application to make it portable and consistent across environments.

Create a `Dockerfile` in your project root:

```command
touch Dockerfile
```

Add the following configuration:

```dockerfile
[label Dockerfile]
FROM python:3.13.3-slim-bookworm

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY main.py .

EXPOSE 8000

# Option 1: Run with Uvicorn directly
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

# Option 2: Use FastAPI CLI (alternative)
# CMD ["fastapi", "run", "main.py", "--port", "8000"]
```

This Dockerfile:

- Uses a lightweight Python 3.13.3 base image
- Sets up a working directory for our application
- Installs Python dependencies first (for better caching)
- Copies our application code
- Exposes port `8000` for incoming connections
- Starts the application with Uvicorn (or alternatively with the FastAPI CLI)

Create a `.dockerignore` file to exclude unnecessary files:

```command
touch .dockerignore
```

In your `.dockerignore` file, add the following content:

```text
[label .dockerignore]
venv/
__pycache__/
*.pyc
.git/
.gitignore
.idea/
.vscode/
```

Next, build your Docker image by running:

```command
docker build -t fastapi-app .
```

This command creates an image called `fastapi-app` using the current folder as the build context.

Once the image is built, start a container with:

```command
docker run --name fastapi-container -d -p 8000:8000 fastapi-app
```

This launches the container in the background (`-d`) and connects port 8000 on your machine to port 8000 inside the container.

Now open your browser and go to `http://localhost:8000/` to make sure your FastAPI app is up and running inside the container:

![Screenshot of the FastAPI app running inside the container](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/4fd7ee9f-efd5-44c3-79cf-3f11ec276700/orig =3248x1996)

With that, your containerized FastAPI app is live and ready to go.

## Working with container logs

Logs are essential for monitoring and debugging your FastAPI app. Docker captures all output from your application, so you can easily check what's happening.

To view logs from your running container:

```command
docker logs fastapi-container
```

```text
[output]
INFO:     Started server process [1]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
INFO:     192.168.215.1:45272 - "GET / HTTP/1.1" 200 OK
```

These messages confirm that Uvicorn has started and is handling requests on port 8000.

To follow the logs in real time (great for development and troubleshooting):

```command
docker logs -f fastapi-container
```

Visit a few endpoints in your browser to see new log entries appear as they happen.

To view only the latest log lines:

```command
docker logs --tail 10 fastapi-container
```

And to see logs from a specific time:

```command
docker logs --since 2025-05-10T10:00:00 fastapi-container
```

When you're done with the container, stop and remove it:

```command
docker stop fastapi-container
```
```command
docker rm fastapi-container
```

Or, if you prefer to have the container clean itself up automatically when it stops, use the `--rm` flag:

```command
docker run --name fastapi-container --rm -d -p 8000:8000 fastapi-app
```

This helps keep your system tidy by removing the container as soon as it exits.

## Using Docker Compose for development

While basic Docker commands are fine for simple apps, Docker Compose is better for managing more complex development setups. It lets you define and run multi-container applications using a single configuration file, making managing things easier.

First, stop any container you started earlier:

```command
docker stop fastapi-container
```

Then, create a `docker-compose.yml` file in the root of your project:

```command
touch docker-compose.yml
```

Add the following configuration:

```yaml
[label docker-compose.yml]
services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    volumes:
      - ./main.py:/app/main.py
    environment:
      - ENVIRONMENT=development
      - LOG_LEVEL=debug
    command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
```

This setup comes with several advantages:

* Live code reloading applies changes to `main.py` instantly without rebuilding
* Environment variables make it easy to manage development-specific settings
* Health checks automatically monitor your app’s status
* Restart policy helps your container recover if it crashes
* Simplified commands make it easier to manage the whole environment

Start the development environment with:

```command
docker-compose up
```

You'll see output similar to:

```text
[output]
[+] Building 4.4s (11/11) FINISHED                                                                                               docker:orbstack
...
 => => writing image sha256:897fb54cc17205ef097a81e53d50d32bace9cdecc40cfe2ca0882dd444e9dc84                                                0.0s
 => => naming to docker.io/library/fastapi-docker-app-api                                                                                   0.0s
 => [api] resolving provenance for metadata file                                                                                            0.0s
[+] Running 2/1
 ✔ Network fastapi-docker-app_default  Created                                                                                              0.0s 
 ✔ Container fastapi-docker-app-api-1  Created                                                                                              0.1s 
Attaching to api-1
api-1  | INFO:     Will watch for changes in these directories: ['/app']
api-1  | INFO:     Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
api-1  | INFO:     Started reloader process [1] using StatReload
api-1  | INFO:     Started server process [8]
api-1  | INFO:     Waiting for application startup.
api-1  | INFO:     Application startup complete.
```

The most powerful feature of this setup is modifying code without container restarts. Try adding a new endpoint to `main.py`:

```python
[label main.py]
from fastapi import FastAPI
from pydantic import BaseModel

...

[highlight]
@app.get("/version")
def get_version():
    return {
        "version": "1.0.0",
        "framework": "FastAPI",
        "python": "3.13.3"
    }
[/highlight]

if __name__ == "__main__":
    import uvicorn
    uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
```

Save the file and visit `http://localhost:8000/version` in your browser to see the changes take effect immediately:

![Screenshot of the browser showing the version page](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/3eba400e-d161-4eb6-0fda-f870f8170a00/md2x =3248x1996)

The logs will show Uvicorn detecting the change:

```text
[output]
api-1  | WARNING:  StatReload detected changes in 'main.py'. Reloading...
api-1  | INFO:     Shutting down
api-1  | INFO:     Waiting for application shutdown.
api-1  | INFO:     Application shutdown complete.
api-1  | INFO:     Finished server process [8]
api-1  | INFO:     Started server process [39]
api-1  | INFO:     Waiting for application startup.
api-1  | INFO:     Application startup complete.
api-1  | INFO:     192.168.107.1:57592 - "GET /version HTTP/1.1" 200 OK
api-1  | INFO:     192.168.107.1:57592 - "GET /favicon.ico HTTP/1.1" 404 Not Found
```

To run Docker Compose in the background:

```command
docker-compose up -d
```

View logs from all services:

```command
docker-compose logs
```

Or follow logs in real-time:

```command
docker-compose logs -f
```

When you're done, shut everything down with:

```command
docker-compose down
```

This workflow brings together Docker’s reliability and the quick feedback loop needed for development, making it a great fit for FastAPI projects of any scale.

## Final thoughts

Using FastAPI with Docker and Docker Compose gives you a solid foundation for building and running web APIs. It simplifies development, ensures consistency across environments, and scales easily as your project grows.

For more details and best practices, visit the official docs:

* [FastAPI Docs](https://fastapi.tiangolo.com/)
* [Docker Docs](https://docs.docker.com/)
* [Docker Compose Docs](https://docs.docker.com/compose/)

