התקנת Paperless-ngx על שרת VPS באמצעות Docker Compose
מדריך מעשי להקמת מערכת Paperless-ngx לניהול מסמכים ב-VPS. למדו להגדיר את ה-stack הרשמי, להגדיר את PAPERLESS_URL, לנהל תיקיית צריכה, להגדיר שפות OCR ולבצע גיבויים מאובטחים.
מה אתם בונים
התקנת Paperless-ngx על גבי VPS הופכת תיקיית מסמכים סרוקים לארכיון שניתן לבצע בו חיפוש. אתם מניחים קובץ PDF בתיקייה ייעודית, והשרת מריץ עליו OCR (זיהוי תווים אופטי), מחלץ את הטקסט, מנחש את התאריך ואת הגורם השולח, ומייעד את הקובץ למקומו. ההתקנה מבוססת על קובץ Docker Compose אחד הכולל ארבעה שירותים. כל מה שבא לאחר מכן הוא הגדרות, ומדריך זה מתמקד בעיקר בכך, שכן שם נוטות התקנות להיכשל. המערכת אינה מיועדת לשמש כספריית תמונות: OCR וניחוש גורמים אינם מועילים עבור תיקיית תמונות חופשה בפורמט JPEG, לכן כדאי לשמור אותן ב-שרת תמונות ייעודי ולהשאיר את Paperless למסמכים בלבד.
Paperless-ngx הוא fork קהילתי מתוחזק של פרויקט Paperless המקורי. הוא חינמי, מאפשר אירוח עצמי, ושומר את המסמכים שלכם כקבצים רגילים בדיסק, כך שלא תיחסמו לעולם מחוץ לארכיון שבבעלותכם. הפעלה שלו על VPS במקום על מחשב ביתי מאפשרת לגשת לסריקות מכל מקום, בלי לפתוח פורט בנתב הביתי, והוא משתלב היטב עם מופע פרטי של Nextcloud עבור הקבצים שאינם על נייר. אותו היגיון חל גם על המחשב שאליו מחובר הסורק, משום ש־relay פרטי של RustDesk על אותו VPS מאפשר להפעיל את המחשב מרחוק, בלי לפתוח גם הפעם פתח בנתב.
מה ה-stack מריץ בפועל
קובץ ה-compose הרשמי מפעיל ארבעה מכולות (containers), והבנה של תפקיד כל אחת מהן הופכת את הלוגים לקריאים.
webserver: ה-image של paperless-ngx עצמו. הוא מריץ את ממשק ה-web, את ה-API, את ה-consumer שעוקב אחר תיקיית הקלט שלכם, ואת ה-Celery task workers שמבצעים OCR.db: PostgreSQL. הוא מאחסן מטא-דאטה, תגיות, אנשי קשר וטבלאות אינדקס לחיפוש טקסט מלא. הוא אינו מאחסן את קובצי ה-PDF שלכם.broker: Valkey, מאגר key-value תואם Redis. הוא משמש כתור משימות בין תהליך ה-web לבין ה-workers.gotenbergו-tika: אופציונליים, קיימים רק בגרסאות ה--tikaשל ה-compose. הם ממירים מסמכי Office (.docx,.xlsx,.odt) ל-PDF כדי ש-paperless יוכל לאנדקס אותם.
נכון ליולי 2026, קובץ ה-compose של postgres מקבע את docker.io/library/postgres:18 ו-docker.io/valkey/valkey:9-alpine, ומושך את היישום מ-ghcr.io/paperless-ngx/paperless-ngx:latest.
דרישות קדם
- שרת VPS מסוג KVM המריץ Ubuntu 24.04 עם הרשאות sudo, ו־Docker עם תוסף Compose מותקן. אם נושא זה חדש עבורך, התחל ב-יסודות Docker Compose עבור VPS וחזור לכאן.
- שם מתחם (domain name) עם רשומת A המצביעה על ה־VPS. Paperless מסרב לשרת בקשות עבור שם מארח שלא הוגדר מראש, לכן נושא זה חשוב מוקדם מכפי שנדמה.
- זיכרון הוא המגבלה הממשית. PostgreSQL, Valkey, gunicorn ורכיב ה־OCR של Tesseract פועלים כולם בו-זמנית ותופסים כ־2 GB לשימוש קל. הקצה 4 GB אם בכוונתך לייבא ארכיון של מאות סריקות, שכן ביצוע OCR על קובץ PDF גדול מרובה עמודים יוצר זינוק בזיכרון שעלול להוביל להריגת התהליך על ידי ה-OOM killer של הליבה.
- דיסק: הארכיון שלך נשמר פעמיים – הקובץ המקורי וקובץ ה-PDF שעבר OCR – לכן יש לתכנן נפח אחסון כפול מגודל הסריקות שלך.
קבלת קובצי ה-compose הרשמיים
קיים מתקין אינטראקטיבי:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"הוא מציג שאלות וכותב עבורך את הקבצים. ביצוע הפעולה ידנית מסתכם בארבע פקודות ומבטיח שתדע היכן נמצא כל רכיב, וזהו המצב הרצוי בשרת שאתה מתחזק.
mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.envהגרסאות השונות נמצאות באותה ספרייה: docker-compose.sqlite.yml, docker-compose.mariadb.yml, וגרסת -tika לכל אחת מהן. בחר ב-postgres עבור התקנה חדשה. SQLite מתאים לכמה מאות מסמכים, אך אינדקס החיפוש בטקסט מלא הופך לאיטי הרבה לפני ש-PostgreSQL מגיע למגבלותיו.
הקובץ .env מכיל שורה אחת, COMPOSE_PROJECT_NAME=paperless. שם זה הופך לקידומת של כל מכולה (container) וכל נפח אחסון (volume), לכן אל תמחק אותו ואז תתהה מדוע docker compose down -v אינו מוצא את הנתונים שלך.
הגדרת docker-compose.env לפני ההפעלה הראשונה
שתי הגדרות אינן אופציונליות. צרו את מפתח ה-secret באמצעות הפקודה המתועדת בפרויקט:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"לאחר מכן, ערכו את docker-compose.env:
PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000PAPERLESS_SECRET_KEY מגיע עם הערך המילולי change-me. הוא משמש לחתימה על עוגיות (session cookies), לכן השארתו כפי שהוא מאפשרת לכל מי שמכיר את ערך ברירת המחדל לזייף session. הגדירו אותו לפני ההפעלה הראשונה, כיוון ששינוי שלו לאחר מכן ינתק את כל המשתמשים מהמערכת.
PAPERLESS_URL היא ההגדרה שתחסוך לכם שעה של עבודה. Paperless הוא יישום Django, ו-Django מאמת את ה-header מסוג Host בכל בקשה. הגדירו את PAPERLESS_URL והוא ימלא עבורכם את ALLOWED_HOSTS, CORS_ALLOWED_HOSTS ו-CSRF_TRUSTED_ORIGINS. אם תשאירו אותו ריק ותפנו דומיין לשרת, כל דף יחזיר שגיאת Bad Request (400) עם DisallowedHost בלוג של המכולה. כתבו את הערך ללא לוכסן (slash) בסוף וללא נתיב.
USERMAP_UID ו-USERMAP_GID קובעים את המשתמש שתחתיו רצה המכולה. התאימו אותם לחשבון שלכם, כפי שניתן לבדוק באמצעות id -u ו-id -g. אם הם אינם תואמים, קבצים שתעתיקו לתיקיית ה-consume לא יהיו קריאים עבור ה-consumer, והלוג יציג שגיאת הרשאות במקום לבצע ייבוא.
הפעלת ה-stack ויצירת המשתמש הראשון
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webserverהפקודה createsuperuser תבקש ממך שם משתמש, כתובת דוא"ל וסיסמה. אין פרטי התחברות ברירת מחדל, לכן דילוג על שלב זה ישאיר אותך בדף התחברות שלא יקבל שום קלט. המתן להופעת שורת הלוג המציינת שהשרת מאזין בפורט 8000 לפני שתנסה לגשת אליו בדפדפן. ההפעלה הראשונה מריצה גם מיגרציות למסד הנתונים, תהליך שעשוי להימשך דקה או שתיים.
בדוק את התקינות באופן מקומי לפני הוספת שם מתחם:
curl -I http://127.0.0.1:8000הפניה (redirect) של 302 אל /accounts/login/ מעידה על כך שה-stack תקין.
הגדרת HTTPS לפני השירות
קובץ ה-compose המקורי מפרסם את 8000:8000, אשר מאזין לכל ממשקי הרשת. בשרת VPS ציבורי, הדבר חושף את כל ארכיון המסמכים שלכם ב-HTTP גלוי לכל מי שימצא את הכתובת. שנו את שורת הפורט כך שתאזין ל-loopback בלבד:
ports:
- "127.0.0.1:8000:8000"לאחר מכן, בצעו TLS termination (סיום הצפנת תעבורה) ב-reverse proxy והעבירו את התעבורה ל-127.0.0.1:8000. אם זהו היישום היחיד בשרת, כל proxy עם לקוח ACME (סביבת ניהול תעודות אוטומטית) יתאים. אם אתם מריצים כמה מכולות מאחורי תצורת תעודה אחת, עקבו אחר תבנית ה-reverse proxy של Traefik עבור יישומי Docker Compose מרובים וחברו את שירות webserver לרשת ה-proxy ללא פרסום פורט כלל.
בכל proxy שתבחרו, עליכם לשלוח את X-Forwarded-Proto: https. בלעדיו, Django סבור שהבקשה הגיעה ב-HTTP, בדיקת המקור בטופס ההתחברות נכשלת, ואתם מקבלים CSRF verification failed. Request aborted. בדף שנראה תקין. החלק השני של פתרון זה הוא הגדרת PAPERLESS_URL לכתובת ה-https:// המדויקת שאתם מקלידים בדפדפן.
בנוסף, העלו את מגבלת גודל ההעלאה ב-proxy. סריקה של 40 MB העוברת דרך proxy המגביל גופים ל-1 MB תידחה עוד לפני ש-paperless תראה אותה, והדפדפן ידווח על כשל העלאה כללי.
כיצד פועלת ספריית ה-consume
קובץ ה-compose מבצע bind-mount ל-./consume מתוך ספריית ה-compose אל תוך ה-container. כל קובץ שתניחו שם ייובא ולאחר מכן יימחק מהתיקייה, כיוון שהקובץ נמצא כעת בתוך ה-volume של ה-media תחת הניהול של paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverאתם אמורים לראות את ה-consumer מזהה את שם הקובץ, מריץ OCR, ומסיים עם שורה המדווחת שהמסמך נוסף. המחזור כולו אורך שניות בודדות עבור סריקה של עמוד אחד, ויכול להימשך דקה או יותר עבור מסמך ארוך.
שתי הגדרות משנות את האופן שבו קבצים נמצאים. PAPERLESS_CONSUMER_RECURSIVE=true גורמת ל-paperless לחפש בתיקיות משנה, ו-PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true הופכת כל שם של תיקיית משנה לתגית (tag), כך שהנחת קובץ ב-consume/invoices/2026/ מתייגת אותו כ-invoices ו-2026. זוהי מערכת התיוק הזולה ביותר שאי פעם תבנו.
זיהוי הוא החלק השני. כברירת מחדל, PAPERLESS_CONSUMER_POLLING_INTERVAL מוגדר כ-0, מה שאומר ש-paperless משתמשת בהתראות מערכת קבצים של ה-kernel, שמופעלות באופן מיידי. התראות אלו אינן עוברות דרך מערכת קבצים מרושתת. אם תיקיית ה-consume שלכם היא שיתוף NFS או SMB כדי שסורק רשת יוכל לכתוב אליה, שום דבר לא יזוהה לעולם. הפתרון הוא להגדיר את המרווח למספר חיובי של שניות, כך ש-paperless תסרוק את התיקייה במקום להסתמך על התראות.
שפות OCR ועלותן
PAPERLESS_OCR_LANGUAGE מקבל קוד Tesseract בן שלוש אותיות, כאשר ברירת המחדל היא eng. ניתן לשלב שפות באמצעות סימן פלוס, כפי שמופיע ב-deu+eng. במקרה זה, Tesseract מנסה כל שפה ובוחרת את התוצאה הטובה ביותר; לכן, כל שפה נוספת מכפילה את זמן המעבד (CPU) המושקע בכל עמוד. בשרת VPS עם vCPU משותף, מדובר בהבדל בין סריקה שמסתיימת תוך עשר שניות לבין כזו שנמשכת דקה שלמה. הגדירו רק את השפות שבהן המסמכים שלכם כתובים בפועל.
ה-image כוללת את השפות אנגלית, גרמנית, איטלקית, ספרדית וצרפתית. עבור כל שפה אחרת, הוסיפו אותה ל-PAPERLESS_OCR_LANGUAGES כרשימה מופרדת ברווחים, לדוגמה PAPERLESS_OCR_LANGUAGES=tur ces, ובצעו הפעלה מחדש. ה-container מוריד את חבילות הנתונים של Tesseract בזמן העלייה, לכן האתחול הראשון לאחר שינוי זה יהיה איטי יותר.
גיבוי מסד הנתונים והמדיה
העתקת Docker volumes בזמן ש־PostgreSQL פועל עלולה להניב גיבוי שלא ניתן לשחזור. Paperless כוללת כלי ייצוא מובנה, הכותב מסמכים יחד עם קובץ JSON המכיל את כל המטא-נתונים אל ה-bind mount המוגדר ב-./export:
docker compose exec webserver document_exporter ../export --delete --no-progress-barהדגל --delete מסיר קבצים מיוצאים שאינם תואמים עוד למסמך קיים, כך שהתיקייה נשארת תמונת מצב עדכנית במקום לגדול ללא הגבלה. הדגל --no-progress-bar שומר על פלט נקי כאשר הפקודה מופעלת מתוך cron.
השחזור מתבצע באמצעות document_importer מול אותה תיקייה בשרת חדש, מה שאומר שתיקיית הייצוא היא הדבר היחיד שצריך לשמור עליו. שלחו אותה לאתר מרוחק לפי לוח זמנים באמצעות גיבויי restic מוצפנים ומבוססי דה-דופליקציה מה-VPS שלכם, והריצו את הייצוא לפני כן כדי ש-restic לעולם לא יגבה ארכיון שנכתב רק בחלקו.
אמתו גיבוי באמצעות בדיקה ש־export/manifest.json קיים ושמספר הקבצים תואם למספר המסמכים בממשק. גיבוי שמעולם לא בדקתם את תוכנו אינו גיבוי. ייצוא לילי שמתחיל להיכשל בשקט גרוע אף יותר, לכן הגדירו ל־cron job לשלוח את קוד היציאה שלו אל שרת ה־ntfy שלכם, וכך תגלו באיזה שבוע הוא הפסיק לפעול, במקום ביום שבו תצטרכו לשחזר.
FAQ
מדוע כל דף מחזיר שגיאת "Bad Request (400)" לאחר הפניית הדומיין שלי אליו?
Django דחה את ה־header מסוג Host מכיוון שהדומיין שלך אינו מופיע בתוך ALLOWED_HOSTS. הגדירו את PAPERLESS_URL=https://paperless.example.com בתוך docker-compose.env, ללא לוכסן (slash) בסוף, ולאחר מכן הריצו את docker compose up -d כדי ליצור מחדש את ה־container. עריכת קובץ ה-env בלבד אינה משפיעה, כיוון שה־container הפעיל שומר על סביבת העבודה איתה הוא עלה.
שמתי קובץ PDF בתיקיית ה-consume ולא קרה דבר. מה הבעיה?
בדקו תחילה את docker compose logs webserver. שגיאת הרשאות משמעותה ש־USERMAP_UID ו־USERMAP_GID אינם תואמים לחשבון שבבעלותו הקובץ; תקנו זאת וצרו מחדש את ה־container. אם אין שורת לוג כלל, משמעות הדבר היא שאירוע הקובץ לא התקבל; זה קורה בתיקיות רשת (network shares) כיוון שהתראות ה-kernel אינן עוברות דרכן. הגדירו את PAPERLESS_CONSUMER_POLLING_INTERVAL לערך כגון 30, ו-paperless יסרוק את התיקייה בכל 30 שניות במקום זאת.
האם ניתן להריץ את paperless-ngx עם SQLite במקום PostgreSQL?
כן, docker-compose.sqlite.yml נתמך וצורך פחות זיכרון, מה שמתאים ל-VPS קטן. המחיר לכך מורגש ככל שהארכיון גדל: חיפוש טקסט מלא ועריכת תגיות מרובות הופכים לאיטיים משמעותית לאחר אלפי מסמכים. הגירה מאוחרת יותר דורשת ייצוא וייבוא, לכן בחרו ב-PostgreSQL כבר עכשיו אם אתם צופים שהארכיון ימשיך לגדול.
כמה שטח דיסק דורש ארכיון של סריקות בפועל?
בערך פי שניים מגודל קובצי המקור שלכם. Paperless שומר על המקור ללא שינוי ומאחסן קובץ PDF שני שעבר OCR עם שכבת טקסט לחיפוש, בתוספת תמונות ממוזערות (thumbnails). סריקה של 200 KB המכילה טקסט בלבד נשארת קטנה. סריקה צבעונית של 30 MB של חוזה ארוך תתפוס כ-60 MB. הוסיפו את ספריית הייצוא אם אתם שומרים אותה על אותו דיסק, והארכיון כולו נמצא על הדיסק שלוש פעמים.
האם אני זקוק ל-containers של Tika ו-Gotenberg?
רק אם אתם מעוניינים שקבצי Word, Excel או OpenDocument יאונדקסו לצד קובצי ה-PDF שלכם. הם ממירים פורמטים אלו ל-PDF כדי ש-paperless יוכל לבצע להם OCR ולחפש בתוכם. הם גם מוסיפים עוד שני containers פעילים וכמה מאות מגה-בייטים של זיכרון, לכן דלגו עליהם בשרת קטן אם כל מה שאתם מתייקים הוא כבר PDF או תמונה.