אירוח עצמי של AFFiNE: מדריך התקנה עם Docker Compose
למדו איך להריץ AFFiNE על שרת VPS באמצעות Docker Compose. המדריך מפרט את ארבע המכולות הנדרשות, תגיות image קבועות, ניהול גיבויים והאם 2GB RAM מספיקים להרצה יציבה של המערכת.
מה מקבלים באירוח עצמי של AFFiNE
אירוח עצמי של AFFiNE מעניק לכם סביבת עבודה בסגנון Notion על שרת שבשליטתכם, הפועלת כארבע מכולות: היישום, משימת העברת נתונים חד-פעמית (migration), Postgres ו־Redis. שיתוף פעולה בזמן אמת כלול, עד ל-10 משתמשים שסביבת עבודה באירוח עצמי מקבלת כברירת מחדל. ההתקנה מבוססת על קובץ compose אחד וקובץ תצורה בפורמט JSON. הנושאים הדורשים מחשבה הם תגיות ה-image, מבנה הדיסק, מגבלת הזיכרון וה-proxy שתציבו בחזית.
AFFiNE משלב עורך מסמכים ולוח ציור אינסופי באותה סביבת עבודה, כך שניתן לקרוא דף כמסמך או לפרוס אותו כלוח מחיק. אם אתם עדיין מתלבטים מה להריץ, קראו תחילה את ה-השוואה בין חלופות Notion לאירוח עצמי. מדריך זה מניח שההחלטה התקבלה, והוא מתמקד בהרצה תקינה של AFFiNE ולא בהשוואה נוספת.
כל המידע כאן נבדק מול תיעוד האירוח העצמי של AFFiNE וקובצי ה-release שפורסמו ב-8 באוגוסט 2026. ה-release היציב האחרון באותו תאריך היה 0.27.3, שפורסם ב-23 ביולי 2026.
מה עושות ארבע המכולות בפועל
affine הוא השרת והלקוח מבוסס הדפדפן בתמונה (image) אחת. הוא מאזין בפורט 3010.
affine_migration הוא משימה חד-פעמית המריצה את node ./scripts/self-host-predeploy.js, מחילה את עדכוני מסד הנתונים (migrations) ומסתיימת. היישום מגדיר condition: service_completed_successfully על משימה זו, כך שעדכון שמסתיים בסטטוס שאינו אפס גורם לכך ש-affine לעולם לא יופעל. כאשר ממשק האינטרנט אינו עולה, הלוג של משימה זו הוא הדבר הראשון שיש לקרוא.
postgres מאחסן את המסמכים, המשתמשים, סביבות העבודה וההרשאות שלך. התמונה המסופקת היא pgvector/pgvector:pg16, שהיא Postgres 16 רגיל עם תוסף pgvector מהודר בתוכו. pgvector מוסיף סוג עמודה vector ל-Postgres, הצורה הנומרית המשמשת לאחסון embeddings כדי שניתן יהיה לחפש טקסט לפי משמעות.
redis הוא תלות הכרחית: גם השרת וגם משימת העדכון ממתינים לבדיקת התקינות שלו לפני שהם מתחילים. שימו לב למה שקובץ ה-compose המסופק לא נותן ל-Redis, וזה volume. שום דבר בתוכו לא שורד docker compose down, וזה אומר לכם בבירור שהוא אינו מחזיק תוכן שלכם ואינו זקוק לגיבוי.
מדוע נעשה שימוש ב-image של pgvector ולא ב-postgres הרגיל
הדרישה נובעת מה-schema של AFFiNE, ולא מהעדפה אישית. ב-schema.prisma מקור הנתונים מצהיר על extensions = [pgvector(map: "vector")], וארבע טבלאות מכילות עמודת embedding מסוג vector(1024). משימת ה-migration יוצרת את הטבלאות הללו ללא קשר לשאלה אם הפעלת את תכונות ה-AI, לכן התוסף חייב להיות קיים במסד הנתונים לפני שה-migration תוכל להסתיים. אם תחליפו ל-postgres:16, התוסף לא יהיה קיים, ה-migration לא תוכל ליצור את העמודות הללו, והשרת יישאר במצב המתנה למשימה שנכשלה.
AFFiNE עברה ל-image של pgvector בגרסה 0.21. בהתקנה ישנה יותר, עריכת שורת ה-image אינה מספיקה לביצוע השדרוג, לכן קראו את דף השדרוג ב-תיעוד ההתקנה העצמית של AFFiNE לפני שתבצעו pull לכל דבר.
דבר נוסף בנוגע לתגית הזו. pg16 מציין את Postgres 16, וגרסה ראשית (major version) של Postgres היא לא מספר שניתן להעלות כרצונו. אם תשנו ל-pg17 על גבי ספריית נתונים קיימת, Postgres יסרב לעלות, עם שורה כמו The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17 בתוך docker compose logs postgres. מעבר גרסה ראשית מחייב ביצוע dump ושחזור (restore) לתוך ספריית נתונים חדשה.
כמה מעבד וזיכרון דורשת התקנה עצמית של AFFiNE
דף הדרישות של AFFiNE מציין צורך במינימום 4 ליבות מעבד ו-2 GB זיכרון RAM, וממליץ להעלות את הזיכרון ל-4 GB ברגע שהמסמכים שלכם עוברים את רף 10,000 המילים. אותו דף מסביר לאן הזיכרון הולך: למערכת הסנכרון ולמיזוג מסמכים. הוא מספק נתון אחד שחשוב לזכור: מיזוג מסמך עם 10,000 שינויים יכול להגיע לשיא של 1 GB.
כעת, השוו זאת לתוכנית של 2 GB עם שני משתמשים שכותבים. הממוצע תקין. Postgres ותהליך ה-Node נשארים מתחת למגבלה עם מקום פנוי. הבעיה היא בשיאי העומס. מיזוג גדול אחד יכול לדרוש 1 GB נוסף מעבר למה שכבר נמצא בזיכרון, ובשרת עם 2 GB ללא swap, מנגנון ה-out-of-memory (OOM) killer של ה-kernel יגיב לבקשה הזו על ידי הריגת התהליך הגדול ביותר, שהוא שרת ה-AFFiNE.
הקולגה שלכם לא יראה שגיאה. הוא יראה את הדף נטען מחדש, מכיוון ש-restart: unless-stopped מחזיר את ה-container לפעולה תוך שניות. אל תנחשו, אלא ודאו זאת:
docker inspect affine_server --format '{{.State.OOMKilled}} {{.RestartCount}}'
sudo dmesg -T | grep -i -E 'out of memory|killed process'true מהפקודה הראשונה, או שורת Killed process המציינת את node מהשנייה, מעידים על כך שנגמר הזיכרון ולא שמדובר בבאג. פתרו זאת משני הכיוונים. הוסיפו תחילה swap, כך שקפיצה בעומס תהפוך לאיטית במקום לקטלנית:
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. ה-swap לא הופך את AFFiNE למהיר, וזו לא מטרתו. הוא הופך קפיצה של שנייה אחת לשנייה איטית במקום ל-container שקרס. הצד השני של הפתרון הוא למנוע מ-Postgres להגדיל את ה-cache שלו לתוך המרחב שהיישום צריך בזמן המיזוג, וזה בדיוק מה ש-מגבלות זיכרון בשירות Compose נועדו עבורו.
קל הרבה יותר לחזות את צורכי האחסון. אלו הנתונים ש-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
}
]התקנת השרת תופסת 1.5 GB. אלף מסמכים של כאלף מילים כל אחד מוסיפים 0.1 GB של נתוני Postgres, שזה כמעט כלום. אלף קבצים שהועלו מוסיפים 10 GB, וזה עיקר הסיפור. אלו נתוני תכנון שפורסמו ולא מדידות מתוך מופע פעיל, לכן התייחסו אליהם כאל הערכה כללית ולא כהבטחה. הצורה היא מה שחשוב: מסד הנתונים שלכם נשאר קטן, והקבצים שאתם מעלים הם אלו שקובעים את נפח הדיסק.
כתבו את קובץ ה-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 כלל. שתי הדרכים תקינות. כתיבת הקובץ בעצמכם מיישבת את הסוגיה, ובכל מקרה תצטרכו לערוך אותו כדי לנעול את גרסאות ה-images ולהגדיר סיסמה למסד הנתונים.
mkdir -p ~/affine/config ~/affine/data
cd ~/affine
printf 'DB_PASSWORD=%s\n' "$(openssl rand -hex 24)" > .env
chmod 600 .envCompose קורא את .env מספריית הפרויקט באופן אוטומטי ומציב את ${DB_PASSWORD} עבורכם, כך שהסיסמה לעולם לא מופיעה בקובץ שתדביקו בשרשור תמיכה. הרגל זה כדאי לשמר בכל stack שאתם מריצים, והנימוק לכך נמצא ב-שמירה על סודות מחוץ לקובץ ה-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מפרסם את הפורט בכתובת ה-loopback בלבד, כך ששום גורם מחוץ לשרת לא יכול להגיע ל-AFFiNE עד שתחליטו כיצד. ה-'3010:3010'המקורי נקשר לכל הממשקים, וברוב ה-VPS images זה כולל את הממשק הציבורי.POSTGRES_HOST_AUTH_METHOD: trustהוסר ובמקומו הוגדרה סיסמה. אימות מסוג trust מקבל כל חיבור למסד הנתונים כמשתמשaffineללא סיסמה. הוא מוגבל לרשת ה-Compose הפנימית, מה שמתאים עד היום שבו תחברו מכולה נוספת לאותה רשת או תפרסמו את 5432 בזמן ניפוי שגיאות (debugging).redis:8-alpineמחליף אתredisהכללי, שמתורגם ל-latest. נכון לאוגוסט 2026 מדובר ב-Redis 8, לכן הנעילה שומרת על ה-major version שבדקתם ומונעת מ-Redis 9 עתידי להגיע במהלךdocker compose pullלא קשור.pgvector/pgvector:pg16נשאר בדיוק כפי שהוגדר במקור, מהסיבה שצוינה לעיל.
POSTGRES_PASSWORD נקרא רק כאשר Postgres יוצר את ספריית הנתונים שלו בפעם הראשונה. במופע (instance) שכבר קיים, הגדירו את הסיסמה באמצעות docker compose exec postgres psql -U affine -c "ALTER USER affine WITH PASSWORD 'yourpassword'" ולאחר מכן עדכנו את DATABASE_URL בהתאם.
הגדרות נמצאות ב-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 הוא המתג לשימוש במפתח אישי (BYOK), המאפשר לבעל מרחב עבודה להזין את מפתח ספק המודל שלו בהגדרות המרחב. אירוח עצמי של AFFiNE אינו כולל מנוי ל-AI. השאירו את שניהם false אם אינכם מעוניינים בכך.
הפעילו את ה-stack:
docker compose up -d
docker compose psdocker compose ps אמור להציג את affine_postgres ו-affine_redis כ-healthy, את affine_server כ-running, ואת affine_migration_job במצב exited (0). כל קוד יציאה אחר במשימת ה-migration הוא הבעיה שיש לחקור, והלוג שלה יציין את השלב שבו התהליך נעצר:
docker compose logs affine_migrationקבעו את גרסת ה-image לפני שתשכחו
stable הוא תגית דינמית. תהליך ה-release של AFFiNE מצביע על כמה תגיות בכל build יציב, ושתיים מהן רלוונטיות כאן: stable, שמתעדכנת בכל release, ו-stable- שאחריה מופיע ה-git short hash, שאינו משתנה. אם תישאר עם stable, פקודת docker compose pull בעוד שישה חודשים תמשוך image שונה ותריץ את ה-migrations שלו מול מסד הנתונים שלכם ברגע שלא בחרתם בו. קבעו (pin) את ה-image המדויק שבדקתם:
docker compose pull
docker image inspect ghcr.io/toeverything/affine:stable --format '{{index .RepoDigests 0}}'הפקודה תדפיס שורה בסגנון ghcr.io/toeverything/affine@sha256: ואחריה hash ארוך. הדביקו את המחרוזת המלאה בשורת ה-image: בתוך שני הקבצים: affine ו-affine_migration. שני הקבצים הללו חייבים להיות זהים תמיד, כיוון שמדובר באותו image הממלא שני תפקידים; חוסר התאמה ביניהם משמעו הרצת migrations למסד הנתונים לפי סכימה אחת, בעוד השירות פועל לפי סכימה אחרת. שדרוג גרסה יהיה מעתה פעולה מכוונת ולא הפתעה: שנו את ה-digest, בצעו גיבוי, הריצו docker compose pull, ולאחר מכן docker compose up -d.
יצירת חשבון מנהל המערכת לפני שמישהו אחר יעשה זאת
פתחו את /admin במופע חדש, ו-AFFiNE תפנה אתכם לדף יצירת חשבון, כיוון שעדיין אין מנהל מערכת לשרת. בתהליך זה אין קוד הזמנה או אסימון הגדרה (setup token). האדם הראשון שיטען את הדף הזה יהפוך למנהל המערכת של השרת שלכם, לכן הפורט חייב להישאר סגור עד שתירשמו.
זו הסיבה שקובץ ה-compose לעיל מאזין ל-127.0.0.1. גשו אליו דרך מנהרת SSH מהמחשב שלכם:
ssh -L 3010:127.0.0.1:3010 you@your-server-ipהשאירו את התהליך רץ ופתחו את http://127.0.0.1:3010/admin בדפדפן המקומי שלכם. הירשמו והתחברו, ולאחר מכן סגרו את המנהרה. רק כעת בטוח לחשוף את המופע לשם מתחם ציבורי.
היכן AFFiNE שומר את הנתונים שלך
שלושה נתיבים מכילים את כל המידע, וכולם נמצאים בתוך התיקייה שיצרת.
./data/postgresהוא ספריית הנתונים של Postgres: מסמכים, משתמשים, סביבות עבודה והרשאות../data/storageממופה ל-/root/.affine/storageבתוך המכולה ומכיל את כל הקבצים שהועלו../configממופה ל-/root/.affine/configומכיל אתconfig.json.
הפרויקט משתמש כאן ב-bind mounts במקום ב-named volumes, וזו בחירה מכוונת: ניתן לדחוס (tar) ולהעתיק את הנתיבים הללו באמצעות פקודות רגילות, מבלי לשאול את Docker היכן הם מאוחסנים. המחיר הוא שהבעלות על הקבצים במערכת המארחת היא באחריותך, וזהו הוויתור שנדון ב-bind mounts and named volumes.
כיצד לגבות את AFFiNE
יש לגבות שני רכיבים, ושיטת הגיבוי שלהם שונה. מסד הנתונים הוא שרת פעיל, לכן העתקת הקבצים שלו בזמן ריצה תיצור עותק פגום. במקום זאת, בצעו 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 מעל ה-socket המקומי שלו, לכן הוא לא יבקש סיסמה. בדקו את הגודל בפלט של ls. קובץ בגודל של כמה מאות בתים מעיד על כך שה-dump נכשל בעוד ה-shell יצר את הקובץ בכל זאת; זהו כשל שמתגלה רק כעבור שישה חודשים. ה--T חשוב גם הוא: בלעדיו, Compose עלול להקצות מסוף (terminal) ולשבש את זרם הנתונים הבינארי.
קבצים שהועלו הם קבצים רגילים, לכן ארכבו אותם באמצעות tar:
tar czf backup/storage-$(date +%F).tgz -C data storage
cp config/config.json backup/config-$(date +%F).jsonשמרו את config.json בגיבוי שלכם באופן ידני. נכון לאוגוסט 2026, תיעוד AFFiNE מציין כי ייצוא הגדרות מלוח הבקרה של המנהל טרם מומש, לכן הקובץ שעל הדיסק הוא העותק היחיד של ההגדרות שלכם. העתיקו את כל שלושת הקבצים מחוץ לשרת. גיבוי שנמצא על אותו דיסק של המידע שהוא אמור להגן עליו אינו נחשב לגיבוי.
שחזור, ומלכודת אחת בצעדים שפורסמו
קראו את צעדי השחזור הרשמיים לפני שתזדקקו להם, וקראו אותם בעיון. כפי שפורסמו באוגוסט 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שימו לב ל-mv במקום ל-rm. שחזור על גבי מסד נתונים שלא שמרתם ממנו עותק הוא הדרך שבה פקודה שגויה אחת הופכת לאובדן נתונים מוחלט, והעברת הספרייה הישנה הצידה אינה עולה דבר. שחזרו גם את ההעלאות, בעזרת tar xzf backup/storage-2026-08-08.tgz -C data, אחרת כל מסמך יוצג עם קבצים מצורפים שבורים. לאחר מכן, התחברו ופתחו מסמך המכיל תמונה. זהו המבחן. שחזור שלא פתחתם בדפדפן הוא קובץ בלבד, לא גיבוי.
הצבת AFFiNE מאחורי proxy קיים
AFFiNE משתמש ב־WebSocket, וזהו רכיב הכרחי. התיעוד מבהיר זאת: WebSocket הוא הבסיס למערכת הסנכרון ושיתוף הפעולה של AFFiNE. לכן, proxy שאינו מבצע upgrade לחיבורים אלו יגרום לכך שהעריכה תפסיק להסתנכרן בשקט. הדף נטען, ההתחברות עובדת, אך עריכה שבוצעה בדפדפן אחד לעולם לא תגיע לאחר. בכלי המפתחים של הדפדפן, פתחו את לשונית Network וסננו לפי WS. חיבור שנפתח ונסגר שוב ושוב מעיד על proxy שאינו מעביר את ה־upgrade.
אם אתם כבר מריצים 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שם ה-certificate resolver חייב להתאים לזה שהוגדר בתצורת ה-Traefik שלכם, ו-loadbalancer.server.port הוא פורט המכולה 3010, לעולם לא פורט של ה-host. Traefik מבצע proxy לחיבורי WebSocket ללא צורך בתצורה נוספת, כך שאין מה להוסיף. אם שאר ה-stack שלכם כבר נמצא מאחורי Authentik עבור single sign-on, הוספת forward auth middleware ב-router הזה תחסום גישת דפדפן ל-AFFiNE. השאירו זאת כבוי עד שתבדקו את אפליקציית ה-desktop, שכן היא אינה מחזיקה session של דפדפן ופשוט תיכשל בסנכרון. הרצת כמה יישומים מאחורי מופע אחד של Traefik מוסברת ב-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;
}ברירת המחדל של client_max_body_size ב-nginx היא 1 MB, לכן ללא שורה זו כל העלאה הגדולה מתמונה קטנה תיכשל עם סטטוס 413, ושום דבר לא יופיע בלוגים של AFFiNE, כיוון שהבקשה מעולם לא הגיעה. Caddy זקוק לשורה אחת, reverse_proxy http://127.0.0.1:3010, והוא מטפל בתעודות וב-WebSocket upgrades בעצמו.
מה נותר מחוץ להתקנה העצמית
היו כנים עם עצמכם בנושא זה לפני שתעבירו צוות שלם.
שיתוף פעולה בזמן אמת קיים, וזהו הפיצ'ר שסביבו נסובות כל ההמלצות לגבי גודל השרת, שכן התיעוד של AFFiNE מייחס את צריכת הזיכרון למערכת הסנכרון ולמיזוג מסמכים. עריכה במצב לא מקוון היא הסיבה לכך שאנשים רבים מעוניינים בכלי המבוסס על גישת local-first, ואפליקציית ה-desktop מאפשרת להוסיף את השרת המותקן עצמית לרשימת ה-workspace ולהתחבר אליו. בדקו את התנהגות ה-offline המדויקת שהצוות שלכם מסתמך עליה לפני שתתחייבו: בצעו עריכה באפליקציית ה-desktop כשהרשת כבויה, התחברו מחדש, ולאחר מכן בדקו את התוצאה במכשיר שני. רשימות פיצ'רים אינן הוכחה, וזה כולל גם את הרשימה הזו.
חיפוש טקסט מלא בצד השרת מושבת בקובץ ה-compose שמגיע עם התוכנה, שבו AFFINE_INDEXER_ENABLED=false מוגדר בשרת ובעבודת ה-migration. הפעלתו משמעותה הוספת קונטיינר Manticore Search, שירות חמישי במספר שצורך זיכרון נוסף. בשרת עם 2 GB, זהו השינוי שדוחף את המערכת מעבר לקצה גבול היכולת. החיפוש בתוך ה-client עדיין עובד על ה-workspace הפתוח.
כדאי להכיר שתי מגבלות לפני שמזמינים משתמשים. ל-workspace מותקן עצמית מוקצים לכל היותר 10 מושבים, וחריגה מכך דורשת רישיון Team מ-AFFiNE. אחסון blob ללא הגבלה וגודל blob ללא הגבלה עבור מופעים מותקנים עצמית מתוארים בתיעוד ככאלה שנועדו להטמעה אך טרם מומשו במלואם, נכון לאוגוסט 2026. אף אחד מאלה לא משנה עבור משק בית או צוות קטן. שניהם משנים אם תכננתם להעביר ארבעים איש.
שדרוגים
קראו תחילה את הערות השחרור (release notes), במיוחד במקרה של עדכון גרסה משנית כגון 0.26 ל-0.27, שבו עלולים להופיע שינויים שוברי תאימות (breaking changes). בצעו גיבוי למסד הנתונים ולתיקיית האחסון לפני כל פעולה, כיוון שתהליך המיגרציה משנה את הסכימה בהפעלה הבאה ולא ניתן לבטל אותו. לאחר מכן, שנו את ה-digest המקובע, הריצו את docker compose pull ולאחריו את docker compose up -d, ועקבו אחר docker compose logs -f affine_migration עד לסיום תקין של התהליך. הפקודה docker image prune תנקה את השכבות הישנות לאחר מכן. הערה היסטורית למשתמשים בהתקנות ישנות מאוד: החל מגרסה 0.23.0 שם ה-image השתנה מ-affine-graphql ל-affine, לכן קובץ compose ישן יותר דורש עדכון של שורות ה-image לפני שפעולת pull תמצא תוצאות כלשהן.
FAQ
מדוע המכולה של AFFiNE לא עולה לעולם?
השירות affine מצהיר על condition: service_completed_successfully במשימת ה-affine_migration, לכן אם תהליך המיגרציה מסתיים בסטטוס שונה מ-0, השרת לא יופעל כלל ולא יוצג ממשק אינטרנט. הריצו את docker compose logs affine_migration כדי לראות באיזה שלב התהליך נעצר. הסיבה הנפוצה ביותר בקובץ compose שנערך ידנית היא שימוש בתמונת postgres סטנדרטית במקום ב-pgvector/pgvector:pg16, כיוון שהסכימה של AFFiNE מצהירה על תוסף pgvector ויוצרת טבלאות עם עמודות vector(1024) ש-Postgres רגיל אינו מסוגל ליצור.
כמה זיכרון RAM דרוש ל-AFFiNE באירוח עצמי?
דף הדרישות של AFFiNE מציין לפחות 4 ליבות CPU ו-2 GB של RAM, עם עלייה ל-4 GB כאשר מסמכים עוברים את רף 10,000 המילים, ומצוין כי מיזוג מסמך עם 10,000 שינויים יכול להגיע לשיא של 1 GB. בשרת עם 2 GB, השיא הזה הוא מה שגורם לקריסה, לא העומס במצב המתנה: מנגנון ה-out-of-memory killer של הליבה עוצר את תהליך ה-AFFiNE, ו-restart: unless-stopped מפעיל אותו מחדש, כך שהמשתמשים חווים רענון דף במקום שגיאה. אשרו זאת באמצעות docker inspect affine_server --format '{{.State.OOMKilled}}' ו-sudo dmesg -T | grep -i 'out of memory', ולאחר מכן הוסיפו קובץ swap בנפח 2 GB כדי שקפיצה בעומס תהיה איטית במקום קטלנית.
היכן AFFiNE שומר את הנתונים שלי, ומה עלי לגבות?
שלושה נתיבים תחת ספריית ה-compose שלכם מכילים את הכל: ./data/postgres עבור מסד הנתונים, ./data/storage עבור קבצים שהועלו, ו-./config עבור config.json. גבו את מסד הנתונים באמצעות docker compose exec -T postgres pg_dump --format c --username affine affine > affine.dump במקום העתקת הקבצים, כיוון שלא ניתן להעתיק Postgres פעיל בצורה בטוחה. בצעו tar ל-./data/storage עבור ההעלאות ושמרו עותק של config.json באופן ידני, שכן ייצוא התצורה מלוח הבקרה של המנהל מוגדר כטרם מומש נכון לאוגוסט 2026.
האם שיתוף פעולה בזמן אמת עובד ב-AFFiNE באירוח עצמי?
כן, ואין צורך להפעיל דבר עבור כך. הדרישה היחידה היא ה-reverse proxy שלכם, כיוון שהסנכרון רץ על גבי חיבורי WebSocket. ב-nginx המשמעות היא proxy_http_version 1.1 בתוספת ה-headers מסוג Upgrade ו-Connection: upgrade, בעוד ש-Traefik ו-Caddy מעבירים את החיבורים הללו ללא צורך בתצורה נוספת. הסימפטום של proxy שאינו מבצע upgrade לחיבורים אלו הוא סביבת עבודה שנטענת ומתחברת כרגיל, בעוד ששינויים שבוצעו בדפדפן אחד לעולם אינם מופיעים באחר.
האם ניתן להריץ את AFFiNE עם תמונת Postgres סטנדרטית?
לא. ה-schema.prisma של AFFiNE מצהיר על extensions = [pgvector(map: "vector")] ומגדיר ארבע טבלאות עם עמודת embedding מסוג vector(1024), ומשימת המיגרציה יוצרת את הטבלאות הללו גם כאשר תכונות ה-AI כבויות. השתמשו ב-pgvector/pgvector:pg16, שהוא Postgres 16 עם התוסף הזה מהודר בתוכו. אם אתם מפנים את AFFiNE לשרת Postgres חיצוני במקום זאת, התקינו עליו pgvector וצרו את התוסף במסד הנתונים היעד לפני הרצת המיגרציה.