SSD Nodes Learn Hosting plans →
गाइड Matt Connorलेखक: Matt Connor

Docker Compose के साथ AFFiNE को self-host कैसे करें

Docker Compose का उपयोग करके AFFiNE को अपने सर्वर पर सेटअप करें। इस गाइड में चार containers, image tags, डेटा स्टोरेज, बैकअप और 2 GB RAM की सीमाओं की पूरी जानकारी दी गई है।

AFFiNE को self-host करने पर आपको क्या मिलता है

AFFiNE को self-host करने से आपको अपने नियंत्रण वाले सर्वर पर Notion जैसा workspace मिलता है, जो चार containers के रूप में चलता है: application, एक one-shot migration job, Postgres, और Redis। इसमें real-time collaboration शामिल है, जो self-hosted workspace के लिए डिफ़ॉल्ट रूप से 10 seats तक उपलब्ध है। इंस्टॉलेशन में एक compose file और एक JSON config file का उपयोग होता है। जिन चीजों पर विचार करना आवश्यक है, वे हैं image tags, disk layout, memory ceiling, और वह proxy जिसे आप सामने रखते हैं।

AFFiNE एक ही workspace में document editor और infinite canvas रखता है, इसलिए एक पेज को document के रूप में पढ़ा जा सकता है या whiteboard की तरह फैलाया जा सकता है। यदि आप अभी भी यह तय कर रहे हैं कि क्या चलाना है, तो पहले self-hosted Notion विकल्पों की तुलना पढ़ें। यह गाइड मानती है कि आपने चुनाव कर लिया है, और यह AFFiNE को दोबारा तुलना करने के बजाय उसे सही ढंग से चलाने पर केंद्रित है।

यहाँ दी गई हर जानकारी को 8 August 2026 को AFFiNE self-host documentation और प्रकाशित release files के आधार पर जाँचा गया था। उस तारीख पर सबसे नया stable release 0.27.3 था, जिसे 23 July 2026 को प्रकाशित किया गया था। नीचे दिए गए दावे जिनकी तारीख बाद की है, उन्हें 12 October 2026 को उनके प्राथमिक स्रोतों के साथ दोबारा जाँचा गया था, जब सबसे नया stable release 0.27.4 था, जिसे 18 August 2026 को प्रकाशित किया गया था।

चारों containers वास्तव में क्या करते हैं

affine एक ही image में सर्वर और वेब क्लाइंट है। यह port 3010 पर listen करता है।

affine_migration एक one-shot job है जो node ./scripts/self-host-predeploy.js को चलाती है, database migrations लागू करती है और फिर exit हो जाती है। application उस job पर condition: service_completed_successfully घोषित करती है, इसलिए यदि migration non-zero status के साथ exit होता है, तो इसका मतलब है कि affine कभी start ही नहीं होता। जब वेब इंटरफेस दिखाई न दे, तो सबसे पहले उस job के log को पढ़ें।

postgres आपके documents, users, workspaces और permissions को सुरक्षित रखता है। प्रदान की गई image pgvector/pgvector:pg16 है, जो सामान्य Postgres 16 है जिसमें pgvector extension compile की गई है। pgvector, Postgres में vector column type जोड़ता है, जो embeddings को स्टोर करने के लिए उपयोग किया जाने वाला numeric format है ताकि text को उसके अर्थ (meaning) के आधार पर खोजा जा सके।

redis एक hard dependency है: सर्वर और migration job दोनों ही इसके start होने से पहले इसके health check का इंतज़ार करते हैं। ध्यान दें कि प्रदान की गई compose file Redis को क्या नहीं देती है, जो कि एक volume है। इसके अंदर कुछ भी docker compose down के बाद सुरक्षित नहीं रहता, और यह स्पष्ट रूप से बताता है कि इसमें आपकी कोई भी सामग्री नहीं है और इसे backup की आवश्यकता नहीं है।

Postgres image pgvector क्यों है, stock postgres क्यों नहीं

