In the last article we talked about making our pydantic documentation as robust as possible. In this part, we’ll focus on containerizing the Weather Forecast API using Docker. By the end of this tutorial, your API will be running inside a Docker container, alongside a PostgreSQL database, all orchestrated using Docker Compose.


Why Dockerize? 🛠️

Docker simplifies application deployment by packaging your application and its dependencies into a portable container. With Docker:

  • You can ensure consistency across development, staging, and production environments.
  • Easily scale and deploy your application.
  • Run multiple services, such as your FastAPI app and PostgreSQL database, together in an isolated environment.

Goals for Part 4 🎯

  1. Create a Dockerfile to containerize the FastAPI app.
  2. Add a docker-compose.yml file to orchestrate the API and PostgreSQL.
  3. Set up a .env file to manage environment variables.
  4. Run the app and database together using Docker Compose.

Here’s my requirements.txt file:

1
2
3
4
5
6
7
8
fastapi
uvicorn
sqlalchemy
pydantic
pydantic-settings
psycopg[binary]
asyncpg
greenlet

Step 1: Create the Dockerfile

The Dockerfile defines the steps to build a Docker image for the FastAPI app.

Sample Dockerfile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Use the official Python image as the base
FROM python:3.10-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set the working directory inside the container
WORKDIR /app

# Copy the requirements file and install dependencies
COPY requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY . /app/

# Expose the application port
EXPOSE 8000

# Command to run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Step 2: Configure Docker Compose

Docker Compose helps manage multi-container applications, like our API and database, using a single YAML file.

Sample docker-compose.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: "3.8"

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    env_file:
      - .env
    depends_on:
      - db
    volumes:
      - .:/app

  db:
    image: postgres:15
    restart: always
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

volumes:
  postgres_data:

Step 3: Add Environment Variables

Environment variables are managed using a .env file to keep sensitive data out of the codebase.

Sample .env

1
2
3
4
5
6
7
# Database configuration
POSTGRES_USER=api
POSTGRES_PASSWORD=securepassword
POSTGRES_DB=weather

# FastAPI configuration
DATABASE_URL=postgresql+asyncpg://api:securepassword@db:5432/weather

Step 4: Build and Run the Containers

Build the Docker Images

Run the following command to build the images defined in docker-compose.yml:

1
docker-compose build

Run the Containers

Start the containers with:

1
docker-compose up

Your FastAPI app will now be accessible at http://localhost:8000.


Step 5: Database Initialization

The PostgreSQL database will be initialized automatically. However, you need to create the schema if it doesn’t exist.

Auto-Running the Schema Script

To automate schema creation, include the schema initialization in the CMD or ENTRYPOINT of the Dockerfile, or add a custom init.sql script to the db service.

Example: Adding an init.sql Script

  1. Create a file named init.sql:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    CREATE TABLE IF NOT EXISTS hourly_weather_forecast (
        start_time TIMESTAMP PRIMARY KEY,
        id SERIAL,
        row_start_datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        row_end_datetime TIMESTAMP,
        is_current BOOLEAN DEFAULT TRUE,
        end_time TIMESTAMP NOT NULL,
        temperature INTEGER,
        temperature_unit VARCHAR(10),
        relative_humidity INTEGER,
        wind_speed VARCHAR(50),
        wind_direction VARCHAR(10),
        short_forecast VARCHAR(255),
        icon VARCHAR(255)
    );
    
  2. Update the docker-compose.yml file:

    1
    2
    3
    4
    5
    
    db:
      image: postgres:15
      volumes:
        - postgres_data:/var/lib/postgresql/data
        - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    

Step 6: Verify the Setup

  1. Visit the API documentation: Open http://localhost:8000/docs in your browser to access the FastAPI auto-generated Swagger UI.

  2. Test the API: Use curl or a tool like Postman to test your endpoints.

  3. Inspect the Database: Connect to the database with:

    1
    
    docker exec -it <container_id> psql -U api -d weather