TrayPage Docs

Docker / VPSでセルフホストする

docker composeで1台のサーバーにTrayPageを立てる手順。ローカルで試す、本番VPS、外部のPostgres・S3、バックアップ、更新。

1台のサーバー(VPS、社内サーバー、手元のPC)で、docker compose up だけでTrayPageを動かします。既定ではすべてを1台に閉じ込めるので、外部サービスに依存せず、閉じたネットワークでも動きます。

ブラウザ / AIエージェント
        │
        ▼
   Caddy(TLS終端・2つのホスト名)
        │  アプリ用 / 閲覧用(ページ本文だけを配信)
        ▼
   app(ghcr.io/8d-inc/tray-page)
        │
        ├── Postgres(同梱。外部のマネージドPostgresに置き換え可)
        └── SeaweedFS(S3互換。外部のS3 / R2に置き換え可)
サービス役割データ(volume)
caddy入口。https:// のoriginなら、Let's Encryptで証明書を自動取得・更新するcaddy-data(証明書)、caddy-config
appTrayPage本体(Web画面・REST API・MCP・共有ページ)。起動時にDBのマイグレーションを自動で適用するなし
postgresDB(profile postgres)postgres-data
seaweedfsページ本文のS3互換ストレージ(profile s3)。起動時にbucketを作るseaweedfs-data
mailpitローカルで試すときのメールの受け口(profile mailpit)なし

どのサービスを起動するかは、.env の COMPOSE_PROFILES で決めます。設定はすべて .env に書きます。一式は、TrayPageのリポジトリの deploy/docker/(compose.yaml・Caddyfile・.env.example)にあります。

必要なもの

  • Docker Engine 24 以降と Docker Compose v2.20 以降(docker compose version で確認)
  • 本番で使う場合: ホスト名(ドメイン)2つと、外から80番・443番に届くサーバー

ローカルで試す

TLSなしで動かします。