यह आवश्यकता AFFiNE के schema से आती है, न कि किसी प्राथमिकता से। v0.27.4 में, schema.prisma अभी भी extensions = [pgvector(map: "vector")] घोषित करता है, लेकिन इसमें कोई embedding columns नहीं हैं। v0.27.3 में vector(1024) प्रकार के embedding columns वाले चार models v0.27.4 में मौजूद नहीं हैं। प्रदान की गई pgvector/pgvector:pg16 image का ही उपयोग करें: extension अभी भी घोषित है, और stock postgres:16 में यह शामिल नहीं है।

आधिकारिक v0.27.4 template pgvector image का उपयोग करता है; v0.21.0 template अभी भी postgres:16 का उपयोग करता था। पुराने install पर, केवल image line को edit करना ही पूरा upgrade नहीं है, इसलिए कुछ भी pull करने से पहले AFFiNE self-host docs में upgrade page को पढ़ें।

उस tag के बारे में एक और बात। pg16 का अर्थ है Postgres 16, और Postgres major version वह संख्या नहीं है जिसे आप बस बढ़ा सकें। यदि आप इसे मौजूदा data directory पर pg17 में बदलते हैं, तो Postgres start होने से मना कर देगा, और docker compose logs postgres में The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 जैसी line दिखाई देगी। Major version को बदलने के लिए PostgreSQL 17 release notes में बताए गए तीन रास्तों में से एक की आवश्यकता होती है: dump लेकर उसे नई data directory में restore करना, pg_upgrade, या logical replication। एक single Compose install पर dump और restore आमतौर पर सबसे सरल होता है, क्योंकि pg_upgrade के लिए पुराने और नए server binaries का एक साथ होना आवश्यक है।

self-hosted AFFiNE के लिए कितनी CPU और RAM की आवश्यकता है

AFFiNE का requirements पेज कम से कम 4 CPU cores और 2 GB RAM की मांग करता है। यदि आपके documents 10,000 शब्दों से अधिक हो जाते हैं, तो यह आवश्यकता बढ़कर 4 GB RAM हो जाती है। उसी पेज पर बताया गया है कि यह memory कहाँ खर्च होती है: sync system और document merging में। इसमें एक आंकड़ा याद रखने योग्य है: 10,000 बदलावों वाले document को merge करने पर memory का उपयोग 1 GB तक पहुँच सकता है।

अब इसकी तुलना 2 GB वाले plan से करें जहाँ दो लोग लिख रहे हों। औसत उपयोग ठीक रहता है। Postgres और Node process सीमा के भीतर रहते हैं और कुछ जगह खाली रहती है। समस्या peak (अधिकतम) उपयोग के समय आती है। एक बड़ा merge सर्वर की memory को 1 GB तक ले जा सकता है। बिना swap वाले 2 GB के सर्वर पर, यह peak, Postgres और अन्य background processes के साथ मिलकर पूरी memory खत्म कर सकता है। ऐसी स्थिति में kernel का out-of-memory (OOM) killer memory खाली करने के लिए किसी process को बंद कर देता है। AFFiNE सर्वर सबसे बड़ी process होने के कारण इसका शिकार बन सकता है, हालाँकि Docker का documentation स्पष्ट करता है कि कोई भी process, यहाँ तक कि स्वयं Docker भी, बंद किया जा सकता है।

जब AFFiNE सर्वर बंद होता है, तो आपके सहयोगी को कोई error नहीं दिखता। उन्हें केवल page reload होता हुआ दिखता है, क्योंकि restart: unless-stopped कुछ ही seconds में container को वापस चालू कर देता है। इसका अनुमान न लगाएँ, इसे confirm करें:

docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'

पहले command से true, या दूसरे command से node को दर्शाने वाली Killed process line का मतलब है कि memory खत्म हो गई थी, न कि कोई bug आया था। इसे दोनों तरफ से ठीक करें। पहले swap जोड़ें, ताकि OOM killer के सक्रिय होने से पहले spike के लिए एक buffer उपलब्ध हो:

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 total दिखाना चाहिए। Swap से AFFiNE की गति नहीं बढ़ती और न ही इसका उद्देश्य यह है। यह spike को जगह देता है, जिससे container के बंद होने के बजाय केवल एक second की देरी होने की संभावना रहती है। यह एक buffer है, गारंटी नहीं। समाधान का दूसरा हिस्सा यह है कि Postgres को उस space में cache बढ़ाने से रोकें जिसकी आवश्यकता merge के समय application को होती है, जिसके लिए Compose service पर memory limits का उपयोग किया जाता है। यदि दोनों उपाय करने के बाद भी processes बंद होती हैं, तो सर्वर को 4 GB RAM की आवश्यकता है, जैसा कि बड़े documents के लिए AFFiNE के requirements पेज पर बताया गया है।

