SSD Nodes Learn 🎉 VPS $4.99/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

Docker Compose দিয়ে AFFiNE self-host করার গাইড

একটি VPS-এ Docker Compose দিয়ে AFFiNE চালান: 4টি container, pinned image tag, data কোথায় থাকে, backup এবং 2 GB RAM-এ বাস্তবে কী সম্ভব।

self-host করে AFFiNE ব্যবহার করলে যা পাবেন

Self-hosting AFFiNE আপনাকে আপনার নিয়ন্ত্রণাধীন সার্ভারে একটি Notion-ধাঁচের workspace দেয়। এটি 4টি container হিসেবে চলে: application, একবার চলা migration job, Postgres এবং Redis। Real-time collaboration অন্তর্ভুক্ত আছে। Self-hosted workspace ডিফল্টভাবে সর্বোচ্চ 10টি seat পায়। ইনস্টলেশনের জন্য একটি 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-এ listen করে।

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। মনে রাখার মতো একটি figure সেখানে দেওয়া হয়েছে: 10,000টি modification-সহ একটি document merge করার সময় memory ব্যবহার সর্বোচ্চ 1 GB পর্যন্ত উঠতে পারে।

এবার 2 GB plan-এ দুইজন একসঙ্গে লিখলে কী হয়, তা বিবেচনা করুন। গড় ব্যবহারে সমস্যা হয় না। Postgres এবং Node process limit-এর নিচে থাকে এবং কিছু memory অব্যবহৃত থাকে। সমস্যা হয় peak usage-এ। একটি বড় merge-এর জন্য ইতিমধ্যে ব্যবহৃত memory-এর অতিরিক্ত 1 GB প্রয়োজন হতে পারে। 2 GB box-এ swap না থাকলে kernel-এর out-of-memory (OOM) killer এই request সামলাতে সবচেয়ে বড় process-টিকে বন্ধ করে দেয়। সেটি হলো 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-এ node-এর নাম-সহ Killed process line থাকলে বুঝবেন 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 total দেখানোর কথা। Swap AFFiNE-কে দ্রুত করে না এবং এর উদ্দেশ্যও তা নয়। এটি এক সেকেন্ডের spike-কে container বন্ধ না করে ধীরগতির একটি second-এ পরিণত করে। সমাধানের অন্য দিক হলো Postgres যেন merge-এর সময় application-এর প্রয়োজনীয় space-এর মধ্যে নিজের cache বাড়াতে না পারে তা নিশ্চিত করা। এ কাজের জন্য Compose service-এ memory limit ব্যবহার করা হয়।

Storage-এর হিসাব করা অনেক সহজ। একই page-এ AFFiNE যে figures প্রকাশ করেছে, সেগুলো হলো:

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
  }
]

Server install-এর জন্য 1.5 GB লাগে। প্রায় 1,000 শব্দের 1,000টি document যোগ করলে Postgres data-তে 0.1 GB যোগ হয়, যা প্রায় কিছুই নয়। 1,000টি uploaded file যোগ করলে 10 GB যোগ হয়। মূল বিষয়টি এটাই। এগুলো running instance থেকে নেওয়া measurement নয়; প্রকাশিত planning figure। তাই এগুলোকে নির্দিষ্ট নিশ্চয়তা নয়, বরং ব্যবহারের ধরন বোঝানোর নির্দেশনা হিসেবে দেখুন। গুরুত্বপূর্ণ বিষয় হলো: আপনার database ছোট থাকবে, আর disk usage নির্ধারণ করবে আপনার uploaded file।

নিজে compose file লিখুন এবং image tag নির্দিষ্ট করে দিন

