本次的程式碼與目錄結構可以參考 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 更小
在建立 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
:- Database :
這邊我們把 PostgreSQL 和 MySQL 的資料都掛載到db_volumes
目錄下
這樣方便我們直接在本機上看到資料 - Backend :
也將目前backend
目錄掛載到 Container 中
可以直接在本機上修改程式碼
- Database :
接著我們設定 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
對於 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
檔案
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 一致
因為我們要透過 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 測試
也可以正常運作 !