آموزش نصب Paperless-ngx روی VPS با Docker Compose
راهنمای کامل نصب Paperless-ngx روی VPS با استفاده از Docker Compose. پیکربندی صحیح Postgres، تنظیم PAPERLESS_URL، مدیریت پوشه consume، OCR و روشهای پشتیبانگیری از اسناد.
آنچه میسازید
نصب Paperless-ngx روی یک VPS، پوشهای از اسناد اسکنشده را به یک آرشیو قابلجستجو تبدیل میکند. شما یک فایل PDF را در دایرکتوری تحت نظارت (watched directory) قرار میدهید، سرور روی آن OCR (تشخیص نوری کاراکتر) اجرا میکند، متن را استخراج کرده، تاریخ و فرستنده را حدس میزند و آن را بایگانی میکند. این نصب شامل یک فایل Docker Compose با چهار سرویس است. هر آنچه پس از آن میآید مربوط به پیکربندی است و این راهنما بخش عمدهای از حجم خود را به آن اختصاص داده، زیرا خرابیهای نصب معمولاً در این مرحله رخ میدهند. این ابزار یک کتابخانه عکس نیست: OCR و حدس فرستنده برای پوشهای از تصاویر تعطیلات (JPEG) کاربردی ندارند، بنابراین آنها را در یک سرور عکس که برای این کار ساخته شده قرار دهید و Paperless را فقط برای اسناد کاغذی نگه دارید. ویدیوها نیز همین وضعیت را دارند: مجموعهای از فیلمهای ریپشده متعلق به یک مدیا سرور است، جایی که چیزی شبیه به یک رابط کاربری Jellyfin که به شکل فروشگاه کرایه فیلم دهه 90 طراحی شده، مرور کردن را به جای جستجو، به هدف اصلی تبدیل میکند.
Paperless-ngx نسخهٔ تحت پشتیبانی جامعهٔ کاربری از پروژه اصلی Paperless است. این نرمافزار رایگان و self-hosted است و اسناد شما را به صورت فایلهای ساده روی دیسک ذخیره میکند، بنابراین هرگز دسترسی شما به آرشیو شخصیتان قطع نخواهد شد. اجرای آن روی یک VPS به جای یک سیستم خانگی به این معناست که اسناد اسکنشدهٔ شما از هر جایی در دسترس هستند بدون اینکه نیاز باشد پورتی را روی روتر خانگی خود باز کنید؛ این ابزار همچنین با یک نمونه شخصی Nextcloud برای فایلهایی که سند کاغذی نیستند به خوبی جفت میشود. همین منطق برای دسکتاپی که اسکنر به آن متصل است نیز صدق میکند، چرا که یک RustDesk relay شخصی روی همان VPS به شما اجازه میدهد آن دستگاه را از راه دور کنترل کنید بدون اینکه نیازی به ایجاد حفره امنیتی در روتر باشد.
پشته نرمافزاری در حال اجرا
فایل compose رسمی، چهار کانتینر را اجرا میکند و آگاهی از عملکرد هر یک، خواندن لاگها را آسانتر میکند.
webserver: خود ایمیج paperless-ngx. این کانتینر رابط وب، API، پردازشگر (consumer) که پوشه ورودی شما را پایش میکند، و workerهای Celery که وظیفه OCR را بر عهده دارند، اجرا میکند.db: دیتابیس PostgreSQL. این بخش متادیتا، برچسبها، فرستندگان و جداول ایندکس جستجوی تماممتن را نگهداری میکند. فایلهای PDF شما در این بخش ذخیره نمیشوند.broker: دیتابیس Valkey، یک ذخیرهساز کلید-مقدار سازگار با Redis. این بخش نقش صف وظایف بین پردازش وب و workerها را ایفا میکند.gotenbergوtika: اختیاری هستند و فقط در نسخههای-tikaاز فایل compose وجود دارند. این کانتینرها اسناد آفیس (.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 دریافت (pull) میکند.
پیشنیازها
- یک VPS با سیستمعامل Ubuntu 24.04 و مجازیساز KVM، دسترسی sudo، و Docker به همراه افزونه Compose نصبشده. اگر این بخش برای شما جدید است، با اصول Docker Compose برای VPS شروع کنید و سپس به اینجا بازگردید.
- یک نام دامنه که رکورد A آن به سمت VPS اشاره میکند. Paperless ورود از طریق HTTPS را روی نام دامنهای که برای آن تعریف نشده باشد نمیپذیرد، بنابراین این مورد زودتر از آنچه تصور میکنید اهمیت پیدا میکند.
- حافظه RAM محدودیت اصلی است. PostgreSQL، Valkey، وبسرور Granian و پردازشگر 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)"این ابزار سوالاتی میپرسد و فایلها را برای شما ایجاد میکند. انجام دستی این کار تنها شامل 4 دستور است و باعث میشود دقیقاً بدانید هر چیزی در کجا قرار دارد؛ این همان چیزی است که برای سروری که قصد نگهداری آن را دارید، به آن نیاز دارید.
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 برای چند صد سند مناسب است، اما ایندکس جستجوی تماممتن (full-text search) خیلی زودتر از PostgreSQL کند میشود.
فایل .env شامل یک خط است: COMPOSE_PROJECT_NAME=paperless. این نام به پیشوند تمام کانتینرها و volumeها تبدیل میشود، بنابراین آن را حذف نکنید و بعد تعجب نکنید که چرا docker compose down -v نمیتواند دادههای شما را پیدا کند.
پیکربندی docker-compose.env پیش از اولین اجرا
دو تنظیمات اختیاری نیستند. کلید امنیتی (secret key) را با دستوری که در مستندات پروژه آمده است تولید کنید:
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=1000مقدار PAPERLESS_SECRET_KEY بهصورت پیشفرض change-me است. این مقدار توکنهای نشست (session tokens) را امضا میکند و نسخههای فعلی، این مقدار جایگیر (placeholder) را بهعنوان تنظیمنشده در نظر میگیرند: تا زمانی که این کلید وجود نداشته باشد، خالی باشد یا همچنان change-me باقی بماند، paperless اجرا نمیشود؛ بنابراین تا زمانی که آن را جایگزین نکنید، stack بالا نخواهد آمد. این مقدار را پیش از اولین اجرا تنظیم کنید، زیرا تغییر آن در آینده باعث خروج (logout) تمام کاربران میشود.
تنظیم PAPERLESS_URL همان موردی است که در وقت شما صرفهجویی میکند. Paperless یک برنامه Django است و Django هم هدر Host و هم مبدأ (origin) گزارششده توسط مرورگر در فرم ورود را بررسی میکند. مقدار PAPERLESS_URL را تنظیم کنید تا آدرس شما را به ALLOWED_HOSTS، CORS_ALLOWED_HOSTS و CSRF_TRUSTED_ORIGINS اضافه کند. اگر آن را خالی بگذارید و یک دامنه را به سرور اشاره دهید، صفحه ورود همچنان بارگذاری میشود (چون PAPERLESS_ALLOWED_HOSTS بهطور پیشفرض * است)، اما ارسال فرم از طریق HTTPS با خطای CSRF verification failed. Request aborted. مواجه میشود، زیرا دامنه شما یک مبدأ مورد اعتماد نیست. این مقدار را بدون اسلش در انتها و بدون مسیر (path) وارد کنید.
مقادیر USERMAP_UID و USERMAP_GID کاربری را که container با آن اجرا میشود تعیین میکنند. آنها را با حساب کاربری خود که با دستورات id -u و id -g قابل مشاهده است، مطابقت دهید. اگر این مقادیر یکسان نباشند، فایلهایی که در پوشه consume کپی میکنید برای consumer غیرقابل خواندن خواهند بود و لاگها بهجای عملیات وارد کردن (import)، خطای دسترسی (permission error) نشان میدهند.
راهاندازی پشته و ایجاد اولین کاربر
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser از شما نام کاربری، ایمیل و رمز عبور میخواهد. هیچ ورود پیشفرضی وجود ندارد. اگر از این مرحله بگذرید، رابط وب از اولین کسی که آن را باز کند میخواهد که superuser را ایجاد کند؛ از آنجایی که پورت 8000 همچنان برای اینترنت باز است، ممکن است آن شخص شما نباشید، بنابراین حساب کاربری را بلافاصله از طریق shell ایجاد کنید. پیش از آنکه مرورگر را امتحان کنید، منتظر بمانید تا لاگ سرور گزارش دهد که روی پورت 8000 در حال گوش دادن است. اولین اجرا همچنین مهاجرتهای دیتابیس را انجام میدهد که یک یا دو دقیقه زمان میبرد.
پیش از اتصال دامنه، آن را بهصورت محلی بررسی کنید:
curl -I http://127.0.0.1:8000یک 302 که به /accounts/login/ تغییر مسیر (redirect) میدهد، به این معنی است که پشته سالم است.
پیادهسازی HTTPS در مقابل سرویس
فایل compose پیشفرض، پورت 8000:8000 را منتشر میکند که به تمام رابطهای شبکه متصل میشود. در یک VPS عمومی، این کار باعث میشود کل آرشیو اسناد شما از طریق HTTP ساده برای هر کسی که آدرس را پیدا کند، در دسترس قرار گیرد. خط مربوط به پورت را تغییر دهید تا فقط به loopback متصل شود:
ports:
- "127.0.0.1:8000:8000"سپس TLS (امنیت لایه انتقال) را در یک reverse proxy خاتمه دهید و درخواستها را به 127.0.0.1:8000 هدایت کنید. اگر این تنها برنامه روی سرور است، هر پروکسی که دارای کلاینت ACME (محیط مدیریت خودکار گواهی) باشد، کافی است. اگر چندین کانتینر را پشت یک تنظیمات گواهی اجرا میکنید، از الگوی reverse proxy ترافیک (Traefik) برای چندین برنامه Docker Compose پیروی کنید و سرویس webserver را بدون انتشار هیچ پورتی، به شبکه پروکسی متصل کنید.
از هر پروکسی که استفاده میکنید، بررسی مبدأ (origin check) در فرم ورود، جایی است که تنظیمات HTTPS معمولاً با مشکل مواجه میشوند. سرویس Paperless متغیر X-Forwarded-Proto را نادیده میگیرد مگر اینکه PAPERLESS_PROXY_SSL_HEADER را تنظیم کنید که بهصورت پیشفرض غیرفعال است. در نتیجه، Django هر درخواست پروکسیشده را بهعنوان HTTP ساده در نظر میگیرد و مبدأ https:// که مرورگر شما ارسال میکند، تنها در صورتی تأیید میشود که در لیست موارد مورد اعتماد باشد. تنظیم PAPERLESS_URL روی دقیقاً همان آدرس https:// که در مرورگر تایپ میکنید، آن را به این لیست اضافه میکند. بدون این تنظیم، با خطای CSRF verification failed. Request aborted. در صفحهای که ظاهر درستی دارد، مواجه خواهید شد.
همچنین محدودیت حجم آپلود پروکسی را افزایش دهید. یک اسکن 40 مگابایتی که از پروکسی با محدودیت بدنه 1 مگابایت عبور میکند، پیش از آنکه به Paperless برسد رد میشود و مرورگر یک خطای عمومی آپلود گزارش میدهد.
نحوه عملکرد دایرکتوری consume
فایل compose، مسیر ./consume را از دایرکتوری compose به داخل کانتینر bind-mount میکند. هر فایلی که در آنجا قرار دهید، وارد سیستم شده و سپس از آن پوشه حذف میشود، زیرا فایل اکنون در 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 از اعلانهای بومی سیستمفایل استفاده میکند که بلافاصله فعال میشوند. این اعلانها در سیستمفایلهای شبکهای قابلاطمینان نیستند. اگر دایرکتوری consume شما یک share از نوع NFS یا SMB است تا یک اسکنر تحت شبکه بتواند در آن بنویسد، ممکن است فایلها بدون شناسایی باقی بمانند. راهحل این است که بازه زمانی را روی یک عدد مثبت (بر حسب ثانیه) تنظیم کنید تا paperless بهجای تکیه بر اعلانها، پوشه را بهصورت دورهای اسکن کند.
زبانهای OCR و هزینههای آنها
PAPERLESS_OCR_LANGUAGE یک کد سه حرفی Tesseract دریافت میکند که مقدار پیشفرض آن eng است. برای ترکیب زبانها از علامت مثبت استفاده کنید، مانند deu+eng. در این حالت، Tesseract برای هر صفحه با تمام زبانهای فهرستشده کار میکند، بنابراین هر زبان اضافی، زمان پردازنده (CPU) مورد نیاز برای هر اسکن را افزایش میدهد. در یک VPS با vCPU اشتراکی، این موضوع تفاوت بین اتمام اسکن در 10 ثانیه و یک دقیقه است. فقط زبانهایی را فهرست کنید که اسناد شما واقعاً به آن زبانها نوشته شدهاند.
این ایمیج بهصورت پیشفرض شامل زبانهای انگلیسی، آلمانی، ایتالیایی، اسپانیایی و فرانسوی است. برای هر زبان دیگری، آن را به PAPERLESS_OCR_LANGUAGES به صورت فهرستی با جداکننده فاصله اضافه کنید، برای مثال PAPERLESS_OCR_LANGUAGES=tur ces، و سپس سرویس را ریاستارت کنید. کانتینر بستههای داده Tesseract را در زمان شروع دانلود میکند، بنابراین اولین بوت پس از این تغییر، کندتر خواهد بود.
پشتیبانگیری از پایگاه داده و رسانهها
کپی کردن Docker volumeها در حالی که PostgreSQL در حال اجرا است، پشتیبان غیرقابلاطمینانی ایجاد میکند که ممکن است بازیابی نشود. Paperless ابزار صادرکننده (exporter) داخلی خود را دارد که اسناد را به همراه یک فایل manifest با فرمت JSON از تمام متادیتاها در bind mount مشخصشده در ./export مینویسد:
docker compose exec -T webserver document_exporter ../export --delete --no-progress-barدستور --delete فایلهای صادرشدهای را که دیگر با سند فعلی مطابقت ندارند حذف میکند تا پوشه بهجای رشد بیرویه، یک آینه دقیق از وضعیت فعلی باقی بماند. --no-progress-bar خروجی را هنگام اجرا از طریق cron تمیز نگه میدارد و -T مانع از آن میشود که Compose درخواست ترمینالی کند که در cron وجود ندارد.
بازیابی با استفاده از document_importer روی همان پوشه در یک stack جدید انجام میشود؛ این یعنی دایرکتوری export تنها چیزی است که باید از آن محافظت کنید. آن را طبق زمانبندی با پشتیبانهای رمزنگاریشده و deduplicate شده restic از VPS خود به خارج از سایت منتقل کنید و همیشه ابتدا export را اجرا کنید تا restic هرگز یک آرشیو ناقص را کپی نکند.
با بررسی وجود فایل export/manifest.json و تطبیق تعداد فایلها با تعداد اسناد در رابط کاربری، پشتیبان را تأیید کنید. پشتیبانی که هرگز آن را لیست نکردهاید، پشتیبان نیست. یک export شبانه که بیسروصدا با شکست مواجه میشود، بدتر است؛ بنابراین کاری کنید که cron job وضعیت خروج (exit status) خود را به سرور ntfy شخصی شما ارسال کند تا بهجای روزی که به بازیابی نیاز دارید، همان هفتهای که مشکل رخ میدهد از آن مطلع شوید.
FAQ
چرا پس از اشاره دادن دامنه به سرویس، تمام صفحات خطای "Bad Request (400)" برمیگردانند؟
با فایلهای پیشفرض چنین اتفاقی نباید رخ دهد، زیرا PAPERLESS_ALLOWED_HOSTS بهطور پیشفرض روی * تنظیم شده است. وجود یک 400 همراه با DisallowedHost در لاگ کانتینر به این معناست که PAPERLESS_ALLOWED_HOSTS روی لیستی تنظیم شده که دامنه شما در آن قرار ندارد. مقدار PAPERLESS_URL=https://paperless.example.com را در docker-compose.env بدون اسلش انتهایی تنظیم کنید تا paperless آن نام دامنه را به لیست اضافه کند، یا خط PAPERLESS_ALLOWED_HOSTS را حذف نمایید. سپس دستور docker compose up -d را اجرا کنید تا کانتینر دوباره ساخته شود. ویرایش فایل env بهتنهایی تأثیری ندارد، زیرا کانتینر در حال اجرا، محیطی را که با آن شروع شده است حفظ میکند.
فایلی را در پوشه consume قرار دادم اما هیچ اتفاقی نیفتاد. مشکل چیست؟
ابتدا docker compose logs webserver را بررسی کنید. خطای مجوز به این معناست که USERMAP_UID و USERMAP_GID با کاربری که مالک فایل است مطابقت ندارند؛ پس آنها را اصلاح کرده و کانتینر را دوباره بسازید. اگر هیچ خطی در لاگ ثبت نمیشود، یعنی رویداد فایل هرگز دریافت نشده است؛ این اتفاق در network shareها رخ میدهد زیرا اعلانهای سیستمفایل (filesystem notifications) در آنجا قابلاطمینان نیستند. مقدار PAPERLESS_CONSUMER_POLLING_INTERVAL را روی چیزی مانند 30 تنظیم کنید تا paperless هر 30 ثانیه پوشه را اسکن کند.
آیا میتوانم paperless-ngx را بهجای PostgreSQL با SQLite اجرا کنم؟
بله، docker-compose.sqlite.yml پشتیبانی میشود و حافظه کمتری مصرف میکند که برای یک VPS کوچک مناسب است. نقطه ضعف آن با رشد آرشیو مشخص میشود: جستجوی متن کامل و ویرایش دستهجمعی برچسبها در تعداد هزاران سند بهطور محسوسی کند میشود. مهاجرت در آینده به معنای export و import مجدد است، بنابراین اگر انتظار دارید آرشیو شما رشد کند، از همین حالا PostgreSQL را انتخاب کنید.
یک آرشیو از اسکنها واقعاً به چه مقدار فضای دیسک نیاز دارد؟
تقریباً دو برابر حجم فایلهای اصلی. Paperless فایل اصلی را دستنخورده نگه میدارد و یک فایل PDF دوم که شامل لایه متنی قابل جستجو (OCR شده) است، به همراه تصاویر بندانگشتی کوچک ذخیره میکند. یک اسکن متنی 200 KB کوچک باقی میماند. یک اسکن رنگی 30 MB از یک قرارداد طولانی، حدود 60 MB فضا اشغال میکند. اگر دایرکتوری export را نیز روی همان دیسک نگه دارید، همان آرشیو سه بار روی دیسک ذخیره میشود.
آیا به کانتینرهای Tika و Gotenberg نیاز دارم؟
فقط اگر میخواهید فایلهای Word، Excel یا OpenDocument در کنار PDFها ایندکس شوند. این کانتینرها آن فرمتها را به PDF تبدیل میکنند تا paperless بتواند آنها را OCR کرده و جستجو کند. آنها همچنین دو کانتینر در حال اجرای دیگر و چند صد مگابایت حافظه اضافه میکنند، بنابراین اگر تمام فایلهای شما از قبل PDF یا تصویر هستند، از نصب آنها در یک سرور کوچک صرفنظر کنید.