ডকুমেন্টে দেওয়া install পদ্ধতিতে curl -L -o docker-compose.yml https://github.com/toeverything/AFFiNE/releases/latest/download/docker-compose.yml ব্যবহার করে একটি প্রস্তুত file download করা হয়। এটি কাজ করে। তবে এর ওপর নির্ভর করার আগে একটি বিষয় জানা দরকার: 8 August 2026 পর্যন্ত release 0.27.3-এর সঙ্গে সংযুক্ত file-টি এখনও .env file থেকে path পড়ে এবং ${UPLOAD_LOCATION}, ${CONFIG_LOCATION}${DB_DATA_LOCATION} ব্যবহার করে। অন্যদিকে, documentation-এর reference page-এ আরও নতুন layout দেখানো হয়েছে, যেখানে সবকিছু ./data-এর অধীনে রাখা হয় এবং কোনো .env প্রয়োজন হয় না। দুটিই সঠিক। নিজে file লিখলে এই অনিশ্চয়তা থাকে না। Image tag নির্দিষ্ট করা এবং database password সেট করার জন্য file-টি যেভাবেই হোক সম্পাদনা করতে হবে।

mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .env

Compose project directory থেকে নিজে .env পড়ে এবং আপনার জন্য ${DB_PASSWORD} প্রতিস্থাপন করে। ফলে support thread-এ paste করার file-এ 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-stopped

Upstream সরবরাহ করা file থেকে এখানে চারটি পার্থক্য আছে। প্রতিটির নির্দিষ্ট কারণ রয়েছে।

  • 127.0.0.1:3010:3010 port-টি শুধু loopback address-এ publish করে। তাই আপনি পদ্ধতি নির্ধারণ না করা পর্যন্ত server-এর বাইরের কোনো host AFFiNE-এ পৌঁছাতে পারবে না। Upstream-এর '3010:3010' সব interface-এ bind করে। অধিকাংশ VPS image-এ এর মধ্যে public interface-ও থাকে।
  • POSTGRES_HOST_AUTH_METHOD: trust বাদ দেওয়া হয়েছে এবং তার পরিবর্তে password সেট করা হয়েছে। Trust authentication ব্যবহার করলে password ছাড়াই ওই database-এ যেকোনো connection affine user হিসেবে গ্রহণ করা হয়। এটি 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:pg16 upstream যেভাবে সেট করেছে, ঠিক সেভাবেই রাখা হয়েছে। এর কারণ উপরে দেওয়া হয়েছে।

Postgres প্রথমবার data directory তৈরি করার সময়ই কেবল POSTGRES_PASSWORD পড়ে। ইতিমধ্যে তৈরি instance-এর ক্ষেত্রে docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" ব্যবহার করে password সেট করুন। এরপর মিলিয়ে DATABASE_URL আপডেট করুন।

Configuration config/config.json-এ থাকে

AFFiNE তার settings config/config.json থেকে পড়ে। এটি সেই directory, যেটি আপনি /root/.affine/config-এ mount করেছেন। এই file স্বয়ংক্রিয়ভাবে তৈরি হয় না। তাই প্রথমবার start করার আগে এটি লিখুন। কোনো editor-এ ~/affine/config/config.json খুলে নিচের content দিন। উদাহরণে দেওয়া domain-এর জায়গায় নিজের 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-এ বাস্তবে খোলেন। AFFiNE এই value থেকে share link এবং workspace invitation তৈরি করে। তাই এটি http://localhost:3010 রাখা হলে, আপনার পাঠানো invitation প্রাপকের নিজের 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 ps

docker compose ps-এ 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 নির্ধারণ করে। এখানে দুটিই গুরুত্বপূর্ণ: stable, যা প্রতিটি release-এ নতুন করে নির্দেশ করা হয়, এবং git short hash-সহ stable-, যা পরিবর্তন করা হয় না। stable-এ রেখে দিলে, ছয় মাস পর একটি docker compose pull ভিন্ন image ডাউনলোড করবে এবং আপনার বেছে না নেওয়া সময়ে সেই 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 একটি পরিকল্পিত edit হবে, আকস্মিক পরিবর্তন নয়: digest পরিবর্তন করুন, backup নিন, docker compose pull, docker compose up -d

অন্য কেউ করার আগে admin account তৈরি করুন

নতুন instance-এ /admin খুললে AFFiNE আপনাকে account তৈরির পৃষ্ঠায় পাঠাবে, কারণ সার্ভারে তখনও কোনো administrator নেই। এই প্রক্রিয়ায় কোনো invitation code বা setup token নেই। যে ব্যক্তি প্রথম ওই পৃষ্ঠা খুলবে, সে-ই আপনার সার্ভারের 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

