SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

Как развернуть AFFiNE через Docker Compose

Пошаговое руководство по установке AFFiNE на VPS. Разбор конфигурации четырех контейнеров, фиксация тегов образов, настройка путей к данным и реальные требования к 2 ГБ RAM.

Что вы получаете при самостоятельном хостинге AFFiNE

Самостоятельный хостинг AFFiNE предоставляет рабочее пространство в стиле Notion на сервере под вашим контролем. Оно работает в виде четырех контейнеров: приложение, однократная задача миграции, Postgres и Redis. Совместная работа в реальном времени включена по умолчанию для рабочих пространств с количеством пользователей до 10. Установка требует одного файла compose и одного файла конфигурации JSON. Необходимо уделить внимание тегам образов, структуре дисков, лимитам памяти и прокси-серверу, который вы установите перед приложением.

AFFiNE объединяет редактор документов и бесконечный холст в одном рабочем пространстве, поэтому одну страницу можно просматривать как документ или как доску для рисования. Если вы еще не решили, что именно запускать, сначала ознакомьтесь с сравнением альтернатив Notion для самостоятельного хостинга. Данное руководство предполагает, что выбор сделан, и посвящено правильному запуску AFFiNE, а не повторному сравнению.

Все сведения в этом руководстве были проверены на соответствие документации по самостоятельному хостингу AFFiNE и опубликованным файлам релизов на 8 августа 2026 года. Самым новым стабильным релизом на эту дату был 0.27.3, опубликованный 23 июля 2026 года. Утверждения, содержащие более поздние даты, были повторно проверены по первичным источникам 12 октября 2026 года, когда самым новым стабильным релизом был 0.27.4, опубликованный 18 августа 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, что прямо указывает на то, что он не хранит ваш контент и не требует резервного копирования.

Почему используется образ Postgres с pgvector, а не стандартный postgres

Это требование продиктовано схемой AFFiNE, а не предпочтениями. В версии 0.27.4 schema.prisma по-прежнему объявляет extensions = [pgvector(map: "vector")], но не содержит столбцов для эмбеддингов. Четыре модели с колонками embedding типа vector(1024), присутствовавшие в 0.27.3, в 0.27.4 отсутствуют. Используйте предоставленный образ pgvector/pgvector:pg16: расширение всё ещё объявлено, а стандартный postgres:16 его не включает.

Официальный шаблон версии 0.27.4 использует образ pgvector; в шаблоне 0.21.0 всё ещё применялся postgres:16. При обновлении старой инсталляции одной лишь правки строки с образом недостаточно, поэтому перед выполнением любых операций pull ознакомьтесь со страницей обновления в документации по 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. Переход на новую мажорную версию требует одного из трёх способов, описанных в примечаниях к выпуску PostgreSQL 17: создание дампа и восстановление в чистую директорию данных, pg_upgrade или логическая репликация. При использовании Docker Compose на одном сервере дамп и восстановление обычно являются самым простым вариантом, так как pg_upgrade требует наличия старых и новых бинарных файлов сервера одновременно.

Сколько CPU и RAM требуется для self-hosted AFFiNE

На странице системных требований AFFiNE указано минимум 4 ядра CPU и 2 ГБ RAM. При объеме документов свыше 10 000 слов требования к памяти возрастают до 4 ГБ. Там же объясняется причина: потребление памяти связано с работой системы синхронизации и слиянием документов. Важно запомнить одну цифру: при слиянии документа с 10 000 изменений потребление памяти может достигать пика в 1 ГБ.

Сравните это с сервером на 2 ГБ RAM, где работают два пользователя. В обычном режиме проблем нет: Postgres и процесс Node укладываются в лимиты с запасом. Проблема возникает в моменты пиковой нагрузки. Одно крупное слияние может потребовать около 1 ГБ памяти. На сервере с 2 ГБ RAM без swap этот пик вместе с Postgres и другими процессами может привести к исчерпанию памяти. В этом случае OOM (out-of-memory) 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 из первой команды или строка Killed process с упоминанием node из второй означают, что память закончилась, а не возникла ошибка в коде. Решайте проблему с двух сторон. Сначала добавьте 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 теперь должен показывать 2.0Gi общего объема swap. Swap не ускоряет AFFiNE, он для этого не предназначен. Он дает место для кратковременного всплеска нагрузки, поэтому такой всплеск скорее приведет к секундной задержке, чем к падению контейнера. Это буфер, а не гарантия. Вторая часть решения — ограничить Postgres, чтобы он не занимал кэшем память, необходимую приложению во время слияния. Для этого используются лимиты памяти для сервиса в Compose. Если после этих мер падения продолжаются, серверу требуется 4 ГБ RAM, как и указано в требованиях AFFiNE для работы с большими документами.