cd deploy/docker
cp .env.example .env
sed -i.bak "s/^BETTER_AUTH_SECRET=.*/BETTER_AUTH_SECRET=$(openssl rand -hex 32)/" .env && rm .env.bak
docker compose up -d
  1. http://localhost:3000 を開き、メールアドレスを入力してログインリンクを送ります。

  2. http://localhost:8025(同梱のMailpit)に届いたメールのリンクを開きます。

  3. MCPクライアントを接続します。MCPのURLは http://localhost:3000/api/mcp です。

    claude mcp add --transport http --scope user traypage http://localhost:3000/api/mcp
  4. MCPでページを作成して公開し、共有URL(http://localhost:3000/share/...)を開きます。ページ本文は閲覧用ホスト http://view.localhost:3000 から、隔離された状態で表示されます。

*.localhost は、ブラウザが自動で自分のPCに解決します。ポートを変えるときは、APP_ORIGIN・VIEWER_ORIGIN・HTTP_PORT の3つを同じ番号にそろえてください。

本番VPSで動かす

  1. ホスト名を2つ決め、DNSのA / AAAAレコードをサーバーのIPに向けます(例: traypage.example.com と view.traypage.example.com)。閲覧用ホストは、利用者やAIが書いた任意のHTMLを配信するので、アプリ用とは別のホスト名が必須です。可能なら別のドメイン(例: traypage-content.example.net)にすると、cookieの共有範囲からも切り離せます。

  2. ファイアウォールで80番・443番(TCP)を開けます。80番は、証明書の取得とhttpsへのリダイレクトに使います。

  3. .env を設定します。

    COMPOSE_PROFILES=postgres,s3
    TRAYPAGE_IMAGE=ghcr.io/8d-inc/tray-page:v1.2.3
    APP_ORIGIN=https://traypage.example.com
    VIEWER_ORIGIN=https://view.traypage.example.com
    HTTP_PORT=80
    HTTPS_PORT=443
    BETTER_AUTH_SECRET=<openssl rand -hex 32>
    POSTGRES_PASSWORD=<openssl rand -hex 24>
    S3_ACCESS_KEY_ID=traypage
    S3_SECRET_ACCESS_KEY=<openssl rand -hex 24>
    SMTP_HOST=smtp.example.com
    SMTP_PORT=587
    SMTP_USER=...
    SMTP_PASSWORD=...
    EMAIL_FROM=TrayPage <noreply@example.com>
  4. docker compose up -d を実行します。初回は、Caddyが証明書を取得するまで数十秒かかります(docker compose logs -f caddy で確認)。

POSTGRES_PASSWORD と S3_* は、初回起動の前に決めてください。DBのvolumeを作った後に POSTGRES_PASSWORD を変えても、DB側のパスワードは変わりません。

現在のバージョンでは、ログインリンクを受け取れる人は誰でもアカウントを作れます。社内だけで使う場合は、ファイアウォールやVPNでアクセス元を絞ってください(セキュリティ上の注意)。

メール

ログインのリンクと招待にメールを使います。次の順で使われます。

設定送り方
RESEND_API_KEYResendのAPI
SMTP_HOST(SMTP_PORT・SMTP_SECURE・SMTP_USER・SMTP_PASSWORD)SMTP。SMTP_SECURE=true で465番(最初からTLS)、それ以外はSTARTTLS

送信元は EMAIL_FROM です。本番では、COMPOSE_PROFILES から mailpit を外してください。

外部のPostgresとS3を使う

PostgresとS3互換ストレージは、.env を変えるだけで外部のサービスに置き換えられます。該当するprofileを COMPOSE_PROFILES から外すと、同梱のコンテナは起動しません。

Postgres(RDS・Cloud SQL・Neon・PlanetScaleなど)

COMPOSE_PROFILES=s3
DATABASE_URL=postgres://traypage:password@db.example.com:5432/traypage?sslmode=require
DATABASE_POOL_MAX=10

DB(空でよい)とユーザーを先に作っておきます。テーブルはアプリが起動時に作ります。複数台で同時に起動しても、1台だけが適用します。

S3互換ストレージ(AWS S3・Cloudflare R2など)

bucketを先に作っておきます。非公開のままでかまいません(アプリが署名付きのリクエストで読み書きします)。

# AWS S3
COMPOSE_PROFILES=postgres
S3_ENDPOINT=https://s3.ap-northeast-1.amazonaws.com
S3_REGION=ap-northeast-1
S3_BUCKET=traypage-pages
S3_ACCESS_KEY_ID=AKIA...
S3_SECRET_ACCESS_KEY=...
# Cloudflare R2(S3 API)
COMPOSE_PROFILES=postgres
S3_ENDPOINT=https://<account_id>.r2.cloudflarestorage.com
S3_REGION=auto
S3_BUCKET=traypage-pages
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...

既定はpath-style(endpoint/bucket/key)です。virtual-hosted styleが必要なサービスでは S3_ADDRESSING_STYLE=virtual にします。両方を外部にする場合は COMPOSE_PROFILES=(空)にします。

更新

docker compose pull
docker compose up -d

DBのマイグレーションは、新しいイメージの起動時に自動で適用されます。更新の前にバックアップを取ってください。

イメージのタグ

タグ中身
vX.Y.Zリリース。本番ではこれで固定する
vX.Yそのマイナーバージョンの最新のリリース
latest最新のリリース(プレリリースを除く)
edgedevelop ブランチの最新。動作確認用
sha-<commit>特定のコミット

linux/amd64 と linux/arm64 に対応しています。

バックアップと復元

バックアップするものは3つです。

対象方法
DB(Postgres)pg_dump
ページ本文(seaweedfs-data volume、または外部のS3 / R2)volumeをtarで固める。外部のS3 / R2は、そのサービスのバージョニングやレプリケーションを使う
.env安全な場所にコピーする。特に BETTER_AUTH_SECRET を失うと、ログイン中のセッションがすべて無効になる

DB、ページ本文の順に取ってください。ページの版は一度書いたら変わらず、本文を書いてからDBに行を作るので、この順なら「DBにあるのに本文がない」状態になりません。

以下は deploy/docker で実行します。volumeの名前は <project名>_<volume名>(既定のproject名は traypage-selfhost)です。

# 1. DB(アプリを止めなくてよい)
docker compose exec -T postgres pg_dump -U traypage -d traypage -Fc > traypage-db-$(date +%F).dump

# 2. ページ本文(書き込みを止めてからvolumeを固める)
docker compose stop app seaweedfs
docker run --rm -v traypage-selfhost_seaweedfs-data:/data:ro -v "$PWD":/backup alpine \
  tar czf /backup/traypage-pages-$(date +%F).tar.gz -C /data .
docker compose start seaweedfs app

caddy-data(証明書)はバックアップしなくても、起動時に取り直せます(Let's Encryptの発行回数の上限には注意してください)。

復元

docker compose up -d postgres seaweedfs
docker compose stop seaweedfs

# ページ本文
docker run --rm -v traypage-selfhost_seaweedfs-data:/data alpine find /data -mindepth 1 -delete
docker run --rm -v traypage-selfhost_seaweedfs-data:/data -v "$PWD":/backup:ro alpine \
  tar xzf /backup/traypage-pages-YYYY-MM-DD.tar.gz -C /data

# DB(既存のテーブルを消してから入れ直す)
docker compose exec -T postgres pg_restore -U traypage -d traypage --clean --if-exists --no-owner \
  < traypage-db-YYYY-MM-DD.dump

docker compose up -d

片付け

docker compose down        # コンテナを止める(データは残る)
docker compose down -v     # volume(DB・ページ本文・証明書)も消す。元に戻せない

うまく動かないとき

  • docker compose ps で全サービスが running(healthy)か確認します。docker compose logs app でアプリのログを見ます。
  • ログインリンクが届かない: SMTP_* を確認します(docker compose logs app に送信の失敗が出ます)。ローカルでは http://localhost:8025 を見ます。
  • 共有ページの本文が表示されない: VIEWER_ORIGIN が APP_ORIGIN と別のホスト名で、ブラウザから開けるかを確認します。
  • 証明書が取れない: DNSがこのサーバーを向いているか、80番・443番に外から届くかを確認します(docker compose logs caddy)。

すべての設定項目は 設定項目のリファレンス にあります。