Storage का अनुमान लगाना बहुत आसान है। ये वे आंकड़े हैं जिन्हें 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
  }
]

सर्वर install करने में 1.5 GB जगह लगती है। लगभग एक हजार शब्दों वाले एक हजार documents से Postgres data में 0.1 GB की वृद्धि होती है, जो न के बराबर है। एक हजार uploaded files से 10 GB की वृद्धि होती है, और यही मुख्य उपयोग है। ये प्रकाशित आंकड़े planning के लिए हैं, न कि किसी चल रहे instance के वास्तविक माप, इसलिए इन्हें एक अनुमान के रूप में देखें, न कि निश्चित वादे के रूप में। मुख्य बात यह है: आपका database छोटा रहता है और आपकी uploads ही disk का उपयोग तय करती हैं।

Compose file स्वयं लिखें, tags को पिन करें

दस्तावेजीकृत इंस्टॉलेशन curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml के साथ एक तैयार फ़ाइल डाउनलोड करता है। यह काम करता है। उस पर निर्भर होने से पहले एक विवरण जानना महत्वपूर्ण है: 8 अगस्त 2026 तक, release 0.27.3 के साथ संलग्न फ़ाइल अभी भी अपने paths को एक .env फ़ाइल से पढ़ती थी, जिसमें ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} और ${DB_DATA_LOCATION} का उपयोग होता था, जबकि दस्तावेज़ के संदर्भ पृष्ठ में एक नया लेआउट दिखाया गया था जो storage और PostgreSQL डेटा को ./data के अंतर्गत रखता है, configuration को ./config के अंतर्गत रखता है, और उसे किसी .env की आवश्यकता नहीं होती। Release 0.27.4 ने संलग्न फ़ाइल को उस लेआउट में स्थानांतरित कर दिया, और 12 अक्टूबर 2026 तक दोनों मेल खाते हैं। यदि आपने पुरानी फ़ाइल से इंस्टॉल किया है, तो आपके paths अभी भी .env से आते हैं, और AFFiNE का अपग्रेड पेज चेतावनी देता है कि उन सेटिंग्स को आगे बढ़ाए बिना compose फ़ाइल को बदलने से Postgres एक खाली डेटा डायरेक्टरी के साथ शुरू हो सकता है। स्वयं फ़ाइल लिखने से यह प्रश्न पूरी तरह से समाप्त हो जाता है, और वैसे भी आपको images को पिन करने और डेटाबेस पासवर्ड सेट करने के लिए इसे संपादित करना ही होगा।

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} को प्रतिस्थापित करता है, इसलिए पासवर्ड उस फ़ाइल में कभी नहीं दिखता जिसे आप किसी सपोर्ट थ्रेड में पेस्ट करेंगे। उस आदत को हर उस स्टैक में बनाए रखना उचित है जिसे आप चलाते हैं, और इसका कारण keeping secrets out of the compose file में है।