Объем хранилища предсказать гораздо проще. Вот цифры, которые 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 ГБ. Тысяча документов примерно по тысяче слов каждый добавят 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}, в то время как справочная страница документации демонстрировала более новую структуру, которая размещает хранилище и данные PostgreSQL в ./data, хранит конфигурацию в ./config и вообще не требует .env. Релиз 0.27.4 перевел приложенный файл на эту структуру, и по состоянию на 12 октября 2026 года они соответствуют друг другу. Если вы устанавливали ПО из старого файла, ваши пути по-прежнему берутся из .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} за вас, поэтому пароль никогда не появится в файле, который вы могли бы вставить в ветку поддержки. Эту привычку стоит сохранять для каждого стека, который вы запускаете, а обоснование приведено в хранении секретов вне 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-адресе. Версии Docker до 28.0.0 позволяют хостам в том же L2-сегменте обращаться к портам, опубликованным на localhost, а настроенная прямая маршрутизация может открыть порт контейнера вопреки публикации на loopback. Используйте Docker 28.0.0 или новее и держите прямую маршрутизацию отключенной во время настройки. Исходный '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 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 на каждую стабильную сборку указывает несколько тегов, из которых важны два: 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 в локальном браузере. Зарегистрируйтесь и войдите в систему, затем закройте туннель. Только теперь можно безопасно открывать доступ к экземпляру по публичному доменному имени. Та же проблема конкурентного доступа возникает и в других self-hosted приложениях, причем ситуация осложняется, если первый вход создает ключ доступа (passkey), привязанный к имени хоста. Именно поэтому TLS и финальный домен должны быть настроены до создания первой учетной записи, когда вы разворачиваете openGym самостоятельно.

Где AFFiNE хранит данные

Все данные находятся в трех каталогах, расположенных внутри созданной вами директории.

  • ./data/postgres — это каталог данных Postgres: документы, пользователи, рабочие пространства, права доступа и все настройки сервера, сохраненные через панель администратора.
  • ./data/storage монтируется в /root/.affine/storage внутри контейнера и содержит все загруженные файлы.
  • ./config монтируется в /root/.affine/config и содержит config.json, а также private.key, созданные в процессе миграции.

Разработчики используют здесь 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. Файл размером в несколько сотен байт означает, что дамп не удался, хотя оболочка все равно создала файл; именно такую ошибку пользователи обнаруживают спустя полгода. Флаг -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 приведет к потере этого файла. Настройки, сохраненные в панели администратора, хранятся в базе данных, поэтому указанный выше дамп уже включает их. По состоянию на 12 октября 2026 года страница резервного копирования AFFiNE дополняет, что измененные настройки сервера также можно экспортировать через панель администратора. Скопируйте все три файла с сервера. Резервная копия, хранящаяся на том же диске, что и исходные данные, не является резервной копией. Это разделение на дамп базы данных и tar-архив директории с загрузками — шаблон, который следует применять для каждого контейнера с состоянием. Этот же метод обеспечивает сохранность истории переписки и вложений, когда вы разворачиваете Chatwoot для службы поддержки самостоятельно.

Восстановление и одна ловушка в опубликованных инструкциях

Ознакомьтесь с официальными инструкциями по восстановлению до того, как они вам понадобятся, и читайте их внимательно. В версии, опубликованной в августе 2026 года, предлагалось скопировать файл с именем affine.backup в контейнер, а затем выполнить восстановление из ./pg.backup — это два разных имени. Кроме того, там предлагалось удалить каталог ./postgres, в то время как текущий файл compose хранит данные в ./data/postgres. По состоянию на 12 октября 2026 года на странице везде используется 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

