SSD Nodes Learn 🎉 VPS $4.99/माह से
गाइड Matt Connorलेखक: Matt Connor

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

Docker Compose का उपयोग करके AFFiNE को अपने VPS पर सेटअप करें। इस गाइड में चार 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 को प्रकाशित किया गया था।

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

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

affine_migration एक वन-शॉट जॉब है जो node ./scripts/self-host-predeploy.js को चलाती है, डेटाबेस माइग्रेशन लागू करती है और फिर बंद हो जाती है। एप्लिकेशन उस जॉब पर condition: service_completed_successfully घोषित करता है, इसलिए यदि माइग्रेशन non-zero स्टेटस के साथ बंद होता है, तो इसका मतलब है कि affine कभी शुरू ही नहीं होगा। जब वेब इंटरफेस दिखाई न दे, तो उस जॉब का लॉग सबसे पहले पढ़ना चाहिए।

postgres आपके दस्तावेज़ों, उपयोगकर्ताओं, वर्कस्पेस और अनुमतियों को सुरक्षित रखता है। प्रदान की गई इमेज pgvector/pgvector:pg16 है, जो pgvector एक्सटेंशन के साथ कंपाइल किया गया सामान्य Postgres 16 है। pgvector, Postgres में vector कॉलम प्रकार जोड़ता है, जो एम्बेडिंग स्टोर करने के लिए उपयोग किया जाने वाला संख्यात्मक रूप है ताकि टेक्स्ट को उसके अर्थ के आधार पर खोजा जा सके।

redis एक अनिवार्य निर्भरता (hard dependency) है: सर्वर और माइग्रेशन जॉब दोनों ही शुरू होने से पहले इसके हेल्थ चेक का इंतज़ार करते हैं। ध्यान दें कि प्रदान की गई compose फाइल Redis को क्या नहीं देती है, जो कि एक वॉल्यूम है। इसके अंदर कुछ भी docker compose down के बाद सुरक्षित नहीं रहता है, और यह स्पष्ट रूप से बताता है कि इसमें आपकी कोई सामग्री नहीं है और इसे किसी बैकअप की आवश्यकता नहीं है।

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

यह आवश्यकता AFFiNE के schema से आती है, न कि किसी प्राथमिकता के कारण। schema.prisma में datasource extensions = [pgvector(map: "vector")] घोषित करता है, और चार tables में embedding column होता है जिसका type vector(1024) है। migration job उन tables को तब भी बनाता है चाहे आप AI features चालू करें या न करें, इसलिए migration पूरा होने से पहले database में extension का मौजूद होना अनिवार्य है। यदि आप postgres:16 का उपयोग करते हैं, तो extension गायब हो जाता है, migration उन columns को नहीं बना पाता, और server एक ऐसे job के इंतज़ार में रुक जाता है जो विफल हो चुका है।

AFFiNE version 0.21 पर pgvector image पर चला गया। उससे पुराने install पर, केवल image line को बदलना ही पूरा 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 बदलने का अर्थ है data का dump लेना और उसे एक नई data directory में restore करना।

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

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

अब इसे 2 GB वाले प्लान के साथ देखें जहाँ दो लोग लिख रहे हैं। औसत उपयोग ठीक रहता है। Postgres और Node process सीमा के भीतर रहते हैं और कुछ जगह खाली भी रहती है। समस्या peak की है। एक बड़ा merge operation पहले से चल रही प्रक्रियाओं के ऊपर 1 GB की अतिरिक्त मांग कर सकता है, और बिना swap वाले 2 GB के सर्वर पर kernel का out-of-memory (OOM) killer उस मांग को पूरा करने के लिए सबसे बड़ी प्रक्रिया को बंद कर देता है, जो कि AFFiNE सर्वर है।

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

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 लाइन का मतलब है कि मेमोरी खत्म हो गई थी, न कि कोई bug मिला है। इसे दोनों तरफ से ठीक करें। पहले swap जोड़ें, ताकि spike घातक होने के बजाय धीमी हो जाए:

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 को मृत होने देता है। सुधार का दूसरा हिस्सा यह है कि Postgres को उस जगह में अपना cache बढ़ाने से रोकें जिसकी आवश्यकता application को merge के समय होती है, जिसके लिए Compose service पर memory limits का उपयोग किया जाता है।

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

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

