Files
playchat/docs/deploy-lightsail.md
colcaroli 20cb449f76 Add Caddy for automatic HTTPS in front of the frontend/backend stack
Caddy now owns ports 80/443 and reverse-proxies to the frontend
container (which was publishing 80 itself, conflicting with Caddy),
handling Let's Encrypt certificate issuance/renewal automatically.
Fix the Caddyfile's upstream name (it referenced a nonexistent "app"
service instead of "frontend") and update the Lightsail deploy guide's
HTTPS section to match this setup instead of the old manual-certbot
approach.
2026-08-18 21:22:53 +09:00

9.8 KiB
Raw Permalink Blame History

AWS Lightsail へのデプロイ

このアプリを AWS Lightsail の「インスタンス」(Ubuntu VPS)上で、Docker Compose を使って動かす手順。 SQLite・ローカルナレッジベース・Discord Bot・APScheduler がすべて 1 プロセス(バックエンドコンテナ)内で常駐するため、 Lightsail Containers(マネージドコンテナサービス)よりも、状態を持てる通常のインスタンス構成の方が相性が良い。

構成

Internet
   │  :80 / :443
   ▼
[caddy コンテナ] Let's Encrypt証明書の自動取得・更新、HTTP→HTTPSリダイレクト
   │  :80(コンテナ間通信のみ、ホストには公開しない)
   ▼
[frontend コンテナ] nginx
   ├─ 静的ファイル (Vue ビルド成果物) を配信
   └─ /chat, /api, /memory, /logs, /health を backend にリバースプロキシ
   │
   ▼
[backend コンテナ] FastAPI (uvicorn)
   └─ SQLite (playchat.db) / knowledge_base を Docker Volume に永続化

frontend/src/api/client.ts はすべて相対パスで API を呼ぶ実装になっているため、 本番でも frontend と backend を同一オリジン(nginx 経由)で配信すれば CORS 設定は事実上不要。

リポジトリには以下を追加済み:

1. Lightsail インスタンスの作成

  1. Lightsail コンソール → 「インスタンスの作成」
  2. プラットフォーム: Linux/Unix、設計図: OS のみ → Ubuntu 22.04 LTS
  3. プラン: 2GB RAM / 1vCPU($10/月)以上を推奨 (フロントエンドの npm run build (vue-tsc) はビルド時にメモリを食うため、1GB プランだと OOM で失敗することがある)
  4. インスタンス名を付けて作成

作成後、インスタンス詳細ページの「ネットワーキング」タブで:

  • 静的 IP をアタッチ(再起動してもIPが変わらないようにする。ドメインを使う場合は必須)
  • ファイアウォールで 80 (HTTP) と 443 (HTTPS) を許可(Caddyの証明書取得とHTTPS配信に両方必要。22/SSH はデフォルトで開いている)

2. SSH 接続 & Docker のインストール

Lightsail コンソールの「接続」タブからブラウザ SSH で入るか、ダウンロードした鍵で接続する。

sudo apt update && sudo apt upgrade -y

# Docker 公式インストールスクリプト
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker

# Docker Compose Plugin の確認(get.docker.com で入る場合が多い)
docker compose version

3. リポジトリの取得と設定

リポジトリは自前の Git サーバー(colc-git.duckdns.org、Gitea/Forgejo系)でホストしている。 DuckDNS 経由の公開IPで、HTTPS(443)・SSH(22) とも外部から到達可能なので、Lightsail インスタンスからも VPN 等は不要で直接クローンできる。認証方法は以下のどちらか。

方法A: SSH デプロイキー(推奨・pull専用なら安全)

ssh-keygen -t ed25519 -C "lightsail-playchat" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub

表示された公開鍵を colc-git.duckdns.org 側のリポジトリ設定の Deploy Keys に Read only で登録してから:

git clone git@colc-git.duckdns.org:colcaroli/playchat.git
cd playchat

方法B: HTTPS + アクセストークン

colc-git.duckdns.org 側で個人アクセストークン(PAT)を発行し:

git clone https://colc-git.duckdns.org/colcaroli/playchat.git
cd playchat
# Username / Password 入力を求められたら、Password 欄に発行したトークンを入力

トークンをキャッシュしたい場合は git config credential.helper cache(メモリに一時保存。 store は平文保存になるため非推奨)。

.env の設定

cp backend/.env.example backend/.env
nano backend/.env   # APIキーやモデル名など本番用の値を設定

backend/.env で最低限確認する項目:

