SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-27

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

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

Что вы получаете при самостоятельном хостинге 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 откажется запускаться, выдав строку вида The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 в docker compose logs postgres. Переход на новую мажорную версию требует выполнения дампа и восстановления данных в чистый каталог.

Сколько 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 -h

free -h теперь должен показывать 2.0Gi общего объема swap. Swap не делает AFFiNE быстрее, он для этого не предназначен. Он превращает секундный скачок в медленную секунду вместо «убитого» контейнера. Вторая часть решения — ограничить рост кэша Postgres, чтобы он не занимал место, необходимое приложению во время слияния. Для этого используются лимиты памяти для сервиса в Compose.

С хранилищем всё гораздо проще. Вот цифры, которые AFFiNE публикует на той же странице:

ChartPublished AFFiNE storage figures, August 2026
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 .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-адресе, поэтому никто вне сервера не сможет получить доступ к 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 ps

Команда docker 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 в локальном браузере. Зарегистрируйтесь и войдите в систему, после чего закройте туннель. Только теперь можно безопасно открывать доступ к экземпляру по публичному доменному имени. Подобная ситуация возможна и в других self-hosted приложениях; ситуация усложняется, если при первом входе создается ключ доступа (passkey), привязанный к имени хоста. Именно поэтому TLS и финальный домен должны быть настроены до создания первой учетной записи, когда вы разворачиваете openGym самостоятельно.

Где 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 года файл на диске остаётся единственной копией настроек. Скопируйте все три файла за пределы сервера. Резервная копия на том же диске, что и защищаемые ею данные, резервной копией не является. Такой набор из дампа базы данных и архива tar каталога с загруженными файлами следует использовать для каждого другого контейнера с сохраняемыми данными. По этой же схеме защищаются история переписки и вложения, когда вы самостоятельно разворачиваете Chatwoot как службу поддержки.

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

Ознакомьтесь с официальными инструкциями по восстановлению до того, как они вам понадобятся, и читайте их внимательно. В версии, опубликованной в августе 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-соединения без дополнительной настройки, поэтому ничего больше добавлять не нужно. Если остальная часть вашего стека уже находится за 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. Неограниченное хранилище для BLOB-объектов и отсутствие лимитов на их размер для 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 GB RAM. При объеме документов более 10,000 слов потребление возрастает до 4 GB. Слияние документа с 10,000 правок может вызвать пиковую нагрузку в 1 GB. На сервере с 2 GB RAM именно этот пик приводит к завершению процесса: OOM-killer ядра останавливает процесс AFFiNE, а restart: unless-stopped перезапускает его, из-за чего пользователи видят перезагрузку страницы вместо ошибки. Проверьте это с помощью docker inspect affine_server --format '{{.State.OOMKilled}}' и sudo dmesg -T | grep -i 'out of memory', затем добавьте swap-файл объемом 2 GB, чтобы скачок нагрузки приводил к замедлению, а не к аварийному завершению.

Где 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 с загрузками нужно упаковать в tar, а 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 и создайте расширение в целевой базе данных перед запуском миграции.