Compose फ़ाइल स्वयं लिखें, टैग्स को पिन करें

दस्तावेजीकरण में दिया गया इंस्टॉलेशन curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml के साथ एक तैयार फ़ाइल डाउनलोड करता है। वह काम करती है। उस पर निर्भर होने से पहले एक विवरण जानना महत्वपूर्ण है: 8 अगस्त 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} को प्रतिस्थापित (substitute) करता है, इसलिए पासवर्ड उस फ़ाइल में कभी नहीं दिखता जिसे आप किसी सपोर्ट थ्रेड में पेस्ट करेंगे। यह आदत हर उस स्टैक के लिए बनाए रखना उचित है जिसे आप चलाते हैं, और इसका तर्क 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 पोर्ट को केवल लूपबैक एड्रेस पर पब्लिश करता है, ताकि सर्वर के बाहर से कोई भी AFFiNE तक तब तक न पहुँच सके जब तक आप स्वयं निर्णय न लें। अपस्ट्रीम का '3010:3010' हर इंटरफ़ेस को बाइंड करता है, और अधिकांश VPS इमेजेस पर इसमें पब्लिक इंटरफ़ेस भी शामिल होता है।
  • POSTGRES_HOST_AUTH_METHOD: trust को हटा दिया गया है और इसके बजाय एक पासवर्ड सेट किया गया है। ट्रस्ट ऑथेंटिकेशन उस डेटाबेस के किसी भी कनेक्शन को बिना पासवर्ड के 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

docker compose ps में affine_postgres और affine_redis को healthy, affine_server को running, और affine_migration_job को exited (0) स्टेट में दिखाना चाहिए। माइग्रेशन जॉब पर कोई भी अन्य एग्जिट कोड वह समस्या है जिसे हल करना है, और इसका लॉग उस स्टेप को बताता है जहाँ प्रक्रिया रुकी है:

docker compose logs affine_migration

भूलने से पहले इमेज को पिन करें

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

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

यह ghcr.io/toeverything/affine@sha256: और उसके बाद एक लंबा हैश प्रिंट करता है। पूरी स्ट्रिंग को affine और affine_migration दोनों की image: लाइन में पेस्ट करें। उन दोनों का हमेशा मेल खाना आवश्यक है, क्योंकि वे एक ही इमेज हैं जो दो भूमिकाएँ निभा रही हैं, और बेमेल होने का अर्थ है डेटाबेस को एक स्कीमा पर माइग्रेट करना जबकि उसे दूसरे के साथ सर्व करना। अपग्रेड करना तब एक सोची-समझी प्रक्रिया बन जाती है, न कि कोई आश्चर्य: डाइजेस्ट बदलें, बैकअप लें, 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 को पब्लिक नाम पर डालना सुरक्षित है।

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

तीन paths में सारा डेटा सुरक्षित रहता है, और ये सभी आपके द्वारा बनाए गए directory के अंदर स्थित होते हैं।

  • ./data/postgres Postgres डेटा directory है: इसमें documents, users, workspaces और permissions होते हैं।
  • ./data/storage को container के अंदर /root/.affine/storage पर mount किया जाता है और इसमें सभी uploaded files होती हैं।
  • ./config को /root/.affine/config पर mount किया जाता है और इसमें config.json होता है।

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

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
cp config/config.json backup/config-$(date +%F).json

config.json को अपने बैकअप में मैन्युअल रूप से रखें। अगस्त 2026 में जांचे जाने पर, AFFiNE का डॉक्यूमेंटेशन अभी भी एडमिन पैनल से कॉन्फ़िगरेशन एक्सपोर्ट को 'not implemented' बताता है, इसलिए डिस्क पर मौजूद फाइल ही आपकी सेटिंग्स की एकमात्र कॉपी है। तीनों फाइलों को सर्वर से बाहर कॉपी करें। जिस डिस्क पर डेटा है, उसी पर बैकअप रखना कोई बैकअप नहीं है।

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