अब ~/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 पते पर प्रकाशित करता है। 28.0.0 से पहले के Docker releases समान L2 सेगमेंट पर hosts को localhost-published पोर्ट तक पहुँचने की अनुमति देते हैं, और कॉन्फ़िगर की गई direct routing लूपबैक पब्लिशिंग के बावजूद कंटेनर पोर्ट को उजागर कर सकती है। Docker 28.0.0 या नए का उपयोग करें और सेटअप के दौरान direct routing को अक्षम रखें। अपस्ट्रीम '3010:3010' हर इंटरफ़ेस को bind करता है, और अधिकांश VPS images पर इसमें सार्वजनिक इंटरफ़ेस भी शामिल होता है।
  • POSTGRES_HOST_AUTH_METHOD: trust हटा दिया गया है और इसके बजाय एक पासवर्ड सेट किया गया है। Trust authentication उस डेटाबेस के किसी भी कनेक्शन को बिना पासवर्ड के affine उपयोगकर्ता के रूप में स्वीकार करता है। यह निजी Compose नेटवर्क तक सीमित है, जो तब तक ठीक है जब तक आप उस नेटवर्क से एक और कंटेनर नहीं जोड़ते या डिबगिंग के दौरान 5432 प्रकाशित नहीं करते।
  • redis:8-alpine एक साधारण redis की जगह लेता है, जो latest पर रिज़ॉल्व होता है। अगस्त 2026 तक यह Redis 8 है, इसलिए पिन उस प्रमुख संस्करण को बनाए रखता है जिसका आपने परीक्षण किया है और भविष्य में किसी असंबंधित docker compose pull के दौरान Redis 9 के आने को रोकता है।
  • pgvector/pgvector:pg16 बिल्कुल वैसा ही रहता है जैसा अपस्ट्रीम इसे सेट करता है, ऊपर दिए गए कारण के लिए।

POSTGRES_PASSWORD केवल तब पढ़ा जाता है जब Postgres पहली बार अपनी डेटा डायरेक्टरी बनाता है। पहले से मौजूद इंस्टेंस पर, पासवर्ड को docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" के साथ सेट करें और फिर मिलान करने के लिए DATABASE_URL को अपडेट करें।

Configuration config/config.json में स्थित है

AFFiNE अपनी सेटिंग्स config/config.json से पढ़ता है, जो वह डायरेक्टरी है जिसे आपने /root/.affine/config पर माउंट किया है। कोई भी प्रक्रिया आपके लिए यह फाइल नहीं बनाती है, इसलिए पहली बार स्टार्ट करने से पहले इसे लिखें। ~/affine/config/config.json को किसी एडिटर में खोलें और इसमें नीचे दी गई सामग्री डालें, जहाँ उदाहरण के स्थान पर अपना डोमेन लिखें:

{
  "$schema": "https://github.com/toeverything/affine/releases/latest/download/config.schema.json",
  "server": {
    "name": "Team workspace",
    "externalUrl": "https://affine.example.com"
  },
  "copilot": {
    "enabled": false,
    "byok": {
      "enabled": false
    }
  }
}

server.externalUrl वह पता होना चाहिए जिसे आपके उपयोगकर्ता वास्तव में ब्राउज़र में खोलते हैं। AFFiNE इसी मान से शेयर लिंक और वर्कस्पेस इनविटेशन बनाता है, इसलिए यदि इसे http://localhost:3010 पर छोड़ दिया जाता है, तो आपके द्वारा भेजा गया इनविटेशन प्राप्तकर्ता को उनकी अपनी मशीन पर निर्देशित करेगा और वहां विफल हो जाएगा। पहली बार स्टार्ट करने से पहले इसे पब्लिक HTTPS पते पर सेट करें, ताकि फाइल और एडमिन पैनल के बीच कोई विसंगति न हो। बाद में एडमिन पैनल में आपके द्वारा बदली गई सेटिंग्स डेटाबेस में सेव होती हैं, न कि इस फाइल में वापस लिखी जाती हैं।

copilot AI फीचर्स को नियंत्रित करता है। copilot.byok.enabled 'bring-your-own-key' स्विच है, जो वर्कस्पेस ओनर को वर्कस्पेस सेटिंग्स में अपनी खुद की मॉडल प्रोवाइडर की (key) पेस्ट करने की अनुमति देता है। AFFiNE को सेल्फ-होस्ट करने में AI सब्सक्रिप्शन शामिल नहीं है। यदि आप इसे नहीं चाहते हैं, तो दोनों को 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

भूलने से पहले image को पिन करें

stable एक बदलता हुआ tag है। AFFiNE का release workflow कई tags को प्रत्येक stable build पर point करता है, जिनमें से दो यहाँ महत्वपूर्ण हैं: stable, जिसे हर release पर बदला जाता है, और stable- जिसके बाद git short hash होता है, जो नहीं बदलता। यदि आप stable पर छोड़ते हैं, तो छह महीने बाद किया गया docker compose pull एक अलग image fetch करेगा और आपके database पर migrations चला देगा, जो आपने तय नहीं किया था। उस सटीक image को पिन करें जिसका आपने परीक्षण किया है:

docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'

यह ghcr.io/toeverything/affine@sha256: के बाद एक लंबा hash वाली लाइन print करेगा। पूरी string को affine और affine_migration दोनों की image: लाइन में paste करें। उन दोनों का हमेशा मेल खाना आवश्यक है, क्योंकि वे एक ही image हैं जो दो भूमिकाएँ निभा रही हैं, और बेमेल होने का अर्थ है database को एक schema पर migrate करना जबकि उसे दूसरे के साथ serve करना। अपग्रेड करना तब एक सोची-समझी प्रक्रिया बन जाती है, न कि कोई आश्चर्य: digest बदलें, backup लें, docker compose pull करें, और docker compose up -d चलाएँ।

किसी और के करने से पहले एडमिन अकाउंट बनाएं

एक नए instance पर /admin खोलें। AFFiNE आपको अकाउंट बनाने वाले पेज पर भेज देगा, क्योंकि सर्वर पर अभी कोई एडमिनिस्ट्रेटर नहीं है। उस प्रक्रिया में कोई इनविटेशन कोड या सेटअप टोकन नहीं होता है। रजिस्ट्रेशन पूरा करने वाला पहला व्यक्ति आपके सर्वर का एडमिनिस्ट्रेटर बन जाता है, इसलिए जब तक आप रजिस्टर न कर लें, तब तक पोर्ट को बंद रखना चाहिए।

यही कारण है कि ऊपर दिया गया compose file 127.0.0.1 पर बाइंड होता है। इसे अपनी मशीन से SSH टनल के माध्यम से एक्सेस करें:

ssh -L 3010:127.0.0.1:3010 you@your-server-ip

इसे चलते रहने दें और अपने लोकल ब्राउज़र में http://127.0.0.1:3010/admin खोलें। रजिस्टर करें और लॉग इन करें, फिर टनल को बंद कर दें। अब instance को पब्लिक नाम पर डालना सुरक्षित है। यही रेस अन्य self-hosted ऐप्स में भी होती है, और यह तब और खराब हो जाती है जब पहला लॉगिन hostname से जुड़ा एक passkey बना देता है। इसीलिए जब आप self-host openGym करते हैं, तो पहले अकाउंट से पहले TLS और फाइनल डोमेन को सेटल करना जरूरी होता है।

AFFiNE आपका डेटा कहाँ रखता है

तीन paths में सब कुछ सुरक्षित रहता है, और ये सभी उस directory के अंदर स्थित हैं जिसे आपने बनाया है।

  • ./data/postgres Postgres डेटा directory है: इसमें documents, users, workspaces, permissions और admin panel से save की गई कोई भी server settings होती हैं।
  • ./data/storage को container के अंदर /root/.affine/storage पर mount किया गया है और यह सभी uploaded files को रखता है।
  • ./config को /root/.affine/config पर mount किया गया है और यह config.json तथा migration job द्वारा generate की गई private.key को रखता है।

Upstream यहाँ named volumes के बजाय bind mounts का उपयोग करता है, और यह चुनाव जानबूझकर किया गया है: आप इन paths को सामान्य commands के साथ tar करके copy कर सकते हैं, बिना Docker से यह पूछे कि उसने उन्हें कहाँ रखा है। इसकी कीमत यह है कि host पर file ownership अब आपकी जिम्मेदारी है, जो कि bind mounts और named volumes में कवर किया गया trade-off है।

AFFiNE का बैकअप कैसे लें

