PYTHON / POSTGRESQL

FastAPIとPostgreSQLを日本のサーバーで公開する手順

GitHubからのデプロイ、DB接続、保存確認まで

データを保存するFastAPIアプリを公開するときは、APIを動かすサーバーに加えて、保存先も用意します。PostgreSQLを使う構成なら、URLへアクセスできても、DBにつながっていなければ保存はできません。アプリ内の一時ファイルへ保存していると、再デプロイでデータを失うこともあります。

この記事では、短いメモを保存・一覧表示するAPIを作り、起動・DB接続・保存を順に確認します。公開先の具体例には、東京リージョンのPaaSであるKamuiDashを使います。要点は、依存関係の準備、テーブルの初期化、APIの起動を分けることです。

サンプルはソース一式をダウンロードできます。PythonのコードとGitの基本操作が分かる方を想定しています。手順の確認には実在の個人情報を使わず、短いテスト用メモだけを保存してください。

この記事の内容

APIを動かす場所と、データを残す場所を分ける

FastAPIはリクエストを受けて処理し、PostgreSQLがメモを保存します。ブラウザやクライアントからDBへ直接つなぐ構成にはしません。

保存の経路 · 概念図
クライアントHTTPSと認証ヘッダーでAPIへ
FastAPI入力検証・SQLの実行
PostgreSQLメモを永続保存
DBの接続情報はサーバーで扱います。クライアントへ直接渡しません。

アプリ内の変数はプロセスの終了で消えます。KamuiDashのアプリのディスクも一時領域なので、再起動やデプロイをまたいで残したいデータはDBへ、アップロード画像などはオブジェクトストレージへ保存します。料金・仕様の「一時ディスク」の説明も確認してください。

ここでは1アプリ・1プロセスの小さな例に絞ります。初期化用のSQLは用意しますが、既存テーブルの変更を追跡する仕組みは含みません。機能を継続して増やす段階ではAlembicなどによるマイグレーション管理へ進めます。

最小のFastAPIサンプルを用意する

ダウンロードしたフォルダーには、次のファイルが入っています。GitHubには、このフォルダーの中身をリポジトリのルートとして保存する想定です。

Text
requirements.txt   Pythonパッケージ
main.py            APIと起動処理
init_db.py         初回のテーブル作成
.gitignore         .envなどの除外

依存関係をrequirements.txtに固定する

Text
fastapi==0.128.8
uvicorn==0.39.0
psycopg[binary]==3.2.13

FastAPIはAPI、UvicornはHTTPサーバー、PsycopgはPostgreSQLへの接続に使います。掲載版の組み合わせでローカル確認しています。将来更新するときは、バージョンを変えた後に保存・読み取りまで再確認してください。

PORTを使って起動し、保存APIには認証を付ける

Python
import hmac
import os
from typing import Optional

import psycopg
import uvicorn
from fastapi import Depends, FastAPI, Header, HTTPException
from pydantic import BaseModel, Field
from psycopg.rows import dict_row

app = FastAPI(title="Small Notes API")

def connection():
    # Context managers commit on success, roll back on error, and close.
    return psycopg.connect(os.environ["DATABASE_URL"], connect_timeout=5,
                           row_factory=dict_row)

def authorize(authorization: Optional[str] = Header(default=None)):
    token = os.environ.get("DEMO_API_TOKEN", "")
    if not token:
        raise HTTPException(status_code=503, detail="API token is not configured")
    expected = "Bearer " + token
    if not hmac.compare_digest((authorization or "").encode(), expected.encode()):
        raise HTTPException(status_code=401, detail="Authentication required")

class Note(BaseModel):
    body: str = Field(min_length=1, max_length=500)

@app.get("/healthz")
def health():
    return {"ok": True}

@app.get("/readyz", dependencies=[Depends(authorize)])
def ready():
    with connection() as conn:
        conn.execute("SELECT 1")
    return {"database": "ok"}

@app.get("/notes", dependencies=[Depends(authorize)])
def list_notes():
    with connection() as conn:
        return conn.execute("SELECT id, body FROM notes ORDER BY id DESC LIMIT 50").fetchall()

