SSD Nodes Learn Hosting plans →
ガイド Matt Connor著者 Matt Connor

AFFiNEを自己ホストする方法と必要なメモリ

Docker ComposeでAFFiNEを1台のVPSに構築する手順です。4つのコンテナ、固定するイメージタグ、データの保存先、バックアップ、2 GB RAMでできる範囲を確認できます。

自己ホストで AFFiNE を導入すると得られるもの

AFFiNE を自己ホストすると、自分で管理するサーバー上で Notion 形式のワークスペースを利用できます。構成は、アプリケーション、1 回だけ実行するマイグレーションジョブ、Postgres、Redis の 4 つのコンテナです。リアルタイム共同編集にも対応しており、自己ホストのワークスペースではデフォルトで最大 10 席を利用できます。インストールに必要なのは、1 つの compose ファイルと 1 つの JSON 設定ファイルです。検討が必要なのは、イメージタグ、ディスク構成、メモリ上限、そして前段に置くプロキシです。

AFFiNE では、ドキュメントエディターと無限キャンバスを同じワークスペースで利用できます。そのため、1 つのページをドキュメントとして読むことも、ホワイトボードのように広げて使うこともできます。何を運用するかまだ決めていない場合は、先に 自己ホスト可能な Notion 代替サービスの比較をお読みください。このガイドでは導入するサービスが決まっていることを前提とし、比較を繰り返すのではなく、AFFiNE を適切に運用する方法を説明します。

ここに記載する内容は、2026 年 8 月 8 日時点の AFFiNE の自己ホスト向けドキュメントと公開リリースファイルに基づいて確認しています。同日時点での最新の安定版は 0.27.3 で、2026 年 7 月 23 日に公開されました。それ以降の日付が付いた記述は、2026 年 10 月 12 日に一次情報源と照合して再確認しています。この時点での最新の安定版は 0.27.4 で、2026 年 8 月 18 日に公開されました。

4 つのコンテナの実際の役割

affine は、サーバーと Web クライアントを 1 つのイメージにまとめたものです。3010 番ポートで待ち受けます。

affine_migration は、node ./scripts/self-host-predeploy.js を実行してデータベースマイグレーションを適用し、終了するワンショットジョブです。アプリケーションはこのジョブに対する condition: service_completed_successfully を宣言しているため、マイグレーションが 0 以外のステータスで終了すると、affine はまったく起動しません。Web インターフェースが起動しない場合は、まずこのジョブのログを確認します。

postgres には、ドキュメント、ユーザー、ワークスペース、権限が保存されます。提供されるイメージは pgvector/pgvector:pg16 で、pgvector 拡張機能を組み込んだ通常の Postgres 16 です。pgvector は Postgres に vector 型を追加します。これは、テキストを意味で検索できるように、embedding を保存するための数値形式です。

redis は必須の依存関係です。サーバーとマイグレーションジョブは、どちらもこのコンテナのヘルスチェックが完了してから起動します。提供される compose ファイルで Redis に volume が割り当てられていない点に注意してください。docker compose down が発生すると、その内部の内容は何も残りません。つまり、ユーザーのコンテンツは保存されておらず、バックアップも必要ありません。

Postgres イメージが stock postgres ではなく pgvector である理由

これは好みではなく、AFFiNE のスキーマ上の要件です。v0.27.4 では、schema.prisma が引き続き extensions = [pgvector(map: "vector")] を宣言していますが、embedding 用のカラムはありません。v0.27.3 で embedding カラムを vector(1024) 型として定義していた 4 つのモデルは、v0.27.4 には存在しません。提供された pgvector/pgvector:pg16 イメージをそのまま使用してください。extension の宣言は残っており、stock postgres:16 にはこの extension が含まれていないためです。

公式の v0.27.4 テンプレートでは pgvector イメージを使用しています。v0.21.0 のテンプレートでは、引き続き postgres:16 を使用していました。古いインストール環境では、イメージ行を編集するだけではアップグレードできません。pull する前に、AFFiNE self-host ドキュメントのアップグレードページを確認してください。