दो चीजों का बैकअप लेना आवश्यक है, और दोनों के बैकअप लेने की प्रक्रिया अलग है। डेटाबेस एक लाइव सर्वर है, इसलिए चलते समय इसकी फाइलों को कॉपी करने से आपको एक दूषित (corrupt) कॉपी मिलेगी। इसके बजाय इसका डंप लें:

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 October 2026 तक AFFiNE का बैकअप पेज यह जोड़ता है कि संशोधित सर्वर सेटिंग्स को एडमिन पैनल से भी एक्सपोर्ट किया जा सकता है। तीनों फाइलों को सर्वर से बाहर कॉपी करें। जिस डिस्क पर डेटा है, उसी पर बैकअप रखना कोई बैकअप नहीं है। डेटाबेस डंप और अपलोड डायरेक्टरी के tar का यह विभाजन वह पैटर्न है जिसे आपको हर दूसरे stateful कंटेनर पर दोहराना चाहिए, और यही वह तरीका है जो support desk के रूप में Chatwoot को self-host करने पर बातचीत के इतिहास और अटैचमेंट को सुरक्षित रखता है।

Restore करना, और प्रकाशित चरणों में एक खामी

Restore करने के आधिकारिक चरणों को जरूरत पड़ने से पहले ही पढ़ लें, और उन्हें ध्यान से पढ़ें। अगस्त 2026 में प्रकाशित चरणों के अनुसार, उन्होंने affine.backup नामक एक फाइल को container में कॉपी किया और फिर ./pg.backup से restore किया, जो कि दो अलग-अलग नाम हैं। उन्होंने एक ./postgres डायरेक्टरी को हटा दिया था, जबकि वर्तमान compose फाइल अपना डेटा ./data/postgres में रखती है। 12 अक्टूबर 2026 तक, यह पेज हर जगह affine.backup का उपयोग करता है और कहता है कि पुरानी Postgres डायरेक्टरी को तब तक रखें जब तक कि restore सत्यापित न हो जाए। इसलिए, यह खामी upstream में ठीक कर दी गई है, लेकिन उस तारीख से पहले सेव की गई चरणों की कॉपी में अभी भी यह त्रुटि हो सकती है। किसी भी स्थिति में, किसी भी स्निपेट में दिए गए पाथ के बजाय उन पाथ का पालन करें जिनका आपने वास्तव में उपयोग किया है। इस गाइड में दिए गए लेआउट के अनुसार क्रम यहाँ दिया गया है:

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

rm के बजाय mv पर ध्यान दें। जिस डेटाबेस की आपने कॉपी नहीं रखी है, उसके ऊपर restore करना ही वह तरीका है जिससे एक गलत कमांड पूरी तरह से डेटा लॉस का कारण बन जाती है, और पुरानी डायरेक्टरी को दूसरी जगह हटाने में कोई खर्च नहीं आता। tar xzf backup/storage-2026-08-08.tgz -C data के साथ uploads को भी restore करें, अन्यथा हर डॉक्यूमेंट टूटे हुए अटैचमेंट के साथ दिखाई देगा। उसके बाद लॉग इन करें और ऐसा डॉक्यूमेंट खोलें जिसमें कोई इमेज हो। यही टेस्ट है। जिसे आपने ब्राउज़र में खोलकर नहीं देखा, वह केवल एक फाइल है, बैकअप नहीं।

AFFiNE को अपने मौजूदा प्रॉक्सी के पीछे सेटअप करना

AFFiNE, WebSocket का उपयोग करता है और यह अनिवार्य है। डॉक्यूमेंटेशन इस बारे में स्पष्ट है: WebSocket, AFFiNE सिंक और कोलैबोरेशन सिस्टम का आधार है। यदि आपका प्रॉक्सी इन कनेक्शन्स को अपग्रेड नहीं करता है, तो आपका वर्कस्पेस सिंक करना बंद कर देगा। पेज लोड होगा, लॉगिन काम करेगा, लेकिन एक ब्राउज़र में किया गया बदलाव दूसरे तक नहीं पहुँचेगा। अपने ब्राउज़र के डेवलपर टूल्स में, 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 के पीछे है, तो इस राउटर पर एक फॉरवर्ड ऑथ मिडलवेयर 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;
}

