SSD Nodes Learn 🎉 VPS від $5.50/міс
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-13

Як розгорнути AFFiNE на VPS через Docker Compose

Запустіть AFFiNE на одному VPS через Docker Compose: 4 контейнери, закріплені теги образів, розташування даних, резервні копії та реальні можливості 2 GB RAM.

Що ви отримуєте, розгортаючи AFFiNE самостійно

Самостійне розгортання AFFiNE дає вам робочий простір у стилі Notion на сервері під вашим контролем. Він працює як чотири контейнери: застосунок, одноразове завдання міграції, Postgres і Redis. Спільна робота в реальному часі доступна для 10 користувачів, які за замовчуванням підтримуються в self-hosted робочому просторі. Для встановлення потрібні один compose-файл і один JSON-файл конфігурації. Окремої уваги потребують теги образів, структура дисків, обмеження пам’яті та проксі перед застосунком.

AFFiNE поєднує в одному робочому просторі редактор документів і безмежне полотно. Тому одну сторінку можна переглядати як документ або розкласти на ній вміст як на дошці. Якщо ви ще вирішуєте, що саме запускати, спочатку прочитайте порівняння self-hosted альтернатив Notion. У цьому посібнику передбачається, що вибір уже зроблено. Тут описано належний запуск AFFiNE, а не повторне порівняння продуктів.

Усю наведену інформацію перевірено за документацією AFFiNE для self-hosted розгортання та опублікованими файлами релізу станом на 8 August 2026. Найновішим стабільним релізом на цю дату був 0.27.3, опублікований 23 July 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 — числове представлення, яке використовується для зберігання embedding, щоб шукати текст за змістом.

redis — це жорстка залежність: сервер і завдання міграції очікують на перевірку його стану перед запуском. Зверніть увагу, чого файл compose, що постачається, не надає Redis: це volume. Після docker compose down усередині нього нічого не зберігається, тому очевидно, що він не містить ваших даних і не потребує резервного копіювання.

Чому образ Postgres — це pgvector, а не stock postgres

Ця вимога випливає зі схеми AFFiNE, а не з чиїхось уподобань. У schema.prisma джерело даних оголошує extensions = [pgvector(map: "vector")], а чотири таблиці містять стовпець embedding типу vector(1024). Завдання міграції створює ці таблиці незалежно від того, чи вмикатимете ви функції AI. Тому розширення вже має існувати в базі даних до завершення міграції. Якщо замінити образ на postgres:16, розширення зникне, міграція не зможе створити ці стовпці, а сервер очікуватиме завершення завдання, яке завершилося помилкою.

AFFiNE перейшов на образ pgvector у версії 0.21. Якщо інсталяція старіша, ніж ця версія, простого редагування рядка з образом недостатньо. Перш ніж завантажувати будь-що, ознайомтеся зі сторінкою оновлення в документації AFFiNE для self-hosting.

Є ще один важливий момент щодо цього тегу. pg16 означає Postgres 16, а major-версію Postgres не можна просто змінити числом. Якщо змінити її на pg17 у наявному каталозі даних, Postgres відмовиться запускатися. У docker compose logs postgres з’явиться рядок на кшталт The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17. Перехід на іншу major-версію потребує створення dump і відновлення його в новий каталог даних.

Скільки CPU та RAM потрібно для self-hosted AFFiNE

На сторінці вимог AFFiNE зазначено щонайменше 4 ядра CPU і 2 GB RAM. Для документів обсягом понад 10,000 слів рекомендований обсяг пам’яті збільшується до 4 GB. На тій самій сторінці пояснюється, на що витрачається пам’ять: на систему синхронізації та злиття документів. Там наведено показник, який варто запам’ятати: злиття документа з 10,000 змінами може тимчасово потребувати до 1 GB.

Тепер порівняймо це з тарифом на 2 GB, якщо над документами працюють двоє людей. Середнє навантаження не є проблемою. Postgres і процес Node залишаються в межах ліміту, і ще залишається вільна пам’ять. Проблема виникає під час пікового навантаження. Одне велике злиття може потребувати додатково 1 GB поверх пам’яті, яку вже використовують процеси. На сервері з 2 GB без swap kernel's 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 у виводі першої команди або рядок 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 має показувати загальний обсяг swap 2.0Gi. 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 GB. Тисяча документів обсягом приблизно тисяча слів кожен додає 0.1 GB даних Postgres, що майже непомітно. Тисяча завантажених файлів додає 10 GB. Саме вони визначають основний обсяг. Це розрахункові показники, опубліковані AFFiNE, а не вимірювання запущеного екземпляра, тому сприймайте їх як орієнтир, а не гарантію. Важлива сама закономірність: база даних залишається невеликою, а обсяг диска визначають завантажені файли.

Створіть compose-файл самостійно та зафіксуйте теги

У задокументованій процедурі встановлення готовий файл завантажується за допомогою curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml. Це працює. Але перед тим, як покладатися на цей файл, варто знати одну деталь: станом на 8 August 2026 файл, прикріплений до 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 .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