Обратите внимание на 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-соединения без дополнительной настройки, поэтому ничего больше добавлять не нужно. Если остальная часть вашего стека уже находится за Authentik для единого входа, промежуточное ПО forward auth на этом роутере ограничит доступ к AFFiNE через браузер. Однако не включайте его, пока не протестируете десктопное приложение: оно не использует сессии браузера и просто перестанет синхронизироваться. Запуск нескольких приложений за одним экземпляром прокси описан в единый 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 связывает потребление памяти с системой синхронизации и слиянием документов. Возможность редактирования офлайн — причина, по которой многие выбирают инструменты local-first, и десктопное приложение позволяет добавить ваш 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 не обнаружит обновлений. Начиная с версии 0.27.4, в официальном шаблоне compose были изменены пути к Postgres, загрузкам и конфигурации, а также параметры подключения к базе данных по умолчанию. Поэтому сравните новый файл со своим текущим, вместо того чтобы просто перезаписывать его поверх работающей установки.

FAQ

Почему контейнер AFFiNE не запускается?

Сервис affine объявляет condition: service_completed_successfully в задаче affine_migration, поэтому, если миграция завершается с любым кодом, отличным от 0, сервер не запускается и веб-интерфейс не становится доступен. Выполните docker compose logs affine_migration, чтобы увидеть, на каком этапе произошла остановка. Убедитесь, что в отредактированном вручную файле compose по-прежнему используется pgvector/pgvector:pg16: стандартный образ Postgres не содержит расширение pgvector, которое всё ещё требуется схемой AFFiNE, даже несмотря на то, что в версии v0.27.4 больше не определены четыре модели эмбеддингов, присутствовавшие в v0.27.3.

Сколько оперативной памяти нужно для self-hosted AFFiNE?

На странице системных требований AFFiNE указано минимум 4 ядра CPU и 2 ГБ оперативной памяти, объем которой возрастает до 4 ГБ при работе с документами объемом более 10 000 слов. Также отмечается, что при слиянии документа с 10 000 изменений потребление памяти может достигать пика в 1 ГБ. На сервере с 2 ГБ памяти именно этот пик становится критическим, а не нагрузка в режиме ожидания: OOM-killer ядра выбирает процесс для завершения, чаще всего это сервер AFFiNE как самый ресурсоемкий, а restart: unless-stopped перезапускает его, поэтому пользователи видят перезагрузку страницы вместо ошибки. Проверьте это с помощью docker inspect affine_server --format '{{.State.OOMKilled}}' и sudo dmesg -T | grep -i 'out of memory', затем добавьте файл подкачки (swap) объемом 2 ГБ в качестве буфера для кратковременных скачков нагрузки и увеличьте объем RAM до 4 ГБ, если завершения процессов продолжаются.

Где AFFiNE хранит данные и что нужно резервировать?

Все данные находятся в трех директориях внутри вашего compose-каталога: ./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 для загрузок и сохраняйте всю директорию config, файл compose и .env, так как эти файлы не являются частью базы данных.

Работает ли совместная работа в реальном времени в self-hosted AFFiNE?

Да, и для этого не требуется никакой дополнительной настройки. Единственное условие — ваш reverse proxy, так как синхронизация происходит через WebSocket-соединения. Для nginx это означает proxy_http_version 1.1, а также заголовки Upgrade и Connection: upgrade, в то время как Traefik и Caddy пропускают такие соединения без дополнительной конфигурации. Признаком того, что прокси не выполняет обновление (upgrade) соединения, является ситуация, когда рабочая область загружается и вход в систему выполняется нормально, но правки, сделанные в одном браузере, не отображаются в другом.

Можно ли запустить AFFiNE со стандартным образом Postgres?

Используйте pgvector/pgvector:pg16 — это Postgres 16 с предустановленным расширением pgvector. AFFiNE v0.27.4 всё ещё объявляет extensions = [pgvector(map: "vector")] в schema.prisma, но больше не определяет четыре модели с колонками эмбеддингов vector(1024), которые были в v0.27.3. Стандартный Postgres не включает это расширение. Если вы подключаете AFFiNE к внешнему серверу Postgres, установите на него pgvector и создайте расширение в целевой базе данных перед запуском миграции.

Была ли эта статья полезной?