このタグについて、もう 1 点注意が必要です。pg16 は Postgres 16 を意味します。Postgres のメジャーバージョンは、単純に数字を変更できるものではありません。既存のデータディレクトリに対して pg17 に変更すると、Postgres は起動を拒否し、docker compose logs postgres に The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 のような行を出力します。メジャーバージョンを移行するには、PostgreSQL 17 のリリースノートに記載されている 3 つの方法のいずれかが必要です。ダンプして新しいデータディレクトリへリストアする方法、pg_upgrade、または論理レプリケーションです。単一の Compose インストールでは、通常、ダンプとリストアが最も簡単です。pg_upgrade では旧バージョンと新バージョンのサーバーバイナリを同時に配置する必要があるためです。

セルフホストのAFFiNEに必要なCPUとRAM

AFFiNEの要件ページでは、少なくとも4 CPUコアと2 GBのRAMが必要とされています。ドキュメントが10,000語を超えると、必要なメモリは4 GBに増えます。同じページには、メモリの主な用途も記載されています。同期システムとドキュメントのマージです。覚えておくべき数値は、10,000件の変更を含むドキュメントをマージすると、メモリ使用量が1 GBまで増える可能性があることです。

これを、2 GBプランで2人が編集する場合に当てはめて考えます。平均的な使用量なら問題ありません。PostgresとNodeプロセスは、余裕を残して制限内に収まります。問題はピーク時です。1回の大規模なマージで、サーバーのメモリ使用量が約1 GBまで増えることがあります。2 GBでswapのないサーバーでは、このピークにPostgresや常駐中の他のプロセスの使用量が加わると、メモリを使い果たす可能性があります。するとカーネルのout-of-memory (OOM) killerが、メモリを解放するためにプロセスを終了します。AFFiNEサーバーは最大のプロセスであるため、終了対象になる可能性が高くなります。ただし、Dockerのドキュメントには、Docker自体を含め、どのプロセスでも選択される可能性があると明記されています。

AFFiNEサーバーが終了した場合、同僚にエラーは表示されません。restart: unless-stopped が数秒以内にコンテナを再起動するため、ページが再読み込みされたように見えます。推測で済ませず、確認してください。

docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'

最初のコマンドのtrue、または2番目のコマンドにnodeを示すKilled process行があれば、バグではなくメモリ不足が発生したということです。対策は両方の側から行います。まずswapを追加し、OOM killerが動作する前にメモリの急増分を退避できるようにします。

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

free -hの結果は、swapの合計が2.0Giになっているはずです。swapによってAFFiNEが高速になるわけではなく、その目的でもありません。短時間の急増分を退避できるようにすることで、コンテナが停止する代わりに、処理が一時的に遅くなるだけで済む可能性を高めます。swapはバッファであり、停止を保証するものではありません。もう一方の対策は、マージ時にアプリケーションが必要とする領域までPostgresがキャッシュを拡大しないようにすることです。そのために使うのが、Composeサービスのメモリ制限です。両方を設定しても停止が続く場合は、ドキュメントが大きい環境向けにAFFiNEの要件ページが示している4 GBが必要です。

ストレージ容量は、はるかに予測しやすい項目です。AFFiNEは同じページで次の数値を公開しています。

グラフPublished AFFiNE storage figures, August 2026
このグラフのデータ
[
  {
    "label": "Server install",
    "gb": 1.5
  },
  {
    "label": "Postgres per 1,000 docs",
    "gb": 0.1
  },
  {
    "label": "Blob store per 1,000 uploads",
    "gb": 10
  }
]

サーバーのインストールには 1.5 GB必要です。約1,000語のドキュメント1,000件で増えるPostgresデータは 0.1 GBで、ほとんど無視できる量です。一方、アップロードしたファイル1,000件では 10 GB増えます。容量の大部分はここで決まります。これらは稼働中のインスタンスで測定した値ではなく、公開されている計画用の数値です。そのため、厳密な保証ではなく、傾向を示す値として扱ってください。重要なのはその傾向です。データベースは小さいままですが、ディスク容量はアップロードファイルによって決まります。

タグを固定して、compose ファイルを自分で作成する