এটি চালু অবস্থায় রাখুন এবং local browser-এ http://127.0.0.1:3010/admin খুলুন। Register করে log in করুন, তারপর tunnel বন্ধ করুন। এখনই instance-টিকে public name-এ দেওয়া নিরাপদ।

AFFiNE আপনার ডেটা যেখানে রাখে

তিনটি path-এ সবকিছু সংরক্ষিত থাকে। এগুলো আপনার তৈরি করা directory-এর ভেতরেই থাকে।

  • ./data/postgres হলো Postgres-এর data directory। এখানে documents, users, workspaces এবং permissions থাকে।
  • ./data/storage container-এর /root/.affine/storage-এ mount করা থাকে। এখানে upload করা সব file থাকে।
  • ./config /root/.affine/config-এ mount করা থাকে। এখানে config.json থাকে।

Upstream এখানে named volume-এর পরিবর্তে bind mount ব্যবহার করে। এই সিদ্ধান্তটি ইচ্ছাকৃত। Docker-কে path জিজ্ঞাসা না করেই সাধারণ command ব্যবহার করে এই path-গুলো tar করে copy করতে পারবেন। এর বিনিময়ে host-এর file ownership আপনাকেই পরিচালনা করতে হবে। এই trade-off-টি bind mount এবং named volume অংশে ব্যাখ্যা করা হয়েছে।

AFFiNE-এর ব্যাকআপ কীভাবে নেবেন

ব্যাকআপ নেওয়ার জন্য দুটি জিনিস আছে, এবং দুটির ব্যাকআপ আলাদাভাবে নিতে হয়। Database একটি চালু server, তাই এটি চলার সময় এর file কপি করলে কপিটি 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 ব্যবহার করে চলে, তাই এটি password চায় না। ওই ls output-এ size পরীক্ষা করুন। কয়েকশো bytes-এর file হলে বুঝবেন dump ব্যর্থ হয়েছে, কিন্তু shell তবুও file তৈরি করেছে। ছয় মাস পরে অনেকে এই ব্যর্থতা আবিষ্কার করেন। -T-ও গুরুত্বপূর্ণ: এটি না থাকলে Compose একটি terminal allocate করতে পারে এবং 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

config.json backup-এ নিজে রাখুন। August 2026-এ যাচাই করা AFFiNE documentation-এ এখনও বলা আছে যে admin panel থেকে configuration export বাস্তবায়িত হয়নি। তাই disk-এ থাকা file-টিই আপনার settings-এর একমাত্র copy। তিনটি file-ই server-এর বাইরে কপি করুন। যে জিনিস সুরক্ষিত রাখার জন্য backup নেওয়া হয়েছে, সেই জিনিসের একই disk-এ থাকা copy backup নয়।

পুনরুদ্ধার এবং প্রকাশিত ধাপের একটি সমস্যা

প্রয়োজন হওয়ার আগেই অফিসিয়াল restore ধাপগুলো পড়ুন এবং মনোযোগ দিয়ে পড়ুন। August 2026-এ প্রকাশিত ধাপগুলোতে affine.backup নামের একটি ফাইল container-এ কপি করা হয়, এরপর ./pg.backup থেকে restore করা হয়। কিন্তু এগুলো দুটি ভিন্ন নাম। সেখানে ./postgres directory সরিয়ে ফেলার নির্দেশও আছে, অথচ বর্তমান compose file-এ data ./data/postgres-এ রাখা হয়। snippet-এ থাকা path অনুসরণ না করে এই guide-এ আপনি যে path ব্যবহার করেছেন সেগুলো অনুসরণ করুন। এই 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 -d

