DockerでFastAPI環境構築(HelloWorldまで)

# FastAPI環境構築

---

リンク

[チュートリアル - ユーザーガイド - FastAPI](https://fastapi.tiangolo.com/ja/tutorial/)

## 使用技術

### 環境

- Docker
- Python 3.12.3(執筆時最新)
- nginx(webサーバー)

### DB

- MySQL 8系
- SQLModel(ORマッパー)
- alembic(マイグレーション管理)

### ライブラリ管理

- poetry

構成はシンプルでnginxをwebサーバーとして配置して、Uvicormをアプリケーションサーバーにします。

# ディレクトリ作成

最低限のディレクトリを作成していきます。

```
project_root
├── _docker
│   ├── nginx
│   │   └── nginx.conf
│   └── python
│       └── Dockerfile
├── src
│   └── main.py
├── .env
├── pyproject.toml
└── docker-compose.yml
```

### /_docker/python/Dockerfile(仮なので最後に再編集します)

pythonのDockerfileです。ライブラリ管理にpoetryを使用するのと、テストDBにsqliteを使用するので、ここでインストールしています。

```dockerfile
FROM python:3.12.3

SHELL ["/bin/bash", "-c"]

# 作業ディレクトリの設定
WORKDIR /src

RUN apt-get update && \
    apt-get install -y build-essential libffi-dev && \
    apt-get install -y sqlite3

RUN pip install --upgrade pip

# 必要なファイルのコピー
COPY ./pyproject.toml /src/pyproject.toml

# Poetryのインストール
RUN curl -sSL https://install.python-poetry.org | python -

ENV PATH="/root/.local/bin:$PATH"

# Poetryの設定と依存関係のインストール
RUN poetry config virtualenvs.create false && \
    poetry install --no-interaction --no-root

# アプリケーションの起動コマンドを設定
ENTRYPOINT ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--reload"]
```

### /_docker/nginx/nginx.conf

FastAPIへ繋ぐためのプロキシ設定です。

```conf
events {}

http {
    server {
        listen 80;

        location / {
            proxy_pass http://fastapi:8000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
```

### /docker-compose.yml

特段、普通の設定かと思います。volumeを使用して永続化を行なったり、MySQLのDBを作成したりしています。

```yml
version: '3.8'
services:
  fastapi:
    build:
      context: .
      dockerfile: _docker/python/Dockerfile
    volumes:
      - .:/src
      - bash-data:/root/.local/bin
      - python-lib-data:/usr/local/lib/
    ports:
      - "8000:8000"
    depends_on:
      - mysql

  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./_docker/nginx/nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - fastapi

  mysql:
    image: mysql:latest
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_DATABASE: 'fastapi_db'
      MYSQL_USER: 'user'
      MYSQL_PASSWORD: 'password'
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - "3334:3306"
    volumes:
      - mysql-data:/var/lib/mysql

volumes:
  mysql-data:
  bash-data:
  python-lib-data:
```

### /src/main.py

とりあえず疎通確認ができるように公式のコードをコピペ

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello World"}
```

### /pyproject.toml

poetry initで自動作成できるが、dockerでやるのも手間なので手動で作成しておくのが無難だと思います。今回も手動で作成します。(最低限のライブラリのみ記載)

```toml
[tool.poetry]
name = "my_project"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.12"
fastapi = "^0.111.0"
uvicorn = "^0.29.0"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```

### ビルドと起動

```shell
docker compose up --build -d
```

下記にアクセスして、接続できたら環境の構築は完了です。

http://localhost:8000/

このままだとライブラリのバージョンが環境によって不揃いになってしまいます。

なので、poetry.lockファイルを生成しましょう。

コンテナに入る

```shell
docker compose exec -it fastapi /bin/bash

コンテナ内でロックファイルを生成
root@5e1559735f8a:/src# poetry lock
```

ルートにロックファイルが生成されます。

```
project_root
├── _docker
│   ├── nginx
│   │   └── nginx.conf
│   └── python
│       └── Dockerfile
├── src
│   └── main.py
├── .env
├── pyproject.toml
├── poetry.lock <-これが作成されます。
└── docker-compose.yml
```

ビルドする際にもlockファイルをコンテナにコピーするようにします。

```dockerfile
FROM python:3.12.3

SHELL ["/bin/bash", "-c"]

# 作業ディレクトリの設定
WORKDIR /src

RUN apt-get update && \
    apt-get install -y build-essential libffi-dev && \
    apt-get install -y sqlite3

RUN pip install --upgrade pip

# 必要なファイルのコピー
COPY ./pyproject.toml /src/pyproject.toml
COPY ./poetry.lock /src/poetry.lock // 追加

# Poetryのインストール
RUN curl -sSL https://install.python-poetry.org | python -

ENV PATH="/root/.local/bin:$PATH"

# Poetryの設定と依存関係のインストール
RUN poetry config virtualenvs.create false && \
    poetry install --no-interaction --no-root

# アプリケーションの起動コマンドを設定
ENTRYPOINT ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--reload"]
```

再ビルド

```shell
docker compose build --no-cache
docker compose up -d

キャッシュあってもいいなら
docker compose up --build -d
```

再度、コンテナを起動してlocalhost:8000にアクセスしてみましょう。
ブラウザに下記が表示されればOKです。
```
{"message":"Hello World"}
```
これで基本的な環境はできたかと思います。

## コマンドの簡略化

怠惰なので、長いコマンドをいちいち打ちたくないです。

例えばコンテナ内に入るときも

```shell
docker compose exec -it fastapi /bin/bash
```

だるすぎます。

なので、makeコマンドで下記のように簡略化します。

```shell
make api
```

まずはmakeコマンドをローカルとコンテナ内で使用することを考えます。

makefileにはincludeが備わっていることに加え、条件分岐も使用できます。

この辺を応用して、ローカルとコンテナ内でmakeコマンドを別管理していきます。

まずは本体のmakeファイルの作成をローカルで行います。

/makefile

```makefile
ifeq ($(MAKE_ENV), container)
    include makefile.container
else
    include makefile.local
endif
```

環境変数MAKE_ENVに値を持たせて、環境によってinclude先を分けます。

Dockerfileの修正

```dockerfile
FROM python:3.12.3

SHELL ["/bin/bash", "-c"]

# 作業ディレクトリの設定
WORKDIR /src

RUN apt-get update && \
    apt-get install -y build-essential libffi-dev && \
    apt-get install -y sqlite3

RUN pip install --upgrade pip

# 必要なファイルのコピー
COPY ./pyproject.toml /src/pyproject.toml
COPY ./poetry.lock /src/poetry.lock

# Poetryのインストール
RUN curl -sSL https://install.python-poetry.org | python -

# 環境変数の設定
ENV MAKE_ENV=container // 追加
ENV PATH="/root/.local/bin:$PATH"

# Poetryの設定と依存関係のインストール
RUN poetry config virtualenvs.create false && \
    poetry install --no-interaction --no-root

# アプリケーションの起動コマンドを設定
ENTRYPOINT ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--reload"]
```

ここからは実際のコマンドが記載されたmakeファイルを環境ごとに作成します。

- makefile.local
- makefile.container

/makefile.local

```makefile
.PHONY: help print build-local up down api
.DEFAULT_GOAL := help

print: ## 分岐テスト用
	echo "ローカル"

build: ## Build docker image to local
	docker compose build --no-cache

up: ## Start docker container
	docker compose up -d

down: ## Stop docker container
	docker compose down

api: ## Run python container
	docker compose exec -it fastapi /bin/bash
```

/makefile.container

```makefile
.PHONY: help print
.DEFAULT_GOAL := help

print: ## 分岐テスト用
	echo "コンテナ"
```

これで完了です。

再ビルドしましょう

```shell
make build
```

下記のように環境によって同じコマンドでも分けて使用できれば完了です。

ローカル

```shell
make print
//出力
echo "ローカル"
ローカル
```

コンテナ

```shell
make api
//出力
docker compose exec -it fastapi /bin/bash

make print
// 出力
echo "コンテナ"
コンテナ
```

poetryやこの後(記載予定)のマイグレーションコマンドなど真価を発揮するのでmakeファイルは作成しておいて損はないと思います。