変数 本番での指針
OPENAI_API_KEY / ANTHROPIC_API_KEY 使うプロバイダーのキーを設定
DEFAULT_MODEL 使いたいモデル名
CORS_ORIGINS http://<静的IP> またはドメイン(nginx が同一オリジンで配信するので基本は素通りするが念のため合わせておく)
DATABASE_URL 編集不要(docker-compose.yml が /app/data/playchat.db に上書きし、Docker Volume に永続化する)
DISCORD_BOT_TOKEN 等 Discord Bot / 通知機能を使うなら設定。不要なら空のままでよい(未設定時は該当機能が無効)
SETTINGS_PASSWORD GUIの「設定」画面(後述)を使うなら必ず設定する。未設定だと設定画面は403で拒否される

backend/.env はコンテナに env_file として渡され、Git 管理対象外(.gitignore 済み)なので直接編集してよい。

GUIからのAPIキー管理

初回起動後は、OPENAI_API_KEY / ANTHROPIC_API_KEY / AWS認証情報などをSSHで.envを編集し直さなくても、 アプリ右上の「設定」ボタン(SETTINGS_PASSWORD によるBasic認証で保護)から変更できる。GUIで保存した値は SQLite(AppSettingテーブル、backend_dataボリュームで永続化)に保存され、.envの値より優先される。

注意: このパスワード保護は簡易的なものであり、HTTP平文通信の場合はBasic認証の資格情報がネットワーク上に そのまま流れてしまう。この機能を使うなら6. HTTPS化と合わせて運用すること。

4. ビルド & 起動

docker compose build
docker compose up -d
docker compose logs -f backend   # 起動ログ確認(Ctrl+C で抜ける。プロセスは動き続ける)

docker-compose.ymlにはCaddyが含まれており、80/443番ポートはCaddyが受け持つ(frontendは直接公開しない)。 Caddyfileにはドメイン名を設定する前提のため、動作確認は静的IPへの直接アクセスではなく 6. HTTPS化の手順でドメイン経由で行うこと。

停止・再起動・更新の運用コマンド:

docker compose ps                       # 状態確認
docker compose restart backend          # backend だけ再起動
git pull && docker compose up -d --build  # コード更新を反映

5. 永続化されるデータ

docker-compose.yml は以下を Named Volume にしている(docker compose down してもデータは消えない。docker compose down -v は消えるので注意):

  • backend_data … playchat.db(会話履歴・メモリ)
  • backend_knowledge_base … knowledge_base/(Web検索で蓄積したナレッジ含む)

バックアップ例:

docker compose exec backend sqlite3 /app/data/playchat.db ".backup /app/data/backup.db"
docker cp $(docker compose ps -q backend):/app/data/backup.db ./playchat-backup-$(date +%F).db

インスタンスごとのバックアップとしては、Lightsail の「スナップショット」機能(自動スナップショット設定可)も併用すると安心。

6. HTTPS 化(Caddy)

Caddy をリバースプロキシとしてfrontendの手前に置き、証明書の取得・更新を 自動化する(Let's Encrypt、ACME HTTP-01 チャレンジ)。手動でのcertbot操作は不要。

構成:

Internet
   │ :80 / :443
   ▼
[caddy コンテナ] ← Let's Encrypt証明書の自動取得・更新、HTTP→HTTPSリダイレクト
   │ :80 (コンテナ間通信、ホストには公開しない)
   ▼
[frontend コンテナ] nginx(静的配信 + backendへのAPIプロキシ)
   ▼
[backend コンテナ]

frontendはもう80番ポートをホストに公開せず(exposeのみ)、代わりにcaddyが80/443を受けて frontendへ内部転送する。ルートの Caddyfile にドメインを設定する:

your-domain.example.com {
    reverse_proxy frontend:80
}

前提条件:

  • ドメインの A レコード(IPv4)を Lightsail の静的 IP に向けておく(DuckDNS等の無料動的DNSでも可)
  • Lightsail ファイアウォールで 443 (HTTPS) を許可(1. Lightsail インスタンスの作成で追加済みのはず)

起動すると、Caddyがドメイン名を見て自動的にACMEチャレンジ(ポート80経由)で証明書を取得し、 以後は自動更新される。証明書はcaddy_dataボリュームに永続化されるので、docker compose down (-v無し)では消えない。

docker compose up -d
docker compose logs -f caddy   # 証明書取得ログを確認("certificate obtained successfully" 等)

https://your-domain.example.com/ にアクセスして確認する。

7. 常駐タスクについて

APScheduler(天気通知など)と Discord Bot は backend コンテナ起動時(FastAPI の lifespan)に単一プロセス内で動く。 restart: unless-stopped を設定しているため、インスタンス再起動やコンテナクラッシュ時も自動的に立ち上がる。 複数インスタンス/複数コンテナへのスケールアウトは想定していない(SQLite・APScheduler・Discord Bot が単一プロセス前提のため)。

コスト目安

  • Lightsail インスタンス 2GB プラン: 約 $10/月(固定IP・80/443 の転送量込み)
  • LLM API 利用料(OpenAI / Anthropic / Bedrock)は別途従量課金