@app.post("/notes", status_code=201, dependencies=[Depends(authorize)])
def create_note(note: Note):
    with connection() as conn:
        return conn.execute("INSERT INTO notes (body) VALUES (%s) RETURNING id, body",
                            (note.body,)).fetchone()

if __name__ == "__main__":
    uvicorn.run("main:app", host="0.0.0.0", port=int(os.getenv("PORT", "8000")))

0.0.0.0で待ち受け、プラットフォームから渡されるPORTを読みます。127.0.0.1だけで待ち受けると、アプリの外から接続できません。KamuiDashのPython設定でも、PORTの利用とセットアップコマンドの明示が案内されています。

/healthzはAPIプロセスの応答だけを確認します。DBへの接続は認証付きの/readyzで別に確認します。こうすると、サーバーが起動しないのか、DBが使えないのかを分けて調べられます。

この例は1つの共通トークンを使う学習用APIです。利用者ごとのアカウントや権限管理はありません。一般利用者向けに公開する場合は、用途に合う認証とレート制限を加えてください。トークンをブラウザ配信するJavaScriptへ書き込んで秘密を保つことはできません。

初回作成用のSQLを分ける

Python
from main import connection

with connection() as conn:
    conn.execute("""
        CREATE TABLE IF NOT EXISTS notes (
            id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
            body text NOT NULL CHECK (length(body) BETWEEN 1 AND 500)
        )
    """)
print("notes table is ready")

IF NOT EXISTSなので、同じスクリプトを再実行しても既存のメモは削除しません。ただし、テーブルが存在するときに列の定義を更新する機能もありません。既存のDBへ流用せず、専用の空DBから始めます。

ローカルで保存と読み取りを確認する

まず、自分が用意したローカルPostgreSQLに、サンプル専用の空DBを作成します。以下の接続先・ユーザー名は例です。外部の本番DBをこの動作確認に使う必要はありません。

Terminal
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

export DATABASE_URL='postgresql://demo_user:local_password@127.0.0.1:5432/notes_demo'
export DEMO_API_TOKEN='local-demo-only-change-before-publication'
python init_db.py
python main.py

別のターミナルでも同じDEMO_API_TOKENを設定し、次を実行します。

Terminal
curl --fail http://127.0.0.1:8000/healthz

curl --fail http://127.0.0.1:8000/readyz \
  -H "Authorization: Bearer $DEMO_API_TOKEN"

curl --fail http://127.0.0.1:8000/notes \
  -H "Authorization: Bearer $DEMO_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"body":"最初の保存テスト"}'

curl --fail http://127.0.0.1:8000/notes \
  -H "Authorization: Bearer $DEMO_API_TOKEN"

保存時はHTTP 201、一覧はHTTP 200で、保存した本文を含むJSONが返る想定です。認証ヘッダーを外した/notesへのアクセスは401になります。DBの接続URLやトークンをログに出さずに、ここまで確認してからGitHubへ保存します。

KamuiDashでDBとPythonアプリを設定する

KamuiDashのプロジェクトにPostgreSQLを作成し、そのDBをアプリ作成時に選びます。DB接続の公式ガイドによると、DATABASE_URLなどの接続情報が環境変数として設定されます。ローカル用のURLをそのままコピーする必要はありません。

KAMUIDASH · SCREENSHOT
KamuiDashのアプリ作成画面。ランタイム言語とGitHubリポジトリを選択する項目。
アプリ作成時のランタイム選択画面。FastAPIはWebサーバーのpythonを選びます。画像は既存の公開記事で使用している実画面です。
項目 このサンプルの設定 確認すること
種類・言語 Webサーバー/Python 静的なWebページとして作らない
ソース 対象のGitHubリポジトリとブランチ サンプル一式が入ったコミットを選ぶ
ルートディレクトリ 空欄 main.pyがリポジトリ直下にある前提
セットアップコマンド pip install -r requirements.txt 依存関係をインストールする
起動前コマンド python init_db.py 専用DBに初回テーブルを用意する
起動コマンド python main.py PORTで待ち受ける
ヘルスチェック /healthz 認証不要で200を返す
データベース 作成したPostgreSQL DATABASE_URLが設定される
追加の環境変数 DEMO_API_TOKEN ローカルと異なる十分長い値を設定する