रिस्टोर करने के आधिकारिक चरणों को जरूरत पड़ने से पहले पढ़ें, और उन्हें ध्यान से पढ़ें। अगस्त 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

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

AFFiNE को अपने मौजूदा proxy के पीछे लगाना

AFFiNE, WebSocket का उपयोग करता है और यह अनिवार्य है। इसका documentation स्पष्ट है: WebSocket ही AFFiNE के sync और collaboration system का आधार है। यदि आपका proxy इन connections को upgrade नहीं करता है, तो आपका workspace sync करना बंद कर देगा। पेज load होगा, login काम करेगा, लेकिन एक browser में किया गया बदलाव दूसरे तक नहीं पहुँचेगा। अपने browser के developer tools में Network tab खोलें और WS के लिए filter करें। यदि connection बार-बार खुल और बंद हो रहा है, तो इसका मतलब है कि proxy upgrade को pass नहीं कर रहा है।

यदि आप अन्य containers के लिए पहले से ही Traefik चला रहे हैं, तो AFFiNE एक सामान्य service के रूप में इसमें शामिल हो जाएगा। ports: block को affine service से हटा दें, फिर यह जोड़ें:

    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'

और file के अंत में, services: के साथ:

networks:
  proxy:
    external: true

Certificate resolver का नाम आपके Traefik configuration में परिभाषित नाम से मेल खाना चाहिए, और loadbalancer.server.port container port 3010 है, न कि host port। Traefik बिना किसी अतिरिक्त configuration के WebSocket connections को proxy करता है, इसलिए इसमें कुछ और जोड़ने की आवश्यकता नहीं है। एक ही instance के पीछे कई apps चलाने के बारे में a single Traefik in front of several apps में बताया गया है।

nginx पर आपको upgrade के लिए स्पष्ट रूप से निर्देश देना होगा:

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 का default मान 1 MB होता है, इसलिए उस line के बिना, एक छोटी photo से बड़ी कोई भी upload 413 status के साथ विफल हो जाएगी और AFFiNE logs में कुछ भी दिखाई नहीं देगा, क्योंकि request कभी पहुँची ही नहीं। Caddy को केवल एक line, reverse_proxy http://127.0.0.1:3010 की आवश्यकता होती है, और यह certificates तथा WebSocket upgrades को स्वयं संभाल लेता है।

Self-hosted build में क्या शामिल नहीं है

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

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

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

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

अपग्रेड

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

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 image का उपयोग करना है, क्योंकि AFFiNE schema pgvector extension घोषित करता है और ऐसे vector(1024) columns के साथ tables बनाता है जिन्हें सामान्य Postgres नहीं बना सकता।

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 memory की आवश्यकता हो सकती है। 2 GB वाले सर्वर पर यही peak memory समस्या पैदा करती है, न कि idle load: kernel का out-of-memory killer AFFiNE process को रोक देता है और restart: unless-stopped उसे फिर से start कर देता है, जिससे users को error के बजाय page reload दिखाई देता है। इसे docker inspect affine_server --format '{{.State.OOMKilled}}' और sudo dmesg -T | grep -i 'out of memory' के साथ confirm करें, फिर 2 GB की swap file जोड़ें ताकि spike घातक होने के बजाय धीमा हो जाए।

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

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

क्या 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 में किए गए बदलाव दूसरे में कभी दिखाई नहीं देंगे।

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

नहीं। AFFiNE का schema.prisma, extensions = [pgvector(map: "vector")] घोषित करता है और vector(1024) type के embedding column के साथ चार tables परिभाषित करता है। AI features बंद होने पर भी migration job उन tables को बनाती है। pgvector/pgvector:pg16 का उपयोग करें, जो कि Postgres 16 है और जिसमें वह extension पहले से compiled है। यदि आप AFFiNE को किसी external Postgres सर्वर से जोड़ते हैं, तो उस पर pgvector install करें और migration चलाने से पहले target database में extension बनाएँ।