Как развернуть AFFiNE через Docker Compose
Пошаговое руководство по установке AFFiNE на VPS. Разберем запуск четырех контейнеров, фиксацию версий образов, настройку путей к данным и требования к 2 ГБ оперативной памяти.
Что вы получаете при самостоятельном хостинге AFFiNE
Самостоятельный хостинг AFFiNE предоставляет рабочее пространство в стиле Notion на сервере под вашим контролем. Система запускается в виде четырех контейнеров: приложение, однократная задача миграции, Postgres и Redis. Совместная работа в реальном времени включена по умолчанию для рабочих пространств с количеством пользователей до 10. Установка выполняется с помощью одного файла compose и одного файла конфигурации JSON. Необходимо уделить внимание выбору тегов образов, структуре дисков, лимитам оперативной памяти и прокси-серверу, который будет стоять перед приложением.
AFFiNE объединяет редактор документов и бесконечный холст в одном рабочем пространстве, поэтому одну и ту же страницу можно просматривать как документ или как интерактивную доску. Если вы еще не решили, какое ПО использовать, сначала ознакомьтесь с сравнением альтернатив Notion для self-hosting. Данное руководство предполагает, что выбор сделан, и фокусируется на корректном запуске AFFiNE, а не на повторном сравнении решений.
Все приведенные здесь данные проверены на соответствие документации по self-hosting AFFiNE и опубликованным файлам релизов на 8 августа 2026 года. Самым новым стабильным релизом на эту дату была версия 0.27.3, выпущенная 23 июля 2026 года.
Что именно делают четыре контейнера
affine — это сервер и веб-клиент в одном образе. Он ожидает входящие соединения на порту 3010.
affine_migration — это однократная задача, которая запускает node ./scripts/self-host-predeploy.js, применяет миграции базы данных и завершает работу. Приложение объявляет condition: service_completed_successfully для этой задачи, поэтому если миграция завершается с ненулевым кодом статуса, affine не запустится вовсе. Если веб-интерфейс не открывается, первым делом следует изучить лог этой задачи.
postgres хранит ваши документы, пользователей, рабочие области и права доступа. Поставляемый образ — pgvector/pgvector:pg16, что представляет собой обычный Postgres 16 с включенным расширением pgvector. pgvector добавляет в Postgres тип столбца vector — числовой формат, используемый для хранения векторов (embeddings), что позволяет выполнять поиск текста по смыслу.
redis является жесткой зависимостью: и сервер, и задача миграции ожидают успешного прохождения проверки работоспособности (health check) этого контейнера перед запуском. Обратите внимание, что в поставляемом файле compose для Redis не указан том (volume). Никакие данные внутри него не сохраняются после docker compose down, что прямо указывает на то, что он не содержит ваших данных и не требует резервного копирования.
Почему используется образ pgvector, а не стандартный postgres
Это требование продиктовано схемой AFFiNE, а не личными предпочтениями. В schema.prisma источник данных объявляет extensions = [pgvector(map: "vector")], а четыре таблицы содержат столбец embedding с типом vector(1024). Задание миграции создает эти таблицы независимо от того, включили вы функции ИИ или нет, поэтому расширение должно присутствовать в базе данных до завершения миграции. Если заменить образ на postgres:16, расширение исчезнет, миграция не сможет создать эти столбцы, и сервер будет бесконечно ожидать выполнения задания, которое завершилось ошибкой.
AFFiNE перешел на образ pgvector начиная с версии 0.21. При обновлении более старых инсталляций одной лишь замены строки с образом недостаточно, поэтому перед загрузкой новых версий ознакомьтесь со страницей обновления в документации по self-host установке AFFiNE.
Еще один важный момент касательно тега. 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. Переход на новую мажорную версию требует выполнения дампа и восстановления данных в свежем каталоге.
Сколько CPU и RAM требуется для self-hosted AFFiNE
На странице системных требований AFFiNE указано минимум 4 ядра CPU и 2 ГБ RAM. Если объем документов превышает 10 000 слов, объем памяти следует увеличить до 4 ГБ. Там же объясняется, на что расходуется память: на систему синхронизации и слияние документов. Стоит запомнить одну цифру: при слиянии документа с 10 000 изменений потребление памяти может достигать 1 ГБ.
Теперь сопоставим это с тарифом на 2 ГБ RAM при работе двух пользователей. В среднем всё в порядке. Postgres и процесс Node укладываются в лимит, и остается свободное место. Проблема возникает в пиковые моменты. Одно крупное слияние может потребовать 1 ГБ сверх уже занятой памяти. На сервере с 2 ГБ RAM без swap OOM (out-of-memory) 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 из первой команды или строка Killed process с упоминанием node во второй означают, что память закончилась, а не возникла ошибка в коде. Решайте проблему с двух сторон. Сначала добавьте 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, он для этого не предназначен. Он превращает секундный скачок в медленную секунду вместо падения контейнера. Вторая часть решения — ограничить 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 ГБ. Тысяча документов примерно по тысяче слов каждый добавляют 0.1 ГБ данных Postgres, что практически ничтожно. Тысяча загруженных файлов добавляют 10 ГБ, и это всё, что нужно знать. Это плановые показатели, а не замеры работающего экземпляра, поэтому воспринимайте их как ориентир, а не как гарантию. Важна сама пропорция: база данных остается небольшой, а объем диска определяется вашими загрузками.
Напишите compose-файл самостоятельно, зафиксировав версии образов
Документированный процесс установки предполагает загрузку готового файла с помощью curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Это работает. Перед использованием стоит учесть одну деталь: по состоянию на 8 августа 2026 года файл, приложенный к релизу 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} за вас, поэтому пароль никогда не появится в файле, который вы могли бы вставить в ветку поддержки. Эту привычку стоит сохранять для любого стека, который вы запускаете, а обоснование приведено в хранении секретов вне compose-файла.
Теперь создайте ~/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Здесь четыре отличия от файла, поставляемого разработчиками, и у каждого есть причина.
127.0.0.1:3010:3010публикует порт только на loopback-адресе, поэтому никто вне сервера не сможет получить доступ к AFFiNE, пока вы сами не решите, как это сделать. Исходный'3010:3010'привязывается ко всем интерфейсам, и на большинстве образов VPS это включает публичный интерфейс.POSTGRES_HOST_AUTH_METHOD: trustудалён, вместо него задан пароль. Метод аутентификации trust принимает любое соединение с этой базой данных от имени пользователяaffineбез пароля. Он ограничен частной сетью Compose, что допустимо до тех пор, пока вы не подключите к этой сети ещё один контейнер или не опубликуете порт 5432 во время отладки.redis:8-alpineзаменяет простойredis, который разрешается вlatest. По состоянию на август 2026 года это Redis 8, поэтому фиксация версии сохраняет мажорный релиз, который вы протестировали, и предотвращает появление Redis 9 во время не связанного с этимdocker compose pull.pgvector/pgvector:pg16остаётся в точности таким, как его задали разработчики, по причине, указанной выше.
POSTGRES_PASSWORD считывается только тогда, когда Postgres создаёт директорию данных в первый раз. Если экземпляр уже существует, установите пароль с помощью 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 управляет функциями ИИ. copilot.byok.enabled — это переключатель для использования собственного ключа, который позволяет владельцу рабочей области вставить ключ своего провайдера моделей в настройках. Самостоятельно развернутый AFFiNE не включает подписку на ИИ. Оставьте оба параметра 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 на каждую стабильную сборку указывает несколько тегов, из которых важны два: stable, который переназначается при каждом релизе, и stable- с коротким git-хешем, который не меняется. Если оставить stable, то через полгода команда docker compose pull загрузит другой образ и выполнит миграции базы данных в момент, который вы не выбирали. Закрепите именно тот образ, который вы протестировали:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'Эта команда выведет строку вида ghcr.io/toeverything/affine@sha256:, за которой следует длинный хеш. Вставьте всю эту строку в поле image: в обоих файлах: affine и affine_migration. Эти два значения всегда должны совпадать, так как это один и тот же образ, выполняющий две роли; несовпадение приведет к тому, что база данных будет мигрирована под одну схему, а обслуживаться — другой. Обновление в таком случае станет осознанным действием, а не неожиданностью: измените хеш, сделайте резервную копию, выполните 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 в локальном браузере. Зарегистрируйтесь и войдите в систему, после чего закройте туннель. Только теперь можно безопасно открывать доступ к экземпляру по публичному доменному имени.
Где AFFiNE хранит данные
Все данные находятся в трех директориях, расположенных внутри созданной вами папки.
./data/postgres— директория с данными Postgres: документы, пользователи, рабочие пространства, права доступа../data/storageмонтируется в/root/.affine/storageвнутри контейнера и содержит все загруженные файлы../configмонтируется в/root/.affine/configи содержитconfig.json.
Разработчики используют здесь bind mounts вместо именованных томов, и это осознанный выбор: вы можете архивировать и копировать эти пути обычными командами, не выясняя, где именно Docker их разместил. Обратной стороной является то, что управление правами доступа к файлам на хосте ложится на вас; этот компромисс описан в bind mounts и именованные тома.
Как создать резервную копию AFFiNE
Необходимо создать резервные копии двух компонентов, и методы их копирования различаются. База данных работает в режиме реального времени, поэтому копирование файлов во время её работы приведёт к повреждению данных. Вместо этого создайте дамп:
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 в резервной копии вручную. В документации AFFiNE по состоянию на август 2026 года указано, что экспорт конфигурации из панели администратора ещё не реализован, поэтому файл на диске является единственной копией ваших настроек. Скопируйте все три файла с сервера. Резервная копия, хранящаяся на том же диске, что и исходные данные, не является резервной копией.
Восстановление и одна ловушка в опубликованных инструкциях
Изучите официальные инструкции по восстановлению до того, как они вам понадобятся, и читайте их внимательно. В версии, опубликованной в августе 2026 года, предлагается скопировать файл с именем affine.backup в контейнер, а затем выполнить восстановление из ./pg.backup — это два разных имени. Кроме того, там предлагается удалить каталог ./postgres, в то время как текущий файл compose хранит данные в ./data/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Обратите внимание на mv, а не на rm. Восстановление поверх базы данных, копию которой вы не сохранили, — это путь, когда одна неверная команда приводит к полной потере данных; перемещение старого каталога в сторону ничего не стоит. Восстановите также загруженные файлы с помощью tar xzf backup/storage-2026-08-08.tgz -C data, иначе каждый документ будет отображаться с битыми вложениями. После этого войдите в систему и откройте документ, содержащий изображение. Это и есть проверка. Восстановление, которое вы не открыли в браузере, — это просто файл, а не резервная копия.
Размещение AFFiNE за уже используемым прокси-сервером
AFFiNE использует WebSocket, и это обязательное требование. Документация прямо указывает: WebSocket является основой системы синхронизации и совместной работы AFFiNE. Если прокси-сервер не поддерживает обновление (upgrade) этих соединений, рабочая область перестанет синхронизироваться. Страница загружается, вход в систему работает, но изменения, сделанные в одном браузере, не отображаются в другом. Откройте вкладку Network в инструментах разработчика браузера и отфильтруйте запросы по WS. Если соединение постоянно открывается и закрывается, значит, прокси-сервер не пропускает запрос на обновление протокола.
Если вы уже используете Traefik для других контейнеров, AFFiNE подключается к нему как обычный сервис. Удалите блок ports: из сервиса affine, а затем добавьте:
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-соединения без дополнительной настройки, поэтому больше ничего добавлять не нужно. Запуск нескольких приложений за одним экземпляром прокси описан в использование одного 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;
}Значение client_max_body_size по умолчанию в nginx составляет 1 MB, поэтому без этой строки любая загрузка файла размером больше небольшой фотографии завершится ошибкой с кодом 413, при этом в логах AFFiNE ничего не появится, так как запрос не дойдет до приложения. Caddy требует только одну строку, reverse_proxy http://127.0.0.1:3010, и самостоятельно обрабатывает сертификаты и обновление WebSocket-соединений.
Чего не хватает в self-hosted версии
Будьте честны с собой, прежде чем переводить на неё команду.
Функция совместной работы в реальном времени присутствует, и именно она определяет требования к ресурсам, так как в документации AFFiNE потребление памяти связывается с системой синхронизации и слиянием документов. Возможность редактирования офлайн — причина, по которой многие выбирают инструменты с приоритетом локальных данных; десктопное приложение позволяет добавить ваш self-hosted сервер в список рабочих пространств и авторизоваться на нём. Протестируйте поведение в офлайне, от которого зависит ваша команда, прежде чем принимать решение: отредактируйте данные в десктопном приложении при отключенной сети, восстановите соединение и проверьте результат на втором устройстве. Списки функций не являются доказательством, и этот список — не исключение.
Полнотекстовый поиск на стороне сервера отключен в поставляемом файле compose, где AFFINE_INDEXER_ENABLED=false задано для сервера и задачи миграции. Его включение требует добавления контейнера Manticore Search, что означает пятый сервис и дополнительный расход памяти. На сервере с 2 GB ОЗУ именно это изменение может привести к нехватке ресурсов. Поиск внутри клиента по-прежнему работает для открытого рабочего пространства.
Перед тем как приглашать пользователей, стоит учесть два ограничения. Self-hosted рабочее пространство ограничено 10 местами; для большего количества потребуется лицензия Team от AFFiNE. Неограниченное хранилище объектов и неограниченный размер объектов для self-hosted инстансов заявлены в документации как планируемые, но на август 2026 года реализованы не полностью. Это не имеет значения для домашнего использования или небольшой команды. Однако это критично, если вы планировали перевести сорок человек.
Обновления
Сначала изучите примечания к выпуску, особенно при переходе между минорными версиями, например с 0.26 на 0.27, так как в них могут содержаться критические изменения. Перед любыми действиями создайте резервную копию базы данных и каталога хранения, поскольку процесс миграции изменяет схему при следующем запуске, и отменить это действие невозможно. Затем измените зафиксированный digest, выполните docker compose pull, а следом docker compose up -d, и отслеживайте docker compose logs -f affine_migration до завершения процесса. docker image prune очищает старые слои после завершения обновления. Историческая справка для тех, кто использует очень старые версии: начиная с версии 0.23.0 имя образа изменилось с affine-graphql на affine, поэтому в файлах compose старше этой версии необходимо обновить строки с указанием образа, иначе команда pull не обнаружит обновлений.
FAQ
Почему контейнер AFFiNE не запускается?
Сервис affine объявляет condition: service_completed_successfully для задания affine_migration, поэтому, если миграция завершается с любым кодом, отличным от 0, сервер не запускается и веб-интерфейс не появляется. Выполните docker compose logs affine_migration, чтобы увидеть, на каком этапе произошла остановка. Самая частая причина при ручном редактировании compose-файла — использование стандартного образа postgres вместо pgvector/pgvector:pg16, так как схема AFFiNE требует расширение pgvector и создает таблицы с колонками vector(1024), которые обычный Postgres создать не может.
Сколько оперативной памяти нужно для self-hosted AFFiNE?
На странице системных требований AFFiNE указано минимум 4 ядра CPU и 2 ГБ RAM, с ростом до 4 ГБ при объеме документов более 10 000 слов; также отмечается, что при слиянии документа с 10 000 изменений потребление может достигать 1 ГБ. На сервере с 2 ГБ RAM именно этот пик становится критическим, а не нагрузка в простое: kernel out-of-memory killer останавливает процесс AFFiNE, а restart: unless-stopped перезапускает его, поэтому пользователи видят перезагрузку страницы вместо ошибки. Проверьте это с помощью docker inspect affine_server --format '{{.State.OOMKilled}}' и sudo dmesg -T | grep -i 'out of memory', затем добавьте swap-файл размером 2 ГБ, чтобы скачок нагрузки приводил к замедлению, а не к аварийному завершению.
Где AFFiNE хранит данные и что нужно резервировать?
Все данные находятся в трех директориях внутри вашего compose-каталога: ./data/postgres для базы данных, ./data/storage для загруженных файлов и ./config для config.json. Создавайте резервную копию базы данных с помощью docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump, а не простым копированием файлов, так как копирование работающего Postgres небезопасно. Архивируйте ./data/storage для загрузок и сохраняйте копию config.json вручную, так как экспорт конфигурации из панели администратора по состоянию на август 2026 года еще не реализован.
Работает ли совместная работа в реальном времени в self-hosted AFFiNE?
Да, и для этого ничего не нужно включать дополнительно. Единственное требование — настройка reverse proxy, так как синхронизация работает через WebSocket-соединения. В nginx это означает использование proxy_http_version 1.1 вместе с заголовками Upgrade и Connection: upgrade, в то время как Traefik и Caddy пропускают эти соединения без дополнительной настройки. Признак того, что прокси не выполняет upgrade соединений — рабочее пространство загружается и авторизация проходит успешно, но правки, сделанные в одном браузере, не отображаются в другом.
Можно ли запустить AFFiNE со стандартным образом Postgres?
Нет. schema.prisma в AFFiNE объявляет extensions = [pgvector(map: "vector")] и определяет четыре таблицы с колонкой embedding типа vector(1024), и задание миграции создает эти таблицы даже при отключенных функциях AI. Используйте pgvector/pgvector:pg16 — это Postgres 16 с уже скомпилированным расширением. Если вы подключаете AFFiNE к внешнему серверу Postgres, установите на нем pgvector и создайте расширение в целевой базе данных перед запуском миграции.