公式のインストール手順では、curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml を使って用意済みのファイルをダウンロードします。これは動作します。ただし、この方法を利用する前に知っておくべき点があります。2026 年 8 月 8 日時点で、release 0.27.3 に添付されていたファイルは .env ファイルからパスを読み取り、${UPLOAD_LOCATION}、${CONFIG_LOCATION}、${DB_DATA_LOCATION} を使用していました。一方、ドキュメントのリファレンスページには、ストレージと PostgreSQL のデータを ./data 以下に置き、設定を ./config 以下に保持し、.env をまったく必要としない新しい構成が示されていました。release 0.27.4 では、添付ファイルもこの構成に変更され、2026 年 10 月 12 日時点で両者は一致しています。古いファイルからインストールした場合、パスは引き続き .env から読み込まれます。AFFiNE のアップグレードページでは、これらの設定を引き継がずに compose ファイルを置き換えると、Postgres が空のデータディレクトリで起動する可能性があると警告しています。ファイルを自分で作成すれば、この問題を完全に避けられます。いずれにしても、イメージを固定し、データベースのパスワードを設定するために編集が必要です。

mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env

Compose はプロジェクトディレクトリにある .env を自動的に読み込み、${DB_PASSWORD} を代入します。そのため、サポートスレッドに貼り付けるファイルにパスワードが表示されることはありません。この習慣は、運用するすべての stack で維持してください。理由については compose ファイルから Secret を除外する を参照してください。

ここで ~/affine/docker-compose.yml を作成します。

name: affine
services:
  affine:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine_server
    ports:
      - '127.0.0.1:3010:3010'
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
      affine_migration:
        condition: service_completed_successfully
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    restart: unless-stopped

  affine_migration:
    image: ghcr.io/toeverything/affine:stable
    container_name: affine_migration_job
    command: ['sh', '-c', 'node ./scripts/self-host-predeploy.js']
    volumes:
      - ./data/storage:/root/.affine/storage
      - ./config:/root/.affine/config
    environment:
      - REDIS_SERVER_HOST=redis
      - DATABASE_URL=postgresql://affine:${DB_PASSWORD}@postgres:5432/affine
      - AFFINE_INDEXER_ENABLED=false
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

  redis:
    image: redis:8-alpine
    container_name: affine_redis
    healthcheck:
      test: ['CMD', 'redis-cli', '--raw', 'incr', 'ping']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  postgres:
    image: pgvector/pgvector:pg16
    container_name: affine_postgres
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: affine
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: affine
      POSTGRES_INITDB_ARGS: '--data-checksums'
    healthcheck:
      test: ['CMD', 'pg_isready', '-U', 'affine', '-d', 'affine']
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

上流が提供するファイルとの違いは 4 つあります。それぞれに理由があります。

  • 127.0.0.1:3010:3010 は loopback address に対してのみポートを公開します。Docker 28.0.0 より前の release では、同じ L2 セグメント上のホストが localhost に公開されたポートへ接続できます。また、direct routing を設定していると、loopback への公開であっても container port が外部から到達可能になります。Docker 28.0.0 以降を使用し、セットアップ中は direct routing を無効にしてください。上流の '3010:3010' はすべての interface に bind します。多くの VPS image では、これに public interface も含まれます。
  • POSTGRES_HOST_AUTH_METHOD: trust は削除し、代わりにパスワードを設定しています。Trust authentication では、パスワードなしで affine user としてそのデータベースへの接続を受け入れます。これは private Compose network に限定されるため、別の container をその network に追加する日や、デバッグ中に 5432 を公開するまでは問題ありません。
  • redis:8-alpine は、latest に解決される bare redis に置き換えています。2026 年 8 月時点ではこれは Redis 8 です。この pin により、テスト済みの major version を維持し、無関係な docker compose pull の実行中に将来の Redis 9 が導入されることを防げます。
  • pgvector/pgvector:pg16 は、上記の理由により上流の設定から変更していません。

POSTGRES_PASSWORD が読み込まれるのは、Postgres が初回にデータディレクトリを作成するときだけです。すでに存在する instance では、docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" でパスワードを設定し、その後 DATABASE_URL を同じ値に更新してください。

設定は config/config.json に保存します

AFFiNE は config/config.json から設定を読み込みます。これは /root/.affine/config にマウントしたディレクトリです。このファイルは自動作成されないため、初回起動前に作成してください。エディターで ~/affine/config/config.json を開き、例のドメインを自分のドメインに置き換えて、次の内容を記述します。

{
  "$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
  "server": {
    "name": "Team workspace",
    "externalUrl": "https://affine.example.com"
  },
  "copilot": {
    "enabled": false,
    "byok": {
      "enabled": false
    }
  }
}

