Running FastAPI with a Database in Separate Docker Containers: A Step-by-Step Guide to Multi-Container Architecture

:rocket: Exploring Multi-Container Architecture with Docker and FastAPI

:open_book: Introduction

In modern application development, microservices and multi-container architectures have become increasingly popular. Separating different services into individual containers gives you flexibility, scalability, and easier maintenance. In this blog, we’ll explore a multi-container architecture using Docker, specifically focusing on a practical example involving a FastAPI-based OAuth2 authentication service and a MySQL database.

:thinking: Why Multi-Container Architecture?

  • :brain: Isolation: Each service runs in its container, reducing conflicts.
  • :chart_increasing: Scalability: Scale specific components without affecting the entire application.
  • :hammer_and_wrench: Maintainability: Easier to update and manage individual services.
  • :flexed_biceps: Resilience: If one container fails, it does not bring down the entire system.

:open_file_folder: Project Structure

The folder structure of our multi-container project looks like this:

multi_container
├── database-container
│   ├── compose.yaml
│   ├── Dockerfile
│   └── README.Docker.md
└── Oauth2-fastapi
    ├── compose.yaml
    ├── Dockerfile
    ├── main.py
    ├── README.Docker.md
    ├── requirements.txt
    ├── routers
    │   ├── auth.py
    │   ├── google_auth.py
    │   └── users.py
    └── utils
        ├── config.py
        ├── db_helper.py
        └── schema.py

:magnifying_glass_tilted_left: Components Explained

1. :file_cabinet: database-container:

Contains the Docker setup for the MySQL database.

  • compose.yaml configures the service with volume and network settings.

2. :key: Oauth2-fastapi:

Implements an authentication service using FastAPI and OAuth2.

  • Includes route handlers (auth.py , google_auth.py , users.py ) and utility scripts (config.py , db_helper.py , schema.py ).

Code for database-container/compose.yaml

version: '3.8'

services:
  server:
    build:
      context: .
    ports:
      - "8000:8000"
    depends_on:
      db:
        condition: service_healthy  # Ensures MySQL is ready before FastAPI starts
    environment:
      - DATABASE_URL=mysql+mysqlconnector://levelup:levelup2025@db:3306/levelup
    networks:
      - app-network

  db:
    image: mysql:latest
    restart: always
    env_file:
      - .env
    ports:
      - "3306:3306"
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

volumes:
  db-data:

networks:
  app-network:
    driver: bridge

Code for database-container/Dockerfile

# syntax=docker/dockerfile:1

FROM alpine:latest as base

FROM base as build
RUN echo -e '#!/bin/sh\necho Hello world from $(whoami)! In order to get your application running in a container, take a look at the comments in the Dockerfile to get started.' > /bin/hello.sh
RUN chmod +x /bin/hello.sh

FROM base AS final

ARG UID=10001
RUN adduser \
    --disabled-password \
    --gecos "" \
    --home "/nonexistent" \
    --shell "/sbin/nologin" \
    --no-create-home \
    --uid "${UID}" \
    appuser
USER appuser

COPY --from=build /bin/hello.sh /bin/

ENTRYPOINT [ "/bin/hello.sh" ]

Code for oauth2-fastapi/compose.yaml

networks:
  database-container_app-network:
    external: true
services:
  server:
    build:
      context: .
    ports:
      - 8060:8060
    networks:
      - database-container_app-network

Explanation:

  • Configures the FastAPI service in a separate container.
  • Connects to the shared network (database-container_app-network ) for inter-container communication.
  • Exposes port 8060 for accessing the FastAPI service.

Code for oauth2-fastapi/dockerfile

# Use a lightweight Python image
FROM python:3.10-slim as base

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

# Set the working directory inside the container
WORKDIR /app

# Create a non-root user
ARG UID=10001
RUN adduser --disabled-password --gecos "" --uid "${UID}" appuser

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

# Copy application code
COPY . .

# Change ownership to the non-root user
RUN chown -R appuser:appuser /app

# Switch to non-root user
USER appuser

# Expose the port FastAPI will run on
EXPOSE 8060

# Start FastAPI application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8060"]

:globe_with_meridians: How Docker Networks Work

Bridge Network:

  • By default, Docker creates a bridge network for containers.
  • Containers on the same bridge network can communicate using their container names as hostnames.

External Network:

  • The oauth2-fastapi container uses an external network (database-container_app-network ) to connect with the MySQL container.
  • This allows services in different Docker Compose files to communicate with each other.

Benefits of Networking:

  • :locked: Isolated environments for services.
  • :vertical_traffic_light: Flexibility to connect and disconnect containers as needed.
  • :link: Ensures secure communication between services.

:chequered_flag: Conclusion

By setting up a multi-container architecture with Docker and FastAPI, you achieve a modular, maintainable, and scalable application. Using external networks and dependency management ensures robust communication between services, enhancing the overall resilience of the system.

:backhand_index_pointing_right: If you want code on routers and utils, check my Medium post: Google Authentication in FastAPI using OAuth2

:light_bulb: Hope this is useful. Happy coding! :technologist:

6 Likes