サンプルを既存リポジトリのサブフォルダーへ入れた場合は、そのフォルダーをルートディレクトリに指定します。コマンドは選択したディレクトリからの相対位置で考えます。

配置時の3つの処理 · 概念図
セットアップrequirements.txtから準備
起動前専用DBにテーブルを用意
起動PORTでHTTP待ち受け
初回作成スクリプトは変更履歴の管理を代替しません。

公開後は、URL・保存・更新を別々に確認する

デプロイが成功したら、発行されたHTTPSのURLに対してローカルと同じ順序で確認します。最初に/healthz、次に/readyz、メモの作成、一覧取得です。

続けて、メモを1件残したままソースを小さく修正して再デプロイし、同じメモを読めるか確認します。これで、APIのプロセスが入れ替わってもデータがDBに残る構成かを確かめられます。

KAMUIDASH · SCREENSHOT
KamuiDashの静的サイトのアプリ詳細。稼働状態、公開URL、GitHubブランチ、コミットごとのデプロイ履歴。
アプリ詳細画面の例。これは既存の静的サイトの画面ですが、公開URLとデプロイ履歴を照合する見方の参考になります。サンプルAPIの実行結果を示す画像ではありません。

起動しないときに見る場所

症状 先に確認する項目
ModuleNotFoundError セットアップのログとrequirements.txtの位置
公開URLへ接続できない 起動ログ、0.0.0.0、PORT、ヘルスチェックのパス
/healthzは200、/readyzは失敗 DBの稼働状態、アプリへのDB接続設定
relation "notes" does not exist 起動前コマンドの実行結果と、接続しているDB
/notesが401 本番のトークンとリクエストの認証ヘッダー
アクセスが増えるとDB接続で失敗 同時接続数、プロセス数、DBプランの接続上限

サンプルは1リクエストごとに接続を開き、処理の終了時に閉じます。常に接続を保持する方式ではありませんが、同時アクセスが多ければ接続上限には達します。本番では接続プールの上限を、DBの上限とプロセス・レプリカ数を含めて決めます。接続数を増やしたときの検証記事も判断材料になります。

よくある疑問

無料でずっと公開できますか?

KamuiDashには対象アプリ・DBの基本料金が30日間無料のトライアルがあります。継続利用は有料です。個人プランのNanoアプリ1台とNano DBを30日使う場合、基本料金の合計は税抜$9で、利用量に応じた料金などは別途です。詳しい条件は公式の料金・仕様で確認できます。

SQLAlchemyやAlembicは必須ですか?

この小さな例の起動には必須ではありません。テーブルの変更や複数環境の運用が始まったら、変更履歴を管理する仕組みが必要です。CREATE TABLE IF NOT EXISTSだけで継続運用を済ませないようにします。

DBも日本にあるなら、すべてのデータが国内だけで処理されますか?

外部認証、メール、監視、AI APIなどを組み合わせると、それぞれへの送信が発生します。東京リージョンでアプリとDBを動かすことと、システム全体のデータ所在は分けて確認します。

次の機能を足す前に、保存できる最小構成を残す

ここまでで用意するのは、起動・DB接続・保存・一覧取得・再デプロイ後の読み取りを確かめられる構成です。この状態をGitに残しておけば、機能追加で問題が出たときにも、どこから変わったかを比較できます。

検証範囲:添付サンプルのAPIとDB操作はローカルで確認しています。KamuiDashの設定は2026年11月2日の公式資料に基づき、このサンプルの新規クラウドデプロイは実施していません。

参考:FastAPIのサーバー起動、Psycopgの接続とトランザクション。

KamuiDashは、GitHubやAI連携からWebアプリを公開できる東京リージョンのPaaSです。