rm-এর বদলে mv ব্যবহারের বিষয়টি লক্ষ্য করুন। যে database-এর copy সংরক্ষণ করেননি, সেটির ওপর restore করলে একটি ভুল command সম্পূর্ণ data loss ঘটাতে পারে। পুরোনো directory-টি সরিয়ে অন্য নামে রাখলে কোনো খরচ হয় না। tar xzf backup/storage-2026-08-08.tgz -C data দিয়ে uploads-ও restore করুন। তা না হলে প্রতিটি document-এর attachment ভাঙা অবস্থায় প্রদর্শিত হবে। এরপর লগ ইন করে image-যুক্ত একটি document খুলুন। এটিই পরীক্ষা। browser-এ খোলেননি এমন restore backup নয়; সেটি কেবল একটি file।

আপনি আগে থেকেই চালান এমন proxy-এর পেছনে AFFiNE স্থাপন

AFFiNE WebSocket ব্যবহার করে, এবং এটি ঐচ্ছিক নয়। ডকুমেন্টেশনে বিষয়টি স্পষ্টভাবে বলা আছে: 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 পাঠাচ্ছে না।

আপনি যদি অন্য 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: true

Certificate resolver-এর নাম আপনার Traefik configuration-এ সংজ্ঞায়িত নামের সঙ্গে মিলতে হবে। loadbalancer.server.port হলো container port 3010; এটি কখনো host port নয়। Traefik অতিরিক্ত configuration ছাড়াই WebSocket connection proxy করে, তাই আর কিছু যোগ করার দরকার নেই। একই instance-এর পেছনে একাধিক app চালানোর বিষয়টি একটি Traefik-এর সামনে একাধিক app-এ দেখানো হয়েছে।

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 আচরণের ওপর নির্ভর করে, তা পরীক্ষা করুন। 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 খুলে রেখেছেন, সেখানে এখনও কাজ করে।

লোকজনকে আমন্ত্রণ জানানোর আগে দুটি সীমা জানা দরকার। একটি self-hosted workspace-এ সর্বোচ্চ 10টি seat দেওয়া যায়। এর বেশি হলে AFFiNE-এর Team license প্রয়োজন। Self-hosted instance-এর জন্য unlimited blob storage এবং unlimited blob size documentation-এ intended, কিন্তু এখনও পুরোপুরি implemented নয় বলে উল্লেখ করা হয়েছে; এটি August 2026-এ যাচাই করা হয়েছে। Household বা ছোট team-এর ক্ষেত্রে এই সীমাগুলোর কোনোটিই গুরুত্বপূর্ণ নয়। তবে চল্লিশজনকে স্থানান্তর করার পরিকল্পনা থাকলে দুটিই গুরুত্বপূর্ণ।

আপগ্রেড

প্রথমে 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 খুঁজে পাওয়ার আগে 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-এ সাধারণত postgres-এর বদলে stock pgvector/pgvector:pg16 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 নয়, এই সর্বোচ্চ ব্যবহার। 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 যোগ করুন, যাতে সাময়িক memory spike ধীরগতির হয়, সম্পূর্ণ ব্যর্থতা না ঘটায়।

AFFiNE আমার data কোথায় রাখে এবং কোন data backup করব?

আপনার compose directory-এর অধীনে 3টি 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 নিরাপদে file copy করা যায় না। Upload-এর জন্য ./data/storage tar করুন। config.json-এর একটি copy হাতে রাখুন। কারণ August 2026 পর্যন্ত admin panel থেকে configuration export সুবিধাটি এখনো বাস্তবায়িত হয়নি বলে তালিকাভুক্ত আছে।

self-hosted AFFiNE-এ real-time collaboration কি কাজ করে?

হ্যাঁ, এবং এর জন্য কিছু enable করতে হয় না। একমাত্র প্রয়োজন reverse proxy সঠিকভাবে configure করা। কারণ sync WebSocket connection-এর মাধ্যমে চলে। nginx-এ এর অর্থ proxy_http_version 1.1 এবং UpgradeConnection: 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-সহ 4টি table সংজ্ঞায়িত করা হয়েছে। AI feature বন্ধ থাকলেও migration job এই table-গুলো তৈরি করে। pgvector/pgvector:pg16 ব্যবহার করুন। এটি সেই extension-সহ compiled Postgres 16। AFFiNE-কে external Postgres server-এ point করলে সেখানে pgvector install করুন এবং migration চালানোর আগে target database-এ extension তৈরি করুন।