nginx में client_max_body_size डिफ़ॉल्ट रूप से 1 MB होता है, इसलिए उस लाइन के बिना एक छोटी फोटो से बड़ी कोई भी अपलोड 413 स्टेटस के साथ विफल हो जाएगी। AFFiNE लॉग्स में कुछ भी दिखाई नहीं देगा, क्योंकि अनुरोध कभी पहुँचा ही नहीं। Caddy को केवल एक लाइन, reverse_proxy http://127.0.0.1:3010 की आवश्यकता होती है, और यह सर्टिफिकेट और WebSocket अपग्रेड को स्वयं संभाल लेता है।

सेल्फ-होस्टेड बिल्ड में क्या शामिल नहीं है

किसी टीम को इस पर शिफ्ट करने से पहले इस बारे में खुद के प्रति ईमानदार रहें।

Real-time collaboration इसमें मौजूद है, और सभी sizing सलाह इसी फीचर के बारे में है, क्योंकि AFFiNE का अपना documentation मेमोरी के उपयोग का कारण sync system और document merging को बताता है। Offline editing वह कारण है जिसके लिए कई लोग local-first टूल चाहते हैं, और desktop application आपके self-hosted सर्वर को अपनी workspace list में जोड़ सकता है और उस पर log in कर सकता है। जिस सटीक offline व्यवहार पर आपकी टीम निर्भर है, उसे commit करने से पहले टेस्ट करें: नेटवर्क बंद करके desktop app में edit करें, फिर reconnect करें, और उसके बाद दूसरे डिवाइस पर परिणाम देखें। फीचर लिस्ट सबूत नहीं होती, और इसमें यह लिस्ट भी शामिल है।

Server-side full-text search shipped compose file में बंद है, जहाँ सर्वर और migration job पर AFFINE_INDEXER_ENABLED=false सेट है। इसे चालू करने का मतलब है एक Manticore Search कंटेनर जोड़ना, जो कि पांचवीं सर्विस है और अधिक मेमोरी लेती है। 2 GB के बॉक्स पर, यही वह बदलाव है जो आपको क्षमता से बाहर ले जाता है। आपके द्वारा open किए गए workspace पर client के अंदर search अभी भी काम करता है।

लोगों को आमंत्रित करने से पहले दो सीमाओं के बारे में जानना जरूरी है। एक self-hosted workspace को अधिकतम 10 seats दी जाती हैं, और उससे आगे जाने के लिए AFFiNE से Team license की आवश्यकता होती है। self-hosted instances के लिए unlimited blob storage और unlimited blob size को documentation में इच्छित बताया गया है लेकिन अक्टूबर 2026 में जांचे जाने पर यह पूरी तरह से लागू नहीं है। ये दोनों बातें किसी घर या छोटी टीम के लिए मायने नहीं रखतीं। यदि आप चालीस लोगों को शिफ्ट करने की योजना बना रहे हैं, तो ये दोनों मायने रखती हैं।

अपग्रेड

अपग्रेड करने से पहले release notes पढ़ें, विशेषकर 0.26 से 0.27 जैसे minor version बदलावों के लिए, जिनमें breaking changes हो सकते हैं। कुछ भी करने से पहले database और storage directory का backup लें, क्योंकि अगली बार start होने पर migration job आपके schema को बदल देती है और इसे undo नहीं किया जा सकता। इसके बाद pinned digest को बदलें, docker compose pull चलाएँ और उसके बाद docker compose up -d का उपयोग करें, फिर docker compose logs -f affine_migration को तब तक monitor करें जब तक वह सफलतापूर्वक exit न हो जाए। docker image prune बाद में पुरानी layers को हटा देता है। बहुत पुराने install वाले उपयोगकर्ताओं के लिए एक ऐतिहासिक जानकारी: version 0.23.0 से image का नाम affine-graphql से बदलकर affine हो गया है, इसलिए उससे पुराने compose file में image lines को फिर से लिखना होगा, अन्यथा pull करने पर कुछ नहीं मिलेगा। और version 0.27.4 से official compose template ने Postgres, upload और configuration paths को स्थानांतरित कर दिया है और default database connection को बदल दिया है, इसलिए नई file को अपनी वर्तमान file से compare करें, न कि उसे सीधे working install पर copy करें।

FAQ

AFFiNE container start क्यों नहीं होता है?