Від файлу, який постачається upstream, цей варіант відрізняється чотирма параметрами. Кожен із них має конкретну причину.

  • 127.0.0.1:3010:3010 публікує порт лише на loopback-адресі, тому жоден вузол поза сервером не зможе підключитися до AFFiNE, доки ви не визначите потрібний спосіб доступу. Upstream '3010:3010' прив’язує порт до всіх інтерфейсів, а на більшості образів VPS це також означає публічний інтерфейс.
  • POSTGRES_HOST_AUTH_METHOD: trust видалено, а натомість установлено пароль. Автентифікація trust приймає будь-яке підключення до цієї бази даних від користувача affine без пароля. Це обмежено приватною мережею Compose, але проблема може виникнути, якщо ви додасте до цієї мережі ще один контейнер або опублікуєте 5432 під час налагодження.
  • redis:8-alpine замінює простий redis, який розгортається до latest. Станом на August 2026 це Redis 8, тому фіксація версії зберігає основну версію, яку ви протестували, і не дає майбутньому Redis 9 з’явитися під час іншого docker compose pull.
  • pgvector/pgvector:pg16 залишається точно таким, як його встановлює upstream, з причини, наведеної вище.

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 керує функціями AI. copilot.byok.enabled — це параметр використання власного ключа. Він дає змогу власнику робочого простору вставити власний ключ постачальника моделей у налаштуваннях робочого простору. Self-hosting AFFiNE не включає підписку на AI. Якщо ця функція не потрібна, залиште обидва параметри 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. Ці значення завжди мають збігатися, оскільки йдеться про той самий образ, який виконує дві ролі. Якщо вони відрізняються, база даних мігрує до однієї схеми, а обслуговуватися буде іншою. Після цього оновлення стає свідомою зміною, а не несподіванкою: змініть digest, створіть резервну копію, 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.

У цьому випадку upstream використовує bind mounts замість іменованих томів. Це зроблено навмисно: ці шляхи можна архівувати за допомогою tar і копіювати звичайними командами, не з’ясовуючи в 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. Файл розміром у кілька сотень байтів означає, що створення дампа завершилося помилкою, хоча shell все одно створив файл. Саме цю проблему часто виявляють лише через шість місяців. Параметр -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 за наявним reverse proxy

AFFiNE використовує WebSocket, і це обов’язково. У документації прямо зазначено: WebSocket є основою системи синхронізації та спільної роботи AFFiNE. Тому proxy, який не оновлює такі з’єднання, створює workspace, де редагування непомітно перестає синхронізуватися. Сторінка завантажується, вхід працює, але зміни, внесені в одному браузері, не з’являються в іншому. В інструментах розробника браузера відкрийте вкладку Network і застосуйте фільтр WS. З’єднання, яке постійно відкривається та закривається, означає, що proxy не передає запит на оновлення з’єднання.

Якщо ви вже використовуєте 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

Назва certificate resolver має збігатися з назвою, визначеною у конфігурації Traefik, а loadbalancer.server.port — це порт контейнера 3010, а не порт хоста. Traefik проксуює WebSocket-з’єднання без додаткової конфігурації, тому більше нічого додавати не потрібно. Якщо решта вашого стека вже працює за Authentik для єдиного входу, forward auth middleware на цьому router обмежить доступ браузера до AFFiNE. Проте не вмикайте його, доки не протестуєте desktop app: він не має browser session і просто не зможе синхронізуватися. Запуск кількох застосунків за одним екземпляром описано в розділі один 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 RAM саме ця зміна може перевищити доступний ресурс. Пошук у клієнті продовжує працювати в уже відкритому робочому просторі.

Перед тим як запросити користувачів, врахуйте два обмеження. Self-hosted робочий простір підтримує не більше 10 місць. Для більшої кількості потрібна Team ліцензія від AFFiNE. В документації зазначено, що необмежене blob-сховище та необмежений розмір blob для self-hosted інстансів заплановані, але станом на August 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 для job affine_migration, тому якщо міграція завершується з будь-яким статусом, відмінним від 0, сервер не запускається, а вебінтерфейс взагалі не з’являється. Виконайте docker compose logs affine_migration, щоб побачити, на якому кроці стався збій. Найчастіша причина у вручну відредагованому compose-файлі — стандартний образ postgres замість pgvector/pgvector:pg16, оскільки схема AFFiNE оголошує розширення pgvector і створює таблиці зі стовпцями vector(1024), які звичайний Postgres не може створити.

Скільки RAM потребує self-hosted AFFiNE?

На сторінці вимог AFFiNE вказано щонайменше 4 ядра CPU і 2 GB RAM. Для документів обсягом понад 10,000 слів потреба зростає до 4 GB. Також зазначено, що злиття документа з 10,000 змінами може тимчасово потребувати до 1 GB. На сервері з 2 GB саме це пікове навантаження спричиняє проблему, а не робоче навантаження в стані простою: 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 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 для резервного копіювання завантажених файлів. Копію config.json зберігайте вручну, оскільки станом на August 2026 експорт конфігурації з панелі адміністратора ще не реалізовано.

Чи працює спільна робота в реальному часі у self-hosted AFFiNE?

Так, і для цього нічого не потрібно вмикати. Єдина вимога стосується reverse proxy, оскільки синхронізація працює через WebSocket-з’єднання. У nginx це означає proxy_http_version 1.1, а також заголовки Upgrade і Connection: upgrade. Traefik і Caddy передають такі з’єднання без додаткової конфігурації. Якщо proxy не виконує upgrade-з’єднання, робочий простір завантажується, а вхід працює нормально, але зміни, внесені в одному браузері, ніколи не з’являються в іншому.

Чи можна запустити AFFiNE зі стандартним образом Postgres?

Ні. schema.prisma AFFiNE оголошує extensions = [pgvector(map: "vector")] і визначає чотири таблиці зі стовпцем embedding типу vector(1024). Job міграції створює ці таблиці навіть тоді, коли функції AI вимкнені. Використовуйте pgvector/pgvector:pg16 — це Postgres 16 із попередньо скомпільованим розширенням. Якщо замість цього підключити AFFiNE до зовнішнього сервера Postgres, встановіть на ньому pgvector і створіть розширення в цільовій базі даних до запуску міграції.