server.externalUrl には、ユーザーが実際にブラウザーで開くアドレスを指定します。AFFiNE はこの値を基に共有リンクとワークスペースへの招待を作成します。http://localhost:3010 のままにすると、送信した招待の宛先が受信者自身のマシンになり、そこで機能しません。初回起動前に公開 HTTPS アドレスへ変更してください。これにより、ファイルと管理パネルの設定が一致します。後から管理パネルで変更した設定はデータベースに保存され、このファイルには書き戻されません。

copilot は AI 機能を制御します。copilot.byok.enabled は BYOK の切り替えです。これを有効にすると、ワークスペースの所有者がワークスペース設定に自分のモデルプロバイダーのキーを入力できます。AFFiNE をセルフホストしても、AI サブスクリプションは付属しません。AI 機能を使用しない場合は、false のままにしてください。

スタックを起動します。

docker compose up -d
docker compose ps --all

docker compose ps --all には、affine_postgres と affine_redis が healthy、affine_server が running、affine_migration_job が exited (0) の状態で表示されるはずです。--all が重要です。これを指定しない場合、docker compose ps には実行中のコンテナだけが表示されるため、正常に完了したマイグレーションジョブは表示されません。マイグレーションジョブにそれ以外の終了コードが表示された場合は、その原因を調査してください。ログには停止した手順が記録されています。

docker compose logs affine_migration

忘れる前にイメージを固定する

stable は更新されるタグです。AFFiNE のリリースワークフローでは、安定版ビルドごとに複数のタグを付けます。ここで重要なのは次の2つです。stable はリリースごとに付け替えられます。stable- と git の短縮ハッシュを組み合わせたタグは、付け替えられません。stable のままにすると、6か月後の docker compose pull で別のイメージが取得され、意図しないタイミングでデータベースに対してマイグレーションが実行されます。テスト済みの正確なイメージを固定してください。

docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'

このコマンドを実行すると、ghcr.io/toeverything/affine@sha256: に続いて長いハッシュが表示されます。その文字列全体を、affine と affine_migration の両方にある image: 行へ貼り付けます。この2つは常に一致させてください。同じイメージを別の役割で使用するためです。一致しない場合、一方のスキーマへデータベースをマイグレーションしながら、別のスキーマを前提とするイメージでデータベースを提供することになります。アップグレードは、予期せず発生するものではなく、意図して行う編集になります。ダイジェストを変更し、バックアップを取得して、docker compose pull、docker compose up -d を実行します。

管理者アカウントを他のユーザーより先に作成する

新しいインスタンスで /admin を開くと、AFFiNE はアカウント作成ページに移動させます。サーバーに管理者がまだいないためです。この手順には招待コードもセットアップトークンもありません。最初に登録を完了したユーザーがサーバーの管理者になります。そのため、登録が完了するまでポートを閉じておく必要があります。

これが、上記の compose ファイルで 127.0.0.1 にバインドしている理由です。自分のマシンから SSH トンネル経由でアクセスします。

ssh -L 3010:127.0.0.1:3010 you@your-server-ip

この状態のままにして、ローカルのブラウザーで http://127.0.0.1:3010/admin を開きます。登録してログインしたら、トンネルを閉じます。この時点で初めて、インスタンスを公開名で公開しても安全です。同じ競合は他のセルフホストアプリでも発生します。最初のログイン時にホスト名に紐付いたパスキーを作成するアプリでは、さらに問題が深刻です。そのため、openGym をセルフホストする場合は、最初のアカウントを作成する前に TLS と最終的なドメインを確定しておく必要があります。

AFFiNE がデータを保存する場所

3 つのパスにすべてのデータが保存されます。いずれも作成したディレクトリ内にあります。

  • ./data/postgres は Postgres のデータディレクトリです。ドキュメント、ユーザー、ワークスペース、権限、管理パネルで保存したサーバー設定が含まれます。
  • ./data/storage はコンテナ内の /root/.affine/storage にマウントされ、アップロードされたすべてのファイルを保持します。
  • ./config は /root/.affine/config にマウントされ、移行ジョブが生成した config.json と private.key を保持します。

Upstream はここで名前付きボリュームではなく bind mount を使用しています。これは意図的な選択です。Docker に保存場所を確認しなくても、通常のコマンドでこれらのパスを tar 化してコピーできます。一方、ホスト上のファイル所有権は自分で管理する必要があります。この点が、bind mount と名前付きボリュームで説明するトレードオフです。

