├── app ├── __init__.py ├── config.py ├── main.py └── db.py ├── .gitignore ├── Dockerfile.traefik ├── requirements.txt ├── prestart.sh ├── Dockerfile.prod ├── Dockerfile ├── traefik.dev.toml ├── traefik.prod.toml ├── README.md ├── docker-compose.yml ├── LICENSE └── docker-compose.prod.yml /app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | -------------------------------------------------------------------------------- /Dockerfile.traefik: -------------------------------------------------------------------------------- 1 | FROM traefik:v2.9.6 2 | 3 | COPY ./traefik.prod.toml ./etc/traefik/traefik.toml 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | asyncpg==0.27.0 2 | fastapi==0.89.1 3 | ormar==0.12.1 4 | psycopg2-binary==2.9.5 5 | uvicorn==0.20.0 6 | -------------------------------------------------------------------------------- /prestart.sh: -------------------------------------------------------------------------------- 1 | echo "Waiting for postgres connection" 2 | 3 | while ! nc -z db 5432; do 4 | sleep 0.1 5 | done 6 | 7 | echo "PostgreSQL started" 8 | 9 | exec "$@" 10 | -------------------------------------------------------------------------------- /Dockerfile.prod: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.11-slim 2 | 3 | RUN apt-get update && apt-get install -y netcat 4 | 5 | COPY requirements.txt . 6 | RUN pip install -r requirements.txt 7 | 8 | COPY . . 9 | -------------------------------------------------------------------------------- /app/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from pydantic import BaseSettings, Field 4 | 5 | 6 | class Settings(BaseSettings): 7 | db_url: str = Field(..., env='DATABASE_URL') 8 | 9 | settings = Settings() 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # pull the official docker image 2 | FROM python:3.11.1-slim 3 | 4 | # set work directory 5 | WORKDIR /app 6 | 7 | # set env variables 8 | ENV PYTHONDONTWRITEBYTECODE 1 9 | ENV PYTHONUNBUFFERED 1 10 | 11 | # install dependencies 12 | COPY requirements.txt . 13 | RUN pip install -r requirements.txt 14 | 15 | # copy project 16 | COPY . . 17 | -------------------------------------------------------------------------------- /traefik.dev.toml: -------------------------------------------------------------------------------- 1 | # listen on port 80 2 | [entryPoints] 3 | [entryPoints.web] 4 | address = ":80" 5 | 6 | # Traefik dashboard over http 7 | [api] 8 | insecure = true 9 | 10 | [log] 11 | level = "DEBUG" 12 | 13 | [accessLog] 14 | 15 | # containers are not discovered automatically 16 | [providers] 17 | [providers.docker] 18 | exposedByDefault = false 19 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | 3 | from app.db import database, User 4 | 5 | 6 | app = FastAPI(title="FastAPI, Docker, and Traefik") 7 | 8 | 9 | @app.get("/") 10 | async def read_root(): 11 | return await User.objects.all() 12 | 13 | 14 | @app.on_event("startup") 15 | async def startup(): 16 | if not database.is_connected: 17 | await database.connect() 18 | # create a dummy entry 19 | await User.objects.get_or_create(email="test@test.com") 20 | 21 | 22 | @app.on_event("shutdown") 23 | async def shutdown(): 24 | if database.is_connected: 25 | await database.disconnect() 26 | -------------------------------------------------------------------------------- /traefik.prod.toml: -------------------------------------------------------------------------------- 1 | [entryPoints] 2 | [entryPoints.web] 3 | address = ":80" 4 | [entryPoints.web.http] 5 | [entryPoints.web.http.redirections] 6 | [entryPoints.web.http.redirections.entryPoint] 7 | to = "websecure" 8 | scheme = "https" 9 | 10 | [entryPoints.websecure] 11 | address = ":443" 12 | 13 | [accessLog] 14 | 15 | [api] 16 | dashboard = true 17 | 18 | [providers] 19 | [providers.docker] 20 | exposedByDefault = false 21 | 22 | [certificatesResolvers.letsencrypt.acme] 23 | email = "your@email.com" 24 | storage = "/certificates/acme.json" 25 | [certificatesResolvers.letsencrypt.acme.httpChallenge] 26 | entryPoint = "web" 27 | -------------------------------------------------------------------------------- /app/db.py: -------------------------------------------------------------------------------- 1 | import databases 2 | import ormar 3 | import sqlalchemy 4 | 5 | from .config import settings 6 | 7 | database = databases.Database(settings.db_url) 8 | metadata = sqlalchemy.MetaData() 9 | 10 | 11 | class BaseMeta(ormar.ModelMeta): 12 | metadata = metadata 13 | database = database 14 | 15 | 16 | class User(ormar.Model): 17 | class Meta(BaseMeta): 18 | tablename = "users" 19 | 20 | id: int = ormar.Integer(primary_key=True) 21 | email: str = ormar.String(max_length=128, unique=True, nullable=False) 22 | active: bool = ormar.Boolean(default=True, nullable=False) 23 | 24 | 25 | engine = sqlalchemy.create_engine(settings.db_url) 26 | metadata.create_all(engine) 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dockerizing FastAPI with Postgres, Uvicorn, and Traefik 2 | 3 | ## Want to learn how to build this? 4 | 5 | Check out the [post](https://testdriven.io/blog/fastapi-docker-traefik/). 6 | 7 | ## Want to use this project? 8 | 9 | ### Development 10 | 11 | Build the images and spin up the containers: 12 | 13 | ```sh 14 | $ docker-compose up -d --build 15 | ``` 16 | 17 | Test it out: 18 | 19 | 1. [http://fastapi.localhost:8008/](http://fastapi.localhost:8008/) 20 | 1. [http://fastapi.localhost:8081/](http://fastapi.localhost:8081/) 21 | 22 | ### Production 23 | 24 | Update the domain in *docker-compose.prod.yml*, and add your email to *traefik.prod.toml*. 25 | 26 | Build the images and run the containers: 27 | 28 | ```sh 29 | $ docker-compose -f docker-compose.prod.yml up -d --build 30 | ``` 31 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: . 6 | command: bash -c 'while !