本次的程式碼與目錄結構可以參考 FastAPI Tutorial : Day23 branch
透過 Docker Compose 一鍵部署 FastAPI + PostgreSQL + MySQL 專案

可以任意在 backend Container 中任意切換 DB 進行測試
![]() |
![]() |
|---|
在 Day21 和 Day23 我們完成 pytest 的 Unit Test
接下來我們要透過 Docker Compose 來部署我們的 FastAPI 專案
如果單純使用 Docker 部署 FastAPI 專案,我們需要先建立一個給 FastAPI 的 Docker Image
再分別建立 PostgreSQL 和 MySQL 的 Docker Image
最後跑 3 次 docker run 來啟動所有 Container
透過 Docker Compose 我們可以透過一個 docker-compose.yml 檔案來定義我們的專案
並且能一次啟動所有 Container
首先我們先來建立 FastAPI 的 Docker Image
在 backend 目錄下建立 Dockerfile
touch backend/Dockerfile
由於我們的專案結構是透過 Poetry 來管理的
所以我們可以先 export Poetry 的 dependencies 到 backend/requirements.txt
poetry export -f requirements.txt -o backend/requirements.txt
接著就可以開始編寫 Dockerfile
backend/Dockerfile
FROM python:3.11.1-slim
ENV PYTHONDONTWRITEBYTECODE 1
WORKDIR /usr/backend
COPY ./requirements.txt /usr/backend/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /usr/backend/
我們把 workdir 設定到 /usr/backend
特別設定 PYTHONDONTWRITEBYTECODE 讓 Python 不會產生 .pyc 檔
可以讓產生的 Docker Image 更小
.dockerignore 設定在建立 Docker Image 時,我們可以透過 .dockerignore 來避免不必要的檔案被加入
再次縮小 Docker Image 的大小
touch .dockerignore
backend/.dockerignore
__pycache__
.pytest_cache
Dockerfile
.dockignore
這樣在 COPY . /usr/backend/ 時就不會把這些檔案加入 Docker Image 中
與不設定 .dockerignore 和 ENV PYTHONDONTWRITEBYTECODE 1 時的 Docker Image 大小比較
![]() |
![]() |
|---|
大概小 10MB 左右
影響 Docker Image 大小的因素有很多,像是使用的 Base Image、安裝的套件等等
如果一開始的 Poetry 是開在 backend 目錄下
可以在 Build Image 時再產生 requirements.txt
並透過 Multi Stage Build 來減少 Image 的大小
FROM python:3.11.1-slim as builder
WORKDIR /tmp
RUN pip install poetry
COPY ./pyproject.toml ./poetry.lock* /tmp/
RUN poetry export -f requirements.txt --output requirements.txt --without-hashes
FROM python:3.11.1-slim
ENV PYTHONDONTWRITEBYTECODE 1
WORKDIR /usr/backend
COPY --from=builder /tmp/requirements.txt /usr/backend/requirements.txt
# ... 剩下與原本的相同
可以參考 fastapi dockerize : docker image with poetry
在根目錄下建立 docker-compose.yml
touch docker-compose.yml
最基本的設定如下
而 services 中的 backend 會使用 backend/Dockerfile 來建立 Docker Image
PostgreSQL 和 MySQL 則是直接使用 Docker Hub 上的 Image
docker-compose.yml
version: '1.0'
services:
postgresql_db:
image: postgres:15.1
restart: always
volumes:
- ./db_volumes/postgresql:/var/lib/postgresql/data/
mysql_db:
image: mysql:8.1.0
restart: always
volumes:
- ./db_volumes/mysql:/var/lib/mysql/
backend:
build: ./backend
volumes:
- ./backend/:/usr/backend/
command: python3 run.py --prod
restart: always
networks:
default:
name: fastapi_tutorial_network
networks : network 來讓所有 Container 透過 Service Name 來連線 volumes :
db_volumes 目錄下 backend 目錄掛載到 Container 中 接著我們設定 Database 要 Expose 的 Port
這邊我們設定 PostgreSQL 的 Port 為 5432
MySQL 的 Port 為 3306
並將 backend Container 的 8003 Port Forward 到本機的 8000 Port
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
expose:
- 5432
mysql_db:
# ...
expose:
- 3306
backend:
# ...
ports:
- 8000:8003
我們 backend Container 會依賴於 PostgreSQL 和 MySQL
應該要先等 Database 啟動完成後才啟動 backend
所以需要特別設定 depends_on
docker-compose.yml
# ...
services:
# ...
backend:
# ...
depends_on:
- postgresql_db
- mysql_db
environment 設定對於 PostgreSQL 和 MySQL 我們都需要設定 environment
需要將 POSTGRES_USER、POSTGRES_PASSWORD、POSTGRES_DB 這些環境變數在 docker-compose.yml 中設定好
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
environment:
- POSTGRES_USER=fastapi_tutorial
- POSTGRES_PASSWORD=fastapi_tutorial_password
- POSTGRES_DB=fastapi_tutorial
mysql_db:
# ...
environment:
- MYSQL_ROOT_PASSWORD=fastapi_tutorial_password
- MYSQL_DATABASE=fastapi_tutorial
這樣的缺點是我們的密碼都會直接寫在 docker-compose.yml 中
env_file 設定所以我們可以透過 env_file 來設定
我們先建立要載入的 .env 檔案
touch db.{postgresql,mysql}.env
db.postgresql.env
POSTGRES_PASSWORD=fastapi_tutorial_password
POSTGRES_USER=fastapi_tutorial
POSTGRES_DB=fastapi_tutorial
db.mysql.env
MYSQL_ROOT_PASSWORD=fastapi_tutorial_password
MYSQL_DATABASE=fastapi_tutorial
要注意這邊 .env 的設定要與 backend/setting/.env.prod 中的 DATABASE_URL 中 User、Password、DB Name 一致
setting/.env.prod 中的 DATABASE_URL因為我們要透過 Docker Compose 來部署
有設定 network 的關係,所以我們必須要使用 Service Name 來連線
有點像 Docker Compose 幫我們設定內網 DNS 的感覺
要將原本是 localhost:5432 的 DATABASE_URL 改成 postgresql_db:5432 localhost:3306 改成 mysql_db:3306
setting/.env.prod
# ...
SYNC_POSTGRESQL_DATABASE_URL='postgresql+psycopg2://fastapi_tutorial:fastapi_tutorial_password@postgresql_db:5432/fastapi_tutorial'
ASYNC_POSTGRESQL_DATABASE_URL='postgresql+asyncpg://fastapi_tutorial:fastapi_tutorial_password@postgresql_db:5432/fastapi_tutorial'
SYNC_MYSQL_DATABASE_URL='mysql+pymysql://root:fastapi_tutorial_password@mysql_db:3306/fastapi_tutorial'
ASYNC_MYSQL_DATABASE_URL='mysql+aiomysql://root:fastapi_tutorial_password@mysql_db:3306/fastapi_tutorial'
# ...
如果這時候直接使用 docker-compose up 來啟動
有時候會發現我們 FastAPI 在連接 Database 時會出現錯誤
說還無法 connect 到 Database
這是因為雖然我們有設定 depends_on
但是我們的 Database 還沒有啟動完成
所以我們可以額外透過 condition 和 healthcheck 來等待 Database 完全啟動完成
docker-compose.yml
version: '1.0'
services:
postgresql_db:
# ...
healthcheck:
test: ["CMD", "pg_isready", "-U", "fastapi_tutorial", "-d", "fastapi_tutorial"]
interval: 5s
timeout: 5s
retries: 5
# ...
mysql_db:
# ...
healthcheck:
test: ["CMD", "echo" , ">/dev/tcp/localhost/3306"]
interval: 5s
timeout: 5s
retries: 5
# ...
PostgreSQL 的 healthcheck 可以透過 pg_isready 來檢查
MySQL 的 healthcheck 則是透過 echo >/dev/tcp/localhost/3306 來檢查
這時候再直接使用 docker-compose up 來啟動
或是使用 docker-compose up -d 來背景執行

就可以看到我們的 FastAPI 專案已經成功部署
可以在 localhost:8000/docs 來看到 Swagger UI
進入 Backend Container 後
以 PostgreSQL 測試
docker exec -it ithome2023-fastapi-tutorial-backend-1 bash
cd tests && pytest --prod --db postgresql

以 MySQL 測試
pytest --prod --db mysql

都可以正常運作 !
今天我們透過 Docker Compose 來部署我們的 FastAPI 專案
使用 env_file 來載入 db.env 檔案
而不用將密碼直接寫在 docker-compose.yml 中
並且透過 depends_on和 healthcheck 來等待 Database 完整啟動完成後
再啟動 backend Container
最後我們進入 backend Container 中跑不同 Database 測試
也可以正常運作 !