AFFiNE をバックアップする方法

バックアップが必要なものは 2 つあり、それぞれ方法が異なります。データベースは稼働中のサーバーなので、稼働中にファイルをコピーするとコピーが破損します。代わりにダンプを取得します。

mkdir -p ~/affine/backup
cd ~/affine
docker compose exec -T postgres pg_dump --format c --username affine affine \
  > backup/affine-$(date +%F).dump
ls -lh backup/

ダンプはコンテナ内でローカルソケットを使用して実行されるため、パスワードの入力を求められません。ls の出力でサイズを確認してください。数百バイト程度のファイルは、シェルがファイルだけを作成し、ダンプ自体は失敗したことを示します。この問題は 6 か月後に初めて発見されることがあります。-T も重要です。これがないと、Compose が端末を割り当ててバイナリストリームを破損させる可能性があります。

アップロードしたファイルは通常のファイルなので、tar でアーカイブします。

tar czf backup/storage-$(date +%F).tgz -C data storage
tar czf backup/config-$(date +%F).tgz config docker-compose.yml .env

バックアップには config ディレクトリ全体、Compose ファイル、.env を含めてください。ダンプにはこれらが含まれないためです。移行ジョブは config/private.key も作成するため、config.json だけをコピーすると、このファイルが抜けます。管理パネルで保存した設定はデータベースに格納されるため、上記のダンプにすでに含まれています。また、2026 年 10 月 12 日時点では、AFFiNE のバックアップページから、変更したサーバー設定を管理パネルでエクスポートすることもできます。3 つのファイルをすべてサーバー外へコピーしてください。保護対象と同じディスク上にあるバックアップは、バックアップとはいえません。このように、データベースのダンプと uploads ディレクトリの tar を分けて取得する方法を、運用するほかのステートフルコンテナでも繰り返してください。これは、サポートデスクとして Chatwoot をセルフホストする場合に、会話履歴と添付ファイルを安全に保つ方法でもあります。

復元と、公開手順にある 1 つの落とし穴

必要になる前に公式の復元手順を読み、内容を注意深く確認してください。2026年8月に公開されていた手順では、affine.backup という名前のファイルをコンテナにコピーした後、./pg.backup から復元していました。これらは異なる名前です。また、現在の compose ファイルがデータを ./data/postgres に保持しているにもかかわらず、./postgres ディレクトリを削除していました。2026年10月12日時点では、ページ全体で affine.backup が使われ、復元を検証するまで古い Postgres ディレクトリを保持するよう記載されています。そのため、この落とし穴は上流で修正されています。ただし、それ以前に保存した手順のコピーには、まだこの問題が残っています。いずれの場合も、スニペットに記載されたパスではなく、実際に使用したパスに従ってください。以下は、このガイドの構成に対する手順です。

cd ~/affine
docker compose down
sudo mv data/postgres data/postgres.old
docker compose up -d postgres
docker compose cp backup/affine-2026-08-08.dump postgres:/tmp/affine.dump
docker compose exec postgres pg_restore --format c --username affine \
  --dbname affine --verbose /tmp/affine.dump
docker compose up -d

rm ではなく mv である点に注意してください。コピーを保持していないデータベースに対して復元すると、1 つの誤ったコマンドでデータが完全に失われます。古いディレクトリを別の場所へ移動しておけば、このリスクをコストなしで回避できます。tar xzf backup/storage-2026-08-08.tgz -C data を使用して uploads も復元してください。復元しないと、すべてのドキュメントで添付ファイルが壊れて表示されます。次にログインし、画像を含むドキュメントを開いてください。これがテストになります。ブラウザーで開いていない復元データは、バックアップではなく単なるファイルです。

すでに運用しているプロキシの背後に AFFiNE を配置する

AFFiNE は WebSocket を使用します。これはオプションではありません。公式ドキュメントにも明記されているとおり、WebSocket は AFFiNE の同期およびコラボレーション機能の基盤です。そのため、接続をアップグレードできないプロキシを使用すると、編集内容の同期が停止していることに気付けないワークスペースになります。ページは表示され、ログインもできますが、一方のブラウザーで行った編集がもう一方に届きません。ブラウザーの開発者ツールで Network タブを開き、WS に絞り込みます。接続が何度も開いては閉じる場合、プロキシがアップグレードを転送していません。