affine service, affine_migration job पर condition: service_completed_successfully घोषित करती है। यदि migration 0 के अलावा किसी अन्य status के साथ exit होता है, तो सर्वर कभी start नहीं होता और कोई web interface दिखाई नहीं देता। यह देखने के लिए कि कौन सा step रुका है, docker compose logs affine_migration चलाएँ। जाँचें कि क्या हाथ से edit की गई compose file अभी भी pgvector/pgvector:pg16 का उपयोग कर रही है: stock Postgres में pgvector extension शामिल नहीं है जिसे AFFiNE का schema घोषित करता है, भले ही v0.27.4 में अब वे चार embedding models परिभाषित नहीं हैं जो v0.27.3 में मौजूद थे।

self-hosted AFFiNE को कितनी RAM चाहिए?

AFFiNE का requirements page कम से कम 4 CPU cores और 2 GB RAM की मांग करता है। जब documents 10,000 शब्दों से अधिक हो जाते हैं, तो यह आवश्यकता 4 GB तक बढ़ जाती है। यह ध्यान दें कि 10,000 बदलावों वाले document को merge करते समय यह 1 GB तक peak पर पहुँच सकता है। 2 GB वाले सर्वर पर यही peak समस्या पैदा करता है, न कि idle load: kernel का out-of-memory killer किसी process को रोकने के लिए चुनता है, और सबसे बड़ी होने के कारण AFFiNE server के रुकने की संभावना सबसे अधिक होती है। इसके बाद restart: unless-stopped इसे फिर से start कर देता है, इसलिए users को error के बजाय page reload दिखाई देता है। इसकी पुष्टि docker inspect affine_server --format '{{.State.OOMKilled}}' और sudo dmesg -T | grep -i 'out of memory' से करें, फिर short spikes के लिए buffer के रूप में 2 GB की swap file जोड़ें। यदि kills जारी रहते हैं, तो 4 GB RAM पर upgrade करें।

AFFiNE मेरा data कहाँ store करता है और मुझे किसका backup लेना चाहिए?

आपके compose directory के अंतर्गत तीन paths में सब कुछ सुरक्षित रहता है: database के लिए ./data/postgres, upload की गई files के लिए ./data/storage, और config.json तथा generate की गई private.key के लिए ./config। Database का backup files को copy करने के बजाय docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump से लें, क्योंकि चल रहे Postgres को सुरक्षित रूप से copy नहीं किया जा सकता। उस dump में admin panel में save की गई सभी settings भी होती हैं, क्योंकि AFFiNE उन्हें database में ही store करता है। uploads के लिए ./data/storage को tar करें और पूरी config directory, Compose file और .env को सुरक्षित रखें, क्योंकि ये files database का हिस्सा नहीं हैं।

क्या self-hosted AFFiNE पर real-time collaboration काम करता है?

हाँ, और इसके लिए कुछ भी enable करने की आवश्यकता नहीं है। एकमात्र आवश्यकता आपका reverse proxy है, क्योंकि sync WebSocket connections पर चलता है। nginx पर इसका मतलब है proxy_http_version 1.1 के साथ Upgrade और Connection: upgrade headers, जबकि Traefik और Caddy बिना किसी अतिरिक्त configuration के उन connections को pass कर देते हैं। यदि proxy उन्हें upgrade नहीं करता है, तो workspace load तो हो जाएगा और login भी सामान्य रूप से होगा, लेकिन एक browser में किए गए edits दूसरे में कभी दिखाई नहीं देंगे।

क्या मैं stock Postgres image के साथ AFFiNE चला सकता हूँ?

pgvector/pgvector:pg16 का उपयोग करें, जो कि Postgres 16 है जिसमें pgvector extension पहले से compiled है। AFFiNE v0.27.4 अभी भी schema.prisma में extensions = [pgvector(map: "vector")] घोषित करता है, लेकिन अब उन चार models को परिभाषित नहीं करता है जिनमें v0.27.3 में vector(1024) embedding columns मौजूद थे। Stock Postgres में घोषित extension शामिल नहीं है। यदि आप AFFiNE को किसी external Postgres server पर point करते हैं, तो उस पर pgvector install करें और migration चलाने से पहले target database में extension create करें।

क्या यह लेख उपयोगी था?