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 को प्रकाशित किया गया था।
चारों 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 से आती है, न कि किसी प्राथमिकता से। 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 शब्दों से अधिक हो जाते हैं तो यह memory को बढ़ाकर 4 GB करने का सुझाव देता है। वही पेज बताता है कि memory कहाँ खर्च होती है: sync system और document merging में। इसमें एक आंकड़ा याद रखने योग्य है, कि 10,000 बदलावों वाले document को merge करने पर यह 1 GB तक पहुँच सकता है।
अब इसे 2 GB वाले plan के संदर्भ में देखें जहाँ दो लोग लिख रहे हैं। औसत उपयोग ठीक है। Postgres और Node process सीमा के भीतर रहते हैं और जगह भी बचती है। समस्या peak के समय आती है। एक बड़ा merge पहले से चल रही memory के अतिरिक्त 1 GB की मांग कर सकता है, और बिना swap वाले 2 GB के box पर kernel का out-of-memory (OOM) killer उस मांग को सबसे बड़ी process यानी AFFiNE server को kill करके पूरा करता है।
आपके सहकर्मी को कोई error नहीं दिखता। उन्हें केवल page reload होता दिखता है, क्योंकि restart: unless-stopped कुछ ही seconds में container को वापस ले आता है। इसका अनुमान न लगाएँ, इसकी पुष्टि करें:
docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'पहले command से true, या दूसरे से node को दर्शाने वाली Killed process line का मतलब है कि आप memory की कमी का सामना कर रहे हैं, न कि किसी bug का। इसे दोनों तरफ से ठीक करें। पहले swap जोड़ें, ताकि spike fatal होने के बजाय slow हो जाए:
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 -hfree -h को अब 2.0Gi swap total दिखाना चाहिए। Swap AFFiNE को fast नहीं बनाता, और न ही इसका उद्देश्य यह है। यह एक-second के spike को dead container के बजाय एक slow second में बदल देता है। सुधार का दूसरा हिस्सा यह है कि Postgres को अपनी cache को उस जगह तक बढ़ने से रोकें जिसकी आवश्यकता application को merge के समय होती है, जिसके लिए Compose service पर memory limits का उपयोग किया जाता है।
Storage का अनुमान लगाना कहीं अधिक आसान है। ये वे आंकड़े हैं जिन्हें AFFiNE उसी पेज पर प्रकाशित करता है:
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
}
]Server install में 1.5 GB जगह लगती है। लगभग एक हजार शब्दों वाले एक हजार documents, 0.1 GB Postgres data जोड़ते हैं, जो लगभग न के बराबर है। एक हजार uploaded files 10 GB जोड़ती हैं, जो पूरी कहानी है। ये एक running instance से लिए गए मापन के बजाय प्रकाशित planning figures हैं, इसलिए इन्हें एक अनुमान के रूप में देखें, न कि किसी वादे के रूप में। महत्वपूर्ण बात यह है: आपका database छोटा रहता है, और आपके uploads ही disk usage तय करते हैं।
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} का उपयोग होता है, जबकि दस्तावेज़ का संदर्भ पृष्ठ एक नया लेआउट दिखाता है जो सब कुछ ./data के अंतर्गत रखता है और जिसे किसी .env की आवश्यकता नहीं होती है। दोनों ही वास्तविक हैं। फ़ाइल को स्वयं लिखने से यह प्रश्न हल हो जाता है, और आपको वैसे भी images को पिन करने और database password सेट करने के लिए इसे संपादित करना ही होगा।
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose अपने आप प्रोजेक्ट डायरेक्टरी से .env पढ़ता है और आपके लिए ${DB_PASSWORD} को प्रतिस्थापित (substitute) करता है, इसलिए पासवर्ड कभी भी उस फ़ाइल में नहीं दिखता जिसे आप सपोर्ट थ्रेड में पेस्ट करेंगे। यह आदत हर उस stack के लिए बनाए रखना उचित है जिसे आप चलाते हैं, और इसका कारण compose फ़ाइल से secrets को बाहर रखना में दिया गया है।
अब ~/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 address पर प्रकाशित करता है, ताकि सर्वर के बाहर से कोई भी AFFiNE तक तब तक न पहुँच सके जब तक आप यह तय न कर लें कि कैसे। अपस्ट्रीम'3010:3010'हर इंटरफ़ेस को bind करता है, और अधिकांश VPS images पर इसमें public इंटरफ़ेस भी शामिल होता है।POSTGRES_HOST_AUTH_METHOD: trustहटा दिया गया है और इसके बजाय एक पासवर्ड सेट किया गया है। Trust authentication उस डेटाबेस के किसी भी कनेक्शन को बिना पासवर्ड केaffineउपयोगकर्ता के रूप में स्वीकार करता है। यह निजी Compose नेटवर्क तक सीमित है, जो तब तक ठीक है जब तक आप उस नेटवर्क से एक और कंटेनर नहीं जोड़ते या डिबगिंग के दौरान 5432 प्रकाशित नहीं करते।redis:8-alpineएक साधारणredisकी जगह लेता है, जोlatestपर resolve होता है। अगस्त 2026 तक यह Redis 8 है, इसलिए पिन उस major version को बनाए रखता है जिसका आपने परीक्षण किया है और भविष्य में किसी असंबंधितdocker compose pullके दौरान Redis 9 के आने को रोकता है।pgvector/pgvector:pg16बिल्कुल वैसा ही रहता है जैसा अपस्ट्रीम ने इसे सेट किया है, जिसका कारण ऊपर दिया गया है।
POSTGRES_PASSWORD को केवल तब पढ़ा जाता है जब Postgres पहली बार अपनी डेटा डायरेक्टरी बनाता है। पहले से मौजूद instance पर, पासवर्ड को 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 psdocker compose ps में affine_postgres और affine_redis को healthy, affine_server को running, और affine_migration_job को exited (0) स्टेट में दिखाना चाहिए। माइग्रेशन जॉब पर कोई भी अन्य एग्जिट कोड वह समस्या है जिसकी जांच करनी है, और इसका लॉग उस स्टेप को बताता है जहाँ प्रक्रिया रुकी है:
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 पर bind होता है। इसे अपनी मशीन से SSH tunnel के माध्यम से एक्सेस करें:
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/postgresPostgres डेटा 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 में कवर किया गया विषय है।
AFFiNE का बैकअप कैसे लें
दो चीजों का बैकअप लेना आवश्यक है, और दोनों के बैकअप लेने की प्रक्रिया अलग है। डेटाबेस एक लाइव सर्वर है, इसलिए चलते हुए सर्वर से फाइलें कॉपी करने पर आपको एक करप्ट (corrupt) कॉपी मिलेगी। इसके बजाय इसका डंप (dump) लें:
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).jsonconfig.json को अपने backup में manually रखें। August 2026 में जाँचे गए AFFiNE documentation में अभी भी admin panel से configuration export को implemented नहीं बताया गया है, इसलिए disk पर मौजूद file ही आपकी settings की एकमात्र copy है। तीनों files को server से बाहर copy करें। जिस disk पर सुरक्षित की जाने वाली चीज़ मौजूद हो, उसी disk पर रखा backup backup नहीं होता। Database dump और uploads directory के tar archive का यह विभाजन आपके द्वारा चलाए जाने वाले हर दूसरे stateful container पर दोहराया जाने वाला pattern है। यही pattern conversation history और attachments को भी सुरक्षित रखता है, जब आप support desk के रूप में Chatwoot को self-host करते हैं।
रिस्टोर करना, और प्रकाशित चरणों में एक खामी
रिस्टोर करने के आधिकारिक चरणों को जरूरत पड़ने से पहले पढ़ें, और उन्हें ध्यान से पढ़ें। अगस्त 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 -drm के बजाय mv पर ध्यान दें। जिस डेटाबेस की आपने कॉपी नहीं रखी है, उसके ऊपर रिस्टोर करना ही वह तरीका है जिससे एक गलत कमांड पूरी तरह से डेटा लॉस का कारण बन जाती है, और पुरानी डायरेक्टरी को दूसरी जगह मूव करने में कुछ नहीं जाता। tar xzf backup/storage-2026-08-08.tgz -C data के साथ अपलोड्स को भी रिस्टोर करें, अन्यथा हर डॉक्यूमेंट टूटे हुए अटैचमेंट्स के साथ दिखाई देगा। इसके बाद लॉग इन करें और ऐसा डॉक्यूमेंट खोलें जिसमें कोई इमेज हो। यही टेस्ट है। जिसे आपने ब्राउज़र में खोलकर नहीं देखा, वह रिस्टोर केवल एक फाइल है, बैकअप नहीं।
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 में जोड़कर उससे login कर सकती है। अपनी टीम द्वारा निर्भर किए जाने वाले सटीक offline व्यवहार का परीक्षण करने से पहले प्रतिबद्ध न हों: नेटवर्क बंद करके desktop app में edit करें, फिर से कनेक्ट करें, और फिर दूसरे डिवाइस पर परिणाम देखें। फीचर लिस्ट सबूत नहीं होती, और इसमें यह लिस्ट भी शामिल है।
Server-side full-text search shipped compose file में बंद है, जहाँ सर्वर और migration job पर AFFINE_INDEXER_ENABLED=false सेट है। इसे चालू करने का मतलब है एक Manticore Search container जोड़ना, जो कि पांचवीं सर्विस है और अधिक मेमोरी लेती है। 2 GB वाले बॉक्स पर, यही वह बदलाव है जो आपको सीमा से बाहर ले जाता है। आपके द्वारा खोले गए workspace पर client के अंदर search अभी भी काम करती है।
लोगों को आमंत्रित करने से पहले दो सीमाओं के बारे में जानना जरूरी है। एक self-hosted workspace को अधिकतम 10 seats दी जाती हैं, और उससे आगे जाने के लिए AFFiNE से Team license की आवश्यकता होती है। documentation में self-hosted instances के लिए unlimited blob storage और unlimited blob size का उल्लेख है कि वे इच्छित हैं लेकिन अभी पूरी तरह से लागू नहीं हुए हैं, अगस्त 2026 में जाँच की गई। इनमें से कोई भी बात किसी घर या छोटी टीम के लिए मायने नहीं रखती। यदि आप चालीस लोगों को शिफ्ट करने की योजना बना रहे हैं, तो ये दोनों बातें मायने रखती हैं।
Upgrades
सबसे पहले 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 वाले users के लिए एक ऐतिहासिक जानकारी: version 0.23.0 से 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 modifications वाले document को merge करने पर 1 GB तक की peak memory लग सकती है। 2 GB वाले सर्वर पर वह peak ही समस्या पैदा करता है, न कि 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' से करें, फिर 2 GB की swap file जोड़ें ताकि spike घातक होने के बजाय धीमा हो जाए।
AFFiNE मेरा data कहाँ store करता है, और मुझे किसका backup लेना चाहिए?
आपके compose directory के अंतर्गत तीन paths में सब कुछ होता है: database के लिए ./data/postgres, uploaded files के लिए ./data/storage, और config.json के लिए ./config। files को copy करने के बजाय docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump के साथ database का backup लें, क्योंकि चल रहे 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 उन connections को बिना किसी अतिरिक्त configuration के pass कर देते हैं। जो proxy उन्हें upgrade नहीं करता, उसका लक्षण यह है कि workspace सामान्य रूप से load और login हो जाता है, जबकि एक browser में किए गए edits दूसरे में कभी दिखाई नहीं देते।
क्या मैं 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 का उपयोग करें, जो कि उस extension के साथ compile किया गया Postgres 16 है। यदि आप AFFiNE को किसी external Postgres सर्वर की ओर point करते हैं, तो उस पर pgvector install करें और migration चलाने से पहले target database में extension बनाएँ।