すでに他のコンテナで Traefik を運用している場合、AFFiNE も通常のサービスとして追加できます。affine サービスから ports: ブロックを削除し、次を追加します。

    networks:
      - default
      - proxy
    labels:
      - 'traefik.enable=true'
      - 'traefik.docker.network=proxy'
      - 'traefik.http.routers.affine.rule=Host(`affine.example.com`)'
      - 'traefik.http.routers.affine.entrypoints=websecure'
      - 'traefik.http.routers.affine.tls.certresolver=letsencrypt'
      - 'traefik.http.services.affine.loadbalancer.server.port=3010'

ファイルの末尾で、services: と同じ階層に次を追加します。

networks:
  proxy:
    external: true

証明書リゾルバー名は、Traefik の設定で定義した名前と一致させる必要があります。また、loadbalancer.server.port はコンテナのポート 3010 であり、ホストのポートではありません。Traefik は追加設定なしで WebSocket 接続をプロキシするため、ほかに追加する設定はありません。スタックの他のアプリがすでに シングルサインオン用の Authentik の背後にある場合、このルーターに forward auth ミドルウェアを設定すると、ブラウザーから AFFiNE へのアクセスを制御できます。ただし、デスクトップアプリでのテストが完了するまでは無効にしてください。デスクトップアプリにはブラウザーのセッションがないため、同期に失敗します。1 つのインスタンスの背後で複数のアプリを運用する方法については、複数のアプリの前段に 1 つの Traefik を置くで説明しています。

nginx では、アップグレードを明示的に指定する必要があります。

location / {
    proxy_pass http://127.0.0.1:3010;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    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;
    client_max_body_size 100m;
}

nginx では client_max_body_size の既定値が 1 MB です。この行がないと、小さな写真より大きいアップロードはすべて 413 ステータスで失敗します。リクエストが AFFiNE に到達しないため、AFFiNE のログには何も記録されません。Caddy では reverse_proxy http://127.0.0.1:3010 の 1 行だけで済み、証明書と WebSocket のアップグレードも自動的に処理されます。

セルフホスト版に含まれない機能

チームを移行する前に、この点を正しく理解してください。

リアルタイムコラボレーションは利用できます。AFFiNE の公式ドキュメントでは、メモリ使用量の主な要因として同期システムとドキュメントのマージが挙げられているため、サイジングに関する説明もこの機能を前提としています。オフライン編集は、ローカルファーストのツールを求める人が多い理由です。デスクトップアプリケーションでは、セルフホストサーバーをワークスペース一覧に追加し、そのサーバーに対してログインできます。導入を決める前に、チームが依存するオフライン動作を正確にテストしてください。デスクトップアプリケーションでネットワークを切断した状態で編集し、再接続してから、2 台目のデバイスで結果を確認します。機能一覧は証拠ではありません。この一覧も例外ではありません。

サーバー側の全文検索は、提供される compose ファイルでは無効になっています。サーバーと migration job の AFFINE_INDEXER_ENABLED=false が設定されています。これを有効にするには、Manticore Search コンテナを追加する必要があります。サービスが 5 つになり、メモリ使用量も増えます。2 GB のサーバーでは、この変更によってリソース上限を超える可能性があります。クライアント内の検索は、現在開いているワークスペースでは引き続き利用できます。

ユーザーを招待する前に、2 つの制限を把握しておいてください。セルフホストのワークスペースに割り当てられるシートは最大 10 です。これを超えるには、AFFiNE の Team ライセンスが必要です。セルフホストインスタンスでの無制限の blob ストレージと blob サイズについては、ドキュメント上では予定されていますが、2026 年 10 月時点ではまだ完全には実装されていません。家庭や小規模チームでは、どちらも問題になりません。40 人を移行する予定なら、どちらも重要な制限になります。

アップグレード

まずリリースノートを読みます。特に 0.26 から 0.27 のようなマイナーバージョン更新では、互換性を損なう変更が入るため注意が必要です。作業前にデータベースとストレージディレクトリをバックアップしてください。次回の起動時にマイグレーションジョブがスキーマを変更し、元に戻せないためです。その後、固定している digest を変更し、docker compose pull に続けて docker compose up -d を実行します。docker compose logs -f affine_migration が正常に終了するまで監視します。終了後、docker image prune で古いレイヤーを削除します。

