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 自己ホスト用ドキュメントと公開リリースファイルに基づいて確認しています。その日時点での最新安定版は、2026 年 7 月 23 日に公開された 0.27.3 でした。
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 イメージが標準の postgres ではなく pgvector である理由
この要件は、好みではなく AFFiNE のスキーマに由来します。schema.prisma ではデータソースが extensions = [pgvector(map: "vector")] を宣言しており、4 つのテーブルに embedding 列があり、その型は vector(1024) です。AI 機能を有効にするかどうかにかかわらず、migration job はこれらのテーブルを作成します。そのため、migration を完了する前に、データベースにこの extension が存在していなければなりません。postgres:16 に置き換えると extension がなくなり、migration はこれらの列を作成できません。その結果、サーバーは失敗した job を待ち続けます。
AFFiNE は version 0.21 で pgvector イメージに移行しました。それより古いインストールでは、イメージの行を編集するだけではアップグレードは完了しません。そのため、何かを pull する前に AFFiNE の self-host ドキュメント の upgrade ページを確認してください。
この tag について、もう 1 つ注意点があります。pg16 は Postgres 16 を意味します。Postgres の major version は、単純に数値を上げて変更できるものではありません。既存の data directory に対して pg17 に変更すると、Postgres は起動を拒否し、docker compose logs postgres に The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 のような行を出力します。major version の移行には、dump を取得し、新しい data directory に restore する作業が必要です。
セルフホストの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が必要になることがあります。swapがない2 GBのサーバーでは、カーネルのout-of-memory (OOM) killerがその要求に応じるため、最大のプロセスである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を追加し、メモリの急増を致命的な障害ではなく低速化に変えます。
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 -hfree -hの結果に、合計2.0Giのswapが表示されるはずです。swapによってAFFiNEが高速になるわけではなく、そのためのものでもありません。1秒間のメモリ急増を、コンテナ停止ではなく低速な処理に変えます。もう一方の対策は、マージ時にアプリケーションが必要とする領域までPostgresのキャッシュが拡大しないようにすることです。そのために使用するのが、Composeサービスのメモリ制限です。
ストレージの使用量は、はるかに予測しやすいものです。AFFiNEは同じページで次の数値を公開しています。
The data behind this chart
[
{
"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} を使用します。一方、ドキュメントのリファレンスページには、すべてを ./data 配下に置き、.env を必要としない新しい構成が示されています。どちらも実在する構成です。ファイルを自分で作成すれば、この違いに迷う必要はありません。どのみち、イメージを固定してデータベースのパスワードを設定するために編集が必要です。
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose はプロジェクトディレクトリから自動的に .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-stoppedupstream が提供するファイルとの違いは 4 つあります。それぞれに理由があります。
127.0.0.1:3010:3010は loopback アドレスにだけポートを公開します。そのため、公開方法を決めるまで、サーバー外部から AFFiNE に接続できません。upstream の'3010:3010'はすべてのインターフェースに bind します。多くの VPS イメージでは、公開インターフェースも含まれます。POSTGRES_HOST_AUTH_METHOD: trustを削除し、代わりにパスワードを設定します。Trust 認証では、そのデータベースへの接続に対して、パスワードなしでaffineuser として認証できます。これは private Compose network に限定されているため通常は問題ありません。ただし、その network に別の container を追加したり、デバッグ中に 5432 を公開したりすると危険です。redis:8-alpineは、latestに解決される単独のredisに置き換えています。2026 年 8 月時点では Redis 8 であるため、この固定により、テスト済みの major version を維持できます。無関係なdocker compose pullの実行中に、将来 Redis 9 が導入されることも防げます。pgvector/pgvector:pg16は、上記の理由により upstream の設定をそのまま維持します。
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 は Bring Your Own Key の切り替えで、ワークスペース所有者がワークスペース設定に自分のモデルプロバイダーのキーを入力できるようにします。AFFiNE をセルフホストしても、AI サブスクリプションは含まれません。AI 機能を使用しない場合は、両方とも false のままにしてください。
スタックを起動します。
docker compose up -d
docker compose psdocker compose ps には affine_postgres と affine_redis が healthy、affine_server が running、affine_migration_job が状態 exited (0) として表示されるはずです。マイグレーションジョブでこれ以外の終了コードが表示された場合は、その原因を調査してください。ログには停止したステップが記録されています。
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を保存します。
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
cp config/config.json backup/config-$(date +%F).json手動でバックアップし、config.json を保持してください。2026 年 8 月時点で確認した AFFiNE のドキュメントでは、管理パネルからの設定エクスポートはまだ未実装と記載されています。そのため、ディスク上のファイルが設定の唯一のコピーです。3 つのファイルをすべてサーバー外へコピーしてください。保護対象と同じディスク上にあるバックアップは、バックアップとはいえません。データベースダンプと uploads ディレクトリの tar を分けて取得するこの方法を、運用する他のステートフルコンテナでも繰り返してください。これは、Chatwoot をサポートデスクとしてセルフホストする場合に、会話履歴と添付ファイルを安全に保つ方法でもあります。
公開手順で復元する際の注意点
必要になる前に公式の復元手順を読み、細部まで確認してください。2026 年 8 月時点の公開手順では、affine.backup という名前のファイルをコンテナにコピーしてから ./pg.backup から復元しますが、これらは異なる名前です。また、現在の compose ファイルではデータを ./data/postgres に保存しているにもかかわらず、./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 -drm ではなく mv である点に注意してください。コピーを保持していないデータベースに対して復元を実行すると、1 つの誤ったコマンドが完全なデータ消失につながります。古いディレクトリを別の場所に移すだけなら、コストはかかりません。tar xzf backup/storage-2026-08-08.tgz -C data を使用してアップロードファイルも復元してください。そうしないと、すべてのドキュメントで添付ファイルが壊れて表示されます。その後、ログインして画像を含むドキュメントを開きます。これがテストです。ブラウザーで開いていない復元データは、バックアップではなく単なるファイルです。
すでに運用しているプロキシの背後に 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 ファイルでは無効です。そこでは AFFINE_INDEXER_ENABLED=false がサーバーとマイグレーションジョブの両方で設定されています。有効にするには Manticore Search コンテナを追加する必要があります。サービスが 5 つ目になり、メモリ使用量も増えます。2 GB のサーバーでは、この変更によってメモリの上限を超えます。クライアント内の検索は、現在開いているワークスペースでは引き続き利用できます。
利用者を招待する前に、2 つの制限を把握しておいてください。セルフホストのワークスペースに割り当てられるシート数は最大 10 です。これを超えるには AFFiNE の Team ライセンスが必要です。セルフホストインスタンス向けの無制限の blob ストレージと blob サイズの無制限化は、ドキュメントでは予定されているものの、まだ完全には実装されていない機能として説明されています。2026 年 8 月時点で確認した内容です。家庭や小規模チームでは、どちらも問題になりません。40 人を移行する予定なら、どちらも重要になります。
アップグレード
最初にリリースノートを確認します。特に 0.26 から 0.27 のようなマイナーバージョンアップでは、互換性を損なう変更が入ることがあります。作業前にデータベースとストレージディレクトリをバックアップしてください。次回の起動時にマイグレーションジョブがスキーマを変更し、元に戻せないためです。次に固定しているダイジェストを変更し、docker compose pull に続けて docker compose up -d を実行します。docker compose logs -f affine_migration が正常に終了するまで監視します。その後、docker image prune で古いレイヤーを削除します。非常に古いインストール環境を使用している場合は、次の点にも注意してください。バージョン 0.23.0 から、イメージ名が affine-graphql から affine に変更されています。そのため、それ以前の compose ファイルでは、pull でイメージを取得できるように image 行を書き換える必要があります。
FAQ
AFFiNE コンテナが起動しないのはなぜですか?
affine サービスは affine_migration ジョブに対して condition: service_completed_successfully を宣言しています。そのため、マイグレーションが 0 以外のステータスで終了すると、サーバーは起動せず、Web インターフェースも表示されません。どの手順で停止したかは docker compose logs affine_migration を実行して確認します。手作業で編集した compose ファイルでは、pgvector/pgvector:pg16 の代わりに標準の postgres イメージを指定していることが最も一般的な原因です。AFFiNE のスキーマは pgvector 拡張機能を宣言し、通常の Postgres では作成できない vector(1024) 型のカラムを持つテーブルを作成するためです。
セルフホストの 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 ファイルを追加してください。そうすれば、メモリ使用量の急増が致命的な障害ではなく、処理の遅延として現れます。
AFFiNE はデータをどこに保存しますか?何をバックアップすればよいですか?
compose ディレクトリ以下の 3 つのパスにすべてのデータが保存されます。データベースは ./data/postgres、アップロードファイルは ./data/storage、config.json は ./config に保存されます。データベースはファイルをコピーせず、docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump でバックアップしてください。稼働中の Postgres のファイルは安全にコピーできないためです。アップロードファイルは ./data/storage を tar でアーカイブし、config.json のコピーは手動で保管してください。管理パネルからの設定エクスポートは、2026 年 8 月時点で未実装と記載されています。
セルフホストの AFFiNE でリアルタイム共同編集を利用できますか?
はい。追加の有効化は必要ありません。必要なのはリバースプロキシの設定です。同期には WebSocket 接続を使用するためです。nginx では proxy_http_version 1.1 に加えて Upgrade と Connection: upgrade のヘッダーを設定します。Traefik と Caddy では、追加設定なしでこれらの接続が通過します。プロキシが WebSocket 接続をアップグレードしない場合、ワークスペースの読み込みとログインは正常に完了しますが、一方のブラウザーで行った編集が別のブラウザーに表示されません。
標準の Postgres イメージで AFFiNE を実行できますか?
いいえ。AFFiNE の schema.prisma は extensions = [pgvector(map: "vector")] を宣言し、vector(1024) 型の embedding カラムを持つ 4 つのテーブルを定義しています。AI 機能を無効にしていても、マイグレーションジョブはこれらのテーブルを作成します。pgvector/pgvector:pg16 を使用してください。これは pgvector 拡張機能を組み込んだ Postgres 16 です。外部の Postgres サーバーを AFFiNE の接続先にする場合は、そのサーバーに pgvector をインストールし、マイグレーションを実行する前に対象データベースで拡張機能を作成してください。