Docker Compose-এ AFFiNE self-host করার সম্পূর্ণ গাইড
একটি VPS-এ Docker Compose দিয়ে AFFiNE চালান: 4টি container, pinned image tag, data কোথায় থাকে, backup এবং 2 GB RAM-এ বাস্তবে কতটা ব্যবহার করা যায় জানুন।
self-hosting AFFiNE করলে যা পাবেন
Self-hosting AFFiNE করলে আপনার নিয়ন্ত্রণাধীন সার্ভারে Notion-এর মতো একটি workspace চালাতে পারবেন। এটি 4টি container হিসেবে চলে: application, one-shot migration job, Postgres এবং Redis। সর্বোচ্চ 10টি seat পর্যন্ত real-time collaboration অন্তর্ভুক্ত থাকে, যা self-hosted workspace-এ default হিসেবে দেওয়া হয়। Installation-এর জন্য একটি compose file এবং একটি JSON config file যথেষ্ট। তবে image tag, disk layout, memory ceiling এবং সামনে ব্যবহৃত proxy নিয়ে পরিকল্পনা করতে হবে।
AFFiNE একই workspace-এ document editor এবং infinite canvas রাখে। তাই একটি page-কে document হিসেবে পড়া যায়, অথবা ছড়িয়ে whiteboard হিসেবেও ব্যবহার করা যায়। আপনি এখনও কী চালাবেন তা নির্ধারণ করে না থাকলে আগে self-hosted Notion বিকল্পগুলোর তুলনা পড়ুন। এই guide ধরে নিচ্ছে যে সিদ্ধান্ত নেওয়া হয়ে গেছে। এখানে আবার তুলনা না করে AFFiNE সঠিকভাবে চালানোর বিষয়টি দেখানো হয়েছে।
এখানে দেওয়া তথ্য AFFiNE-এর self-host documentation এবং 8 August 2026 তারিখে প্রকাশিত release file মিলিয়ে যাচাই করা হয়েছে। ওই তারিখে সর্বশেষ stable release ছিল 0.27.3, যা 23 July 2026-এ প্রকাশিত হয়।
চারটি container আসলে কী করে
affine একই image-এ server এবং web client হিসেবে কাজ করে। এটি port 3010-এ listening করে।
affine_migration একটি one-shot job, যা node ./scripts/self-host-predeploy.js চালায়, database migration প্রয়োগ করে এবং তারপর বন্ধ হয়ে যায়। Application-টি ওই job-এর ওপর condition: service_completed_successfully ঘোষণা করে। তাই migration non-zero status দিয়ে বন্ধ হলে affine একেবারেই start হয় না। Web interface চালু না হলে প্রথমে ওই job-এর log পড়ুন।
postgres আপনার document, user, workspace এবং permission সংরক্ষণ করে। সরবরাহ করা image হলো pgvector/pgvector:pg16। এটি pgvector extension-সহ সাধারণ Postgres 16। pgvector Postgres-এ একটি vector column type যোগ করে। Text-এর embedding সংরক্ষণে এই numeric form ব্যবহার করা হয়, যাতে অর্থের ভিত্তিতে text search করা যায়।
redis একটি hard dependency। Server এবং migration job—উভয়ই start হওয়ার আগে এর health check-এর জন্য অপেক্ষা করে। সরবরাহ করা compose file Redis-কে যা দেয় না, তা হলো একটি volume। docker compose down-এর পর এর ভেতরের কোনো কিছু টিকে থাকে না। তাই স্পষ্ট যে এতে আপনার কোনো content থাকে না এবং এর backup-এর প্রয়োজন নেই।
Postgres image কেন stock postgres নয়, pgvector
এই প্রয়োজনীয়তা AFFiNE-এর schema থেকে এসেছে, কোনো পছন্দের কারণে নয়। schema.prisma-এ datasource extensions = [pgvector(map: "vector")] ঘোষণা করে, এবং চারটি table-এ embedding column রয়েছে, যার type vector(1024)। আপনি AI feature চালু করুন বা না করুন, migration job এই table-গুলো তৈরি করে। তাই migration শেষ হওয়ার আগে database-এ extension-টি থাকতে হবে। postgres:16 বসালে extension আর থাকে না। Migration ওই column-গুলো তৈরি করতে পারে না। ফলে 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 পরিবর্তনের জন্য dump নিয়ে নতুন data directory-তে restore করতে হয়।
Self-hosted AFFiNE-এর কত CPU এবং RAM প্রয়োজন
AFFiNE-এর requirements page-এ অন্তত 4টি CPU core এবং 2 GB RAM চাওয়া হয়েছে। আপনার document 10,000 শব্দের বেশি হলে প্রয়োজনীয় memory 4 GB-এ বেড়ে যায়। একই page-এ memory কোথায় ব্যবহৃত হয় তাও বলা হয়েছে: sync system এবং document merging। সেখানে মনে রাখার মতো একটি সংখ্যা দেওয়া আছে: 10,000টি modification থাকা একটি document merge করার সময় memory usage 1 GB পর্যন্ত উঠতে পারে।
এবার এটি 2 GB plan-এ দুইজনের একসঙ্গে লেখার পরিস্থিতির সঙ্গে মিলিয়ে দেখুন। গড় usage-এ সমস্যা হয় না। Postgres এবং Node process সীমার নিচে থাকে, কিছু memory অব্যবহৃতও থাকে। সমস্যা হয় peak usage-এ। একটি বড় merge ইতিমধ্যে ব্যবহৃত memory-এর অতিরিক্ত 1 GB চাইতে পারে। 2 GB-এর এমন server-এ swap না থাকলে kernel-এর out-of-memory (OOM) killer বড় process-টিকে বন্ধ করে সেই request সামলায়। সাধারণত সেটি AFFiNE server।
আপনার সহকর্মী কোনো error দেখতে পান না। তারা শুধু page 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-এর output-এ true অথবা দ্বিতীয় command-এ Killed process-এর এমন একটি line, যেখানে node-এর নাম আছে, দেখালে বুঝবেন memory শেষ হয়ে গেছে। এটি bug নয়। দুই দিক থেকেই সমাধান করুন। প্রথমে swap যোগ করুন, যাতে memory spike fatal না হয়ে ধীরগতির হয়:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hএখন free -h-এ মোট 2.0Gi swap দেখানোর কথা। Swap AFFiNE-কে দ্রুত করে না এবং এর উদ্দেশ্যও তা নয়। এটি এক সেকেন্ডের spike-কে container বন্ধ হয়ে যাওয়ার পরিবর্তে ধীরগতির একটি second-এ পরিণত করে। সমাধানের অন্য দিক হলো Postgres-এর cache যেন merge-এর সময় application-এর প্রয়োজনীয় memory দখল করে না বাড়ে তা নিশ্চিত করা। এ কাজের জন্যই Compose service-এ memory limit ব্যবহার করা হয়।
Storage-এর প্রয়োজনীয়তা অনুমান করা অনেক সহজ। একই page-এ 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 লাগে। প্রতিটি প্রায় 1,000 শব্দের 1,000টি document যোগ করলে Postgres data-তে 0.1 GB যোগ হয়, যা প্রায় কিছুই নয়। 1,000টি uploaded file যোগ করলে 10 GB যোগ হয়; মূল বিষয় এটিই। এগুলো চলমান instance থেকে নেওয়া measurement নয়, বরং প্রকাশিত planning figure। তাই এগুলোকে নির্দিষ্ট প্রতিশ্রুতি নয়, usage-এর ধরন বোঝানোর নির্দেশক হিসেবে বিবেচনা করুন। গুরুত্বপূর্ণ বিষয় হলো ধরনটি: আপনার database ছোট থাকবে, আর disk usage নির্ধারণ করবে আপনার upload।
নিজেই Compose ফাইল লিখুন, tag pin করুন
ডকুমেন্টে দেখানো ইনস্টল প্রক্রিয়া curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml দিয়ে তৈরি করা একটি ফাইল download করে। এটি কাজ করে। তবে এর ওপর নির্ভর করার আগে একটি বিষয় জানা দরকার: 8 August 2026 অনুযায়ী release 0.27.3-এর সঙ্গে সংযুক্ত ফাইলটি .env ফাইল থেকে path পড়ে এবং ${UPLOAD_LOCATION}, ${CONFIG_LOCATION} ও ${DB_DATA_LOCATION} ব্যবহার করে। অন্যদিকে documentation-এর reference page-এ দেখানো নতুন layout-এ সবকিছু ./data-এর অধীনে রাখা হয়েছে এবং সেখানে .env একেবারেই প্রয়োজন হয় না। দুটিই কার্যকর। নিজেই ফাইল লিখলে এই পার্থক্য নিয়ে প্রশ্ন থাকে না। তা ছাড়া image pin করা এবং database password সেট করার জন্য ফাইলটি আপনাকেই সম্পাদনা করতে হবে।
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose নিজে থেকেই project directory থেকে .env পড়ে এবং আপনার হয়ে ${DB_PASSWORD} প্রতিস্থাপন করে। ফলে support thread-এ paste করার ফাইলে password দেখা যায় না। আপনি যে প্রতিটি stack চালান, সেখানেই এই অভ্যাস বজায় রাখা উচিত। এর কারণ compose file-এর বাইরে secret রাখা-এ ব্যাখ্যা করা হয়েছে।
এখন ~/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-stoppedupstream-এর সরবরাহ করা ফাইল থেকে এখানে চারটি পার্থক্য আছে। প্রতিটিরই নির্দিষ্ট কারণ রয়েছে।
127.0.0.1:3010:3010port-টি শুধু loopback address-এ publish করে। তাই আপনি পদ্ধতি নির্ধারণ না করা পর্যন্ত server-এর বাইরে থেকে কেউ AFFiNE-এ পৌঁছাতে পারবে না। upstream-এর'3010:3010'প্রতিটি interface-এ bind করে। অধিকাংশ VPS image-এ এর মধ্যে public interface-ও থাকে।POSTGRES_HOST_AUTH_METHOD: trustবাদ দেওয়া হয়েছে এবং তার বদলে password সেট করা হয়েছে। Trust authentication ব্যবহার করলে password ছাড়াই যেকোনো connection ওই database-এaffineuser হিসেবে গ্রহণ করা হয়। এটি private Compose network-এ সীমাবদ্ধ থাকলে সমস্যা নেই। তবে debugging-এর সময় ওই network-এ আরেকটি container যুক্ত করলে বা 5432 publish করলে ঝুঁকি তৈরি হবে।- সরাসরি
redisব্যবহারের পরিবর্তেredis:8-alpineব্যবহার করা হয়েছে। এটিlatest-এ resolve হয়। August 2026 অনুযায়ী সেটি Redis 8। তাই এই pin পরীক্ষিত major version ধরে রাখে এবং কোনো সম্পর্কহীনdocker compose pullচলাকালে ভবিষ্যতের Redis 9 এসে যাওয়া ঠেকায়। pgvector/pgvector:pg16upstream যেভাবে সেট করেছে, ঠিক সেভাবেই রাখা হয়েছে। এর কারণ উপরে দেওয়া হয়েছে।
Postgres প্রথমবার data directory তৈরি করার সময়ই POSTGRES_PASSWORD পড়ে। ইতিমধ্যে থাকা instance-এ docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" দিয়ে password সেট করুন। এরপর মিলিয়ে নিতে DATABASE_URL update করুন।
Configuration config/config.json-এ থাকে
AFFiNE তার সেটিংস config/config.json থেকে পড়ে। এটি সেই directory, যেটি আপনি /root/.affine/config-এ mount করেছেন। কোনো কিছু আপনার জন্য এই file তৈরি করে না। তাই প্রথমবার start করার আগে এটি লিখুন। Editor-এ ~/affine/config/config.json খুলে নিচের content দিন। উদাহরণের জায়গায় আপনার নিজের domain বসান:
{
"$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-এ এমন address দিতে হবে, যেটি আপনার users আসলে browser-এ open করেন। AFFiNE এই value থেকে share link এবং workspace invitation তৈরি করে। তাই এটি http://localhost:3010 থাকলে, আপনার পাঠানো invitation recipient-এর নিজের machine-এ নির্দেশ করবে এবং সেখানে ব্যর্থ হবে। প্রথমবার start করার আগেই এটিকে public HTTPS address-এ সেট করুন। এতে file এবং admin panel-এ address নিয়ে অসামঞ্জস্য থাকবে না।
copilot AI feature-গুলো নিয়ন্ত্রণ করে। copilot.byok.enabled হলো bring-your-own-key switch। এটি workspace owner-কে workspace settings-এ নিজের model provider key paste করার সুযোগ দেয়। AFFiNE self-host করলে কোনো AI subscription অন্তর্ভুক্ত হয় না। আপনি এটি না চাইলে উভয় false রাখুন।
Stack start করুন:
docker compose up -d
docker compose psdocker compose ps-এর output-এ affine_postgres এবং affine_redis healthy, affine_server running এবং affine_migration_job-এর state exited (0) দেখানো উচিত। Migration job-এ অন্য কোনো exit code দেখা গেলে সেটিই অনুসন্ধান করতে হবে। এর log-এ কোন step-এ কাজ থেমেছে তা উল্লেখ থাকে:
docker compose logs affine_migrationইমেজটি ভুলে যাওয়ার আগেই নির্দিষ্ট করে দিন
stable একটি পরিবর্তনশীল tag। AFFiNE-এর release workflow প্রতিটি stable build-এর জন্য একাধিক tag নির্দেশ করে। এখানে দুটি tag গুরুত্বপূর্ণ: stable, যা প্রতিটি release-এ নতুন করে নির্দেশ করা হয়, এবং git short hash-সহ stable-, যা পরিবর্তন করা হয় না। stable ব্যবহার করে রাখলে, ছয় মাস পরে একটি docker compose pull ভিন্ন image fetch করবে এবং আপনার বেছে না নেওয়া সময়ে সেই image database-এর বিরুদ্ধে migration চালাবে। আপনি যে সঠিক image পরীক্ষা করেছেন, সেটি নির্দিষ্ট করে দিন:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'এতে ghcr.io/toeverything/affine@sha256:-এর মতো একটি line দেখাবে, যার পরে একটি দীর্ঘ hash থাকবে। সম্পূর্ণ string-টি affine এবং affine_migration—উভয়ের image: line-এ বসান। এই দুটির মান সব সময় একই হতে হবে, কারণ একই image এখানে দুটি ভূমিকা পালন করছে। অমিল হলে database একটি schema-তে migrate হবে, কিন্তু service অন্য schema দিয়ে তা পরিবেশন করবে। এরপর upgrade একটি ইচ্ছাকৃত সম্পাদনা হবে, আকস্মিক ঘটনা নয়: digest পরিবর্তন করুন, backup নিন, docker compose pull, docker compose up -d।
অন্য কেউ করার আগে admin account তৈরি করুন
একটি fresh instance-এ /admin খুললে AFFiNE আপনাকে account creation page-এ পাঠাবে, কারণ সার্ভারে এখনও কোনো administrator নেই। এই flow-তে কোনো invitation code বা setup token নেই। এই page প্রথম যে ব্যক্তি load করবেন, তিনিই আপনার server-এর administrator হবেন। তাই আপনি register না করা পর্যন্ত port বন্ধ রাখতে হবে।
এই কারণেই উপরের compose file-এ 127.0.0.1-এ bind করা হয়েছে। নিজের machine থেকে SSH tunnel ব্যবহার করে এতে পৌঁছান:
ssh -L 3010:127.0.0.1:3010 you@your-server-ipএই tunnel চালু রাখুন এবং নিজের local browser-এ http://127.0.0.1:3010/admin খুলুন। Register করে log in করুন, তারপর tunnel বন্ধ করুন। এখনই instance-কে public name-এ যুক্ত করা নিরাপদ। অন্যান্য self-hosted app-এও একই race condition থাকে। যেসব app-এ প্রথম login-এর সময় hostname-এর সঙ্গে bound একটি passkey তৈরি হয়, সেখানে ঝুঁকি আরও বেশি। তাই আপনি যখন self-host করা openGym করবেন, তখন প্রথম account তৈরির আগেই TLS এবং final domain নির্ধারণ করতে হবে।
AFFiNE আপনার ডেটা যেখানে সংরক্ষণ করে
তিনটি path-এ সব ডেটা থাকে। এগুলো আপনার তৈরি করা directory-এর ভেতরেই রয়েছে।
./data/postgresহলো Postgres-এর data directory। এখানে documents, users, workspaces এবং permissions থাকে।./data/storagecontainer-এ/root/.affine/storageহিসেবে mount করা আছে। এখানে upload করা সব file থাকে।./config/root/.affine/configহিসেবে mount করা আছে। এখানেconfig.jsonথাকে।
এখানে Upstream named volumes-এর পরিবর্তে bind mounts ব্যবহার করে। এটি ইচ্ছাকৃত সিদ্ধান্ত। আপনি সাধারণ command ব্যবহার করে এই path-গুলো tar করে copy করতে পারবেন। Docker এগুলো কোথায় রেখেছে, তা জানতে হবে না। এর বিনিময়ে host-এ file ownership এখন আপনাকেই পরিচালনা করতে হবে। এই trade-off bind mounts এবং named volumes-এ ব্যাখ্যা করা হয়েছে।
AFFiNE কীভাবে ব্যাকআপ করবেন
ব্যাকআপ করতে হবে এমন দুটি জিনিস আছে, এবং সেগুলোর ব্যাকআপের পদ্ধতি আলাদা। Database একটি চলমান server। তাই এটি চলার সময় এর file copy করলে copy-টি 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/Dump-টি container-এর local socket-এর মাধ্যমে container-এর ভেতরেই চলে। তাই এটি password চায় না। ls output-এ এর size পরীক্ষা করুন। কয়েকশো byte-এর file হলে বুঝবেন dump ব্যর্থ হয়েছে, যদিও shell file-টি তৈরি করেছে। এই ব্যর্থতা অনেক সময় ছয় মাস পরে ধরা পড়ে। -T-ও গুরুত্বপূর্ণ। এটি না থাকলে Compose একটি terminal বরাদ্দ করতে পারে এবং binary stream corrupt করতে পারে।
Uploaded file-গুলো সাধারণ file। তাই এগুলো tar করুন:
tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).jsonআপনার backup-এ config.json নিজে হাতে রাখুন। August 2026-এ পরীক্ষা করে দেখা গেছে, AFFiNE documentation-এ admin panel থেকে configuration export এখনও implement করা হয়নি। তাই ডিস্কে থাকা ফাইলটিই আপনার settings-এর একমাত্র copy। তিনটি ফাইলই server-এর বাইরে copy করুন। যে disk-এ সুরক্ষিত ডেটা আছে, একই disk-এ রাখা backup প্রকৃত backup নয়। Database dump এবং uploads directory-এর tar—এই বিভাজনটি আপনি চালানো প্রতিটি অন্য stateful container-এর ক্ষেত্রেও অনুসরণ করুন। আপনি support desk হিসেবে Chatwoot self-host করলে conversation history এবং attachments নিরাপদ রাখার জন্যও একই পদ্ধতি ব্যবহার করবেন।
পুনরুদ্ধার এবং প্রকাশিত ধাপগুলোর একটি সমস্যা
প্রয়োজন হওয়ার আগেই অফিসিয়াল restore ধাপগুলো পড়ুন এবং মনোযোগ দিয়ে পড়ুন। August 2026-এ প্রকাশিত ধাপগুলোতে affine.backup নামের একটি ফাইল container-এ কপি করা হয়, তারপর ./pg.backup থেকে restore করা হয়। এই দুটি ভিন্ন নাম। ধাপগুলোতে ./postgres directory মুছে ফেলা হয়, কিন্তু বর্তমান compose file-এ data রাখা আছে ./data/postgres-এ। snippet-এ থাকা path অনুসরণ না করে আপনি বাস্তবে যে path ব্যবহার করেছেন, সেগুলো অনুসরণ করুন। এই guide-এর layout অনুযায়ী ক্রমটি হলো:
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 ব্যবহারের বিষয়টি লক্ষ করুন। যে database-এর কপি রাখা হয়নি, তার ওপর restore করলেই একটি ভুল command সম্পূর্ণ data loss-এ পরিণত হতে পারে। পুরনো directory-টি অন্য নামে সরিয়ে রাখতে কোনো খরচ নেই। tar xzf backup/storage-2026-08-08.tgz -C data ব্যবহার করে uploads-ও restore করুন। তা না হলে প্রতিটি document-এর attachment ভাঙা অবস্থায় দেখা যাবে। এরপর লগ ইন করে একটি image-যুক্ত document খুলুন। এটিই পরীক্ষা। Browser-এ খোলা হয়নি এমন restore একটি file, backup নয়।
ইতিমধ্যে চালানো proxy-এর পেছনে AFFiNE স্থাপন করা
AFFiNE WebSocket ব্যবহার করে, এবং এটি ঐচ্ছিক নয়। documentation-এ বিষয়টি স্পষ্টভাবে বলা আছে: WebSocket হলো AFFiNE-এর sync ও collaboration system-এর ভিত্তি। তাই কোনো proxy এই connection-গুলো upgrade না করলে এমন workspace তৈরি হয় যেখানে editing নীরবে sync হওয়া বন্ধ করে দেয়। Page load হয়, login কাজ করে, কিন্তু একটি browser-এ করা edit অন্য browser-এ পৌঁছায় না। আপনার browser-এর developer tools-এ Network tab খুলে WS filter করুন। যে connection বারবার open ও close হয়, সেটি সাধারণত এমন proxy নির্দেশ করে যা upgrade pass করছে না।
আপনি যদি অন্য container-এর জন্য আগে থেকেই Traefik চালান, তাহলে AFFiNE-কে একটি সাধারণ service হিসেবে এতে যুক্ত করুন। affine service থেকে ports: block মুছে দিন, তারপর যোগ করুন:
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: trueCertificate resolver-এর নামটি আপনার Traefik configuration-এ সংজ্ঞায়িত নামের সঙ্গে মিলতে হবে। loadbalancer.server.port হলো container port 3010; এটি কখনোই host port নয়। Traefik অতিরিক্ত configuration ছাড়াই WebSocket connection proxy করে, তাই আর কিছু যোগ করার প্রয়োজন নেই। আপনার stack-এর বাকি অংশ যদি ইতিমধ্যে single sign-on-এর জন্য Authentik-এর পেছনে থাকে, তাহলে এই router-এ একটি forward auth middleware ব্যবহার করে AFFiNE-তে browser access নিয়ন্ত্রণ করা যাবে। তবে desktop app পরীক্ষা না করা পর্যন্ত এটি সক্রিয় রাখবেন না। Desktop app কোনো browser session বহন করে না এবং তখন sync ব্যর্থ হবে। একই instance-এর পেছনে একাধিক app চালানোর বিষয়টি একাধিক app-এর সামনে একটি Traefik-এ ব্যাখ্যা করা হয়েছে।
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 log-এ কিছু দেখা যায় না, কারণ request-টি সেখানে পৌঁছায়ইনি। Caddy-এর জন্য একটি line যথেষ্ট, reverse_proxy http://127.0.0.1:3010। এটি নিজেই certificate এবং WebSocket upgrade পরিচালনা করে।
Self-hosted build-এ যা থাকে না
কোনো team স্থানান্তর করার আগে এই সীমাবদ্ধতাগুলো সম্পর্কে নিজের কাছে সৎ থাকুন।
Real-time collaboration আছে। Sizing-সংক্রান্ত পরামর্শের মূল বিষয়ও এটি, কারণ AFFiNE-এর নিজস্ব documentation অনুযায়ী memory ব্যবহার sync system এবং document merging-এর জন্য হয়। Offline editing-এর কারণেই অনেকে local-first tool চান। Desktop application আপনার self-hosted server-কে workspace list-এ যোগ করতে পারে এবং সেটির বিরুদ্ধে login করতে পারে। সিদ্ধান্ত নেওয়ার আগে আপনার team যে নির্দিষ্ট offline behaviour-এর ওপর নির্ভর করে, তা পরীক্ষা করুন: network বন্ধ রেখে desktop app-এ edit করুন, পুনরায় connect করুন, তারপর দ্বিতীয় device-এ ফলাফল পরীক্ষা করুন। Feature list প্রমাণ নয়। এই তালিকাটিও তার ব্যতিক্রম নয়।
Shipped compose file-এ server-side full-text search বন্ধ থাকে। সেখানে AFFINE_INDEXER_ENABLED=false server এবং migration job—উভয় ক্ষেত্রেই সেট করা আছে। এটি চালু করতে একটি Manticore Search container যোগ করতে হবে। এতে পঞ্চম service যোগ হবে এবং memory ব্যবহার বাড়বে। 2 GB-এর server-এ এই পরিবর্তনই সীমা অতিক্রম করাতে পারে। Client-এর ভেতরের search আপনি যে workspace খুলেছেন, সেখানে এখনও কাজ করে।
মানুষকে invite করার আগে দুটি সীমা জানা দরকার। একটি self-hosted workspace-এ সর্বোচ্চ 10টি seat দেওয়া হয়। এর বেশি হলে AFFiNE-এর Team license প্রয়োজন। Self-hosted instance-এর জন্য unlimited blob storage এবং unlimited blob size documentation-এ intended but not yet fully implemented হিসেবে বর্ণিত হয়েছে; এটি August 2026-এ যাচাই করা হয়েছিল। Household বা ছোট team-এর ক্ষেত্রে এই দুটির কোনোটিই গুরুত্বপূর্ণ নয়। তবে আপনি যদি forty জনকে স্থানান্তর করার পরিকল্পনা করে থাকেন, তাহলে দুটিই গুরুত্বপূর্ণ।
আপগ্রেড
প্রথমে release notes পড়ুন, বিশেষ করে 0.26 থেকে 0.27-এর মতো minor version bump-এর ক্ষেত্রে, যেখানে breaking change থাকতে পারে। কোনো পরিবর্তন করার আগে database এবং storage directory-এর backup নিন, কারণ পরবর্তী start-এর সময় migration job schema পরিবর্তন করে এবং এই পরিবর্তন পূর্বাবস্থায় ফেরানোর কোনো ব্যবস্থা নেই। এরপর pinned digest পরিবর্তন করুন, docker compose pull চালান এবং তার পরে docker compose up -d চালান। পরিষ্কারভাবে শেষ না হওয়া পর্যন্ত docker compose logs -f affine_migration monitor করুন। এরপর docker image prune পুরোনো layer-গুলো সরিয়ে দেয়। পুরোনো install ব্যবহারকারীদের জন্য একটি ঐতিহাসিক তথ্য: version 0.23.0 থেকে image name affine-graphql থেকে affine-এ পরিবর্তিত হয়েছে। তাই এর চেয়ে পুরোনো compose file-এ pull সফল হওয়ার আগে image line-গুলো নতুন করে লিখতে হবে।
FAQ
AFFiNE container কেন কখনো চালু হয় না?
affine service, affine_migration job-এর ওপর condition: service_completed_successfully নির্ধারণ করে। তাই migration 0 ছাড়া অন্য কোনো status নিয়ে শেষ হলে server আর চালু হয় না এবং কোনো web interface-ও দেখা যায় না। কোন ধাপে কাজ থেমেছে তা দেখতে docker compose logs affine_migration চালান। হাতে সম্পাদনা করা compose file-এ সাধারণত সমস্যার কারণ হয় pgvector/pgvector:pg16-এর পরিবর্তে stock postgres image ব্যবহার করা। কারণ AFFiNE schema-তে pgvector extension নির্ধারিত আছে এবং vector(1024) column-সহ table তৈরি করা হয়, যা সাধারণ Postgres তৈরি করতে পারে না।
self-hosted AFFiNE-এর কত RAM প্রয়োজন?
AFFiNE-এর requirements page-এ অন্তত 4 CPU core এবং 2 GB RAM চাওয়া হয়েছে। Document 10,000 শব্দ ছাড়ালে প্রয়োজন 4 GB পর্যন্ত বাড়ে। সেখানে আরও বলা হয়েছে, 10,000 modification-সহ একটি document merge করার সময় RAM ব্যবহার 1 GB-এ উঠতে পারে। 2 GB server-এ সমস্যাটি idle load নয়, বরং এই peak usage। Kernel-এর out-of-memory killer AFFiNE process বন্ধ করে দেয় এবং restart: unless-stopped সেটি আবার চালু করে। ফলে ব্যবহারকারীরা error-এর বদলে page reload দেখতে পান। docker inspect affine_server --format '{{.State.OOMKilled}}' এবং sudo dmesg -T | grep -i 'out of memory' দিয়ে এটি নিশ্চিত করুন। এরপর 2 GB swap file যোগ করুন, যাতে সাময়িক বৃদ্ধি ধীরগতির হয়, সরাসরি fatal না হয়।
AFFiNE আমার data কোথায় সংরক্ষণ করে, এবং কোন data backup করব?
আপনার compose directory-এর অধীনে তিনটি path-এ সব data থাকে: database-এর জন্য ./data/postgres, uploaded file-এর জন্য ./data/storage এবং config.json-এর জন্য ./config। File copy না করে docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump দিয়ে database backup নিন, কারণ চলমান Postgres নিরাপদে copy করা যায় না। Upload-এর জন্য ./data/storage tar করুন। config.json হাতে আলাদাভাবে সংরক্ষণ করুন, কারণ August 2026 পর্যন্ত admin panel থেকে configuration export সুবিধাটি এখনও implement করা হয়নি বলে উল্লেখ আছে।
self-hosted AFFiNE-এ real-time collaboration কি কাজ করে?
হ্যাঁ, এবং এর জন্য কিছু enable করতে হয় না। একমাত্র প্রয়োজন আপনার reverse proxy, কারণ sync WebSocket connection-এর মাধ্যমে চলে। nginx-এ এর অর্থ হলো proxy_http_version 1.1 এবং Upgrade ও Connection: upgrade header ব্যবহার করা। Traefik এবং Caddy অতিরিক্ত configuration ছাড়াই এই connection pass through করে। Proxy WebSocket connection upgrade না করলে সাধারণত দেখা যায়, workspace স্বাভাবিকভাবে load হয় এবং login করা যায়, কিন্তু একটি browser-এ করা edit অন্য browser-এ দেখা যায় না।
stock Postgres image দিয়ে কি AFFiNE চালানো যায়?
না। AFFiNE-এর schema.prisma-এ extensions = [pgvector(map: "vector")] নির্ধারিত আছে এবং vector(1024) type-এর embedding column-সহ চারটি table সংজ্ঞায়িত করা হয়েছে। AI feature বন্ধ থাকলেও migration job এই table-গুলো তৈরি করে। pgvector/pgvector:pg16 ব্যবহার করুন। এটি সেই extension-সহ compile করা Postgres 16। এর পরিবর্তে AFFiNE-কে কোনো external Postgres server-এ সংযুক্ত করলে, সেই server-এ pgvector install করুন এবং migration চালানোর আগে target database-এ extension তৈরি করুন।