非常に古いインストールを使用している場合は、次の点にも注意してください。version 0.23.0 で、イメージ名が affine-graphql から affine に変更されました。そのため、それ以前の compose file では、pull でイメージを取得できるように image の行を書き換える必要があります。また、version 0.27.4 では、公式 compose template の Postgres、upload、configuration のパスが変更され、デフォルトのデータベース接続も変更されました。稼働中のインストールにそのまま上書きするのではなく、新しいファイルと既存のファイルを比較してください。

FAQ

AFFiNE コンテナが起動しないのはなぜですか?

affine サービスは affine_migration ジョブに condition: service_completed_successfully を指定しています。そのため、マイグレーションが 0 以外のステータスで終了すると、サーバーは起動せず、Web インターフェースも表示されません。どの手順で停止したかを確認するには、docker compose logs affine_migration を実行します。手動編集した Compose ファイルでも pgvector/pgvector:pg16 を使用していることを確認してください。標準の Postgres には pgvector 拡張機能が含まれていません。AFFiNE のスキーマでは現在もこの拡張機能が宣言されています。一方、v0.27.4 では v0.27.3 に存在した 4 つの埋め込みモデルが定義されなくなっています。

セルフホストの AFFiNE にはどの程度の RAM が必要ですか?

AFFiNE の要件ページでは、最低 4 CPU コアと 2 GB の RAM が必要とされています。ドキュメントが 10,000 語を超える場合は 4 GB が必要になり、10,000 件の変更を含むドキュメントをマージすると 1 GB に達することがあると説明されています。2 GB のサーバーでは、アイドル時の負荷ではなく、このピークが問題になります。カーネルの out-of-memory killer が停止するプロセスを選びます。最も大きなプロセスである AFFiNE サーバーが選ばれる可能性が高く、restart: unless-stopped がサーバーを再起動します。そのため、ユーザーにはエラーではなくページの再読み込みとして現れます。docker inspect affine_server --format '{{.State.OOMKilled}}' と sudo dmesg -T | grep -i 'out of memory' で確認し、短時間のスパイクに備えるバッファとして 2 GB の swap ファイルを追加してください。それでも停止が続く場合は、RAM を 4 GB に増やします。

AFFiNE はデータをどこに保存しますか?何をバックアップすればよいですか?

Compose ディレクトリ以下の 3 つのパスにすべてのデータが保存されます。./data/postgres はデータベース、./data/storage はアップロードファイル、./config は config.json と生成された private.key 用です。データベースをバックアップするには、ファイルをコピーするのではなく docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump を使用してください。実行中の Postgres は安全にコピーできないためです。このダンプには、管理パネルで保存した設定も含まれます。AFFiNE はそれらをデータベースに保存するためです。アップロードファイル用の ./data/storage を tar でアーカイブし、Compose ファイルと .env を含む config ディレクトリ全体も保持してください。これらのファイルはデータベースには含まれません。

セルフホストの AFFiNE でリアルタイム共同編集は機能しますか?

はい。追加の有効化は必要ありません。唯一の要件はリバースプロキシです。同期には WebSocket 接続を使用するためです。nginx では proxy_http_version 1.1 に加えて Upgrade と Connection: upgrade のヘッダーを設定します。Traefik と Caddy では追加設定なしでこれらの接続を中継できます。プロキシが WebSocket 接続をアップグレードしない場合、ワークスペースの読み込みとログインは正常に完了しますが、一方のブラウザーで行った編集が別のブラウザーに表示されません。

標準の Postgres イメージで AFFiNE を実行できますか?

pgvector 拡張機能を組み込んだ Postgres 16 である pgvector/pgvector:pg16 を使用してください。AFFiNE v0.27.4 では、schema.prisma に extensions = [pgvector(map: "vector")] が引き続き宣言されています。ただし、v0.27.3 に存在した、vector(1024) の埋め込み列を持つ 4 つのモデルは定義されなくなっています。標準の Postgres には、宣言された拡張機能が含まれていません。外部の Postgres サーバーを AFFiNE に指定する場合は、そのサーバーに pgvector をインストールし、マイグレーションを実行する前に対象データベースで拡張機能を作成してください。

この記事は役に立ちましたか?