{"id":137,"date":"2025-07-20T12:15:13","date_gmt":"2025-07-20T11:15:13","guid":{"rendered":"https:\/\/david.lidstone.me\/blog\/?p=137"},"modified":"2025-07-26T12:32:39","modified_gmt":"2025-07-26T11:32:39","slug":"paperless-ngx-real-world-install","status":"publish","type":"post","link":"https:\/\/david.lidstone.me\/blog\/?p=137","title":{"rendered":"Paperless NGX Real World Install"},"content":{"rendered":"\n<h2 class=\"wp-block-heading has-x-large-font-size\">What is Paperless NGX?<\/h2>\n\n\n\n<p>Paperless is a mature document management system with lots of features. NGX is the latest fork. I highly recommend it, but I won&#8217;t go into the features here &#8211; just search YouTube for some videos and there is lots of information.<\/p>\n\n\n\n<p>So, if there is lots of information, why would you want to follow this install? Because although there are lots of tutorials, most of them are focused on &#8216;fast&#8217; and &#8216;easy&#8217;, and the upshot is something you can play around with, but would not want to rely on. The tutorials also don&#8217;t tend to go into the stack in much detail and can end up a bit &#8220;you know what to do&#8221;. I don&#8217;t. I really don&#8217;t.<\/p>\n\n\n\n<p>Having mainly been using containers (and having other reasons for keeping my single home-server on Windows) I don&#8217;t have a Linux server to use. I also want to use a NAS for the file storage as I don&#8217;t have a lot of room on my server. <\/p>\n\n\n\n<h2 class=\"wp-block-heading has-x-large-font-size\">So, what are we doing?<\/h2>\n\n\n\n<p>A quick disclaimer before we dive in. I used perplexity.ai and ChatGPT heavily (and over many hours) to get this straight, so most credit goes to whomever it was stolen from by the AI.<\/p>\n\n\n\n<p>The stack will be:<\/p>\n\n\n\n<p><mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\"> Windows 10 &#8211;&gt; Hyper-V &#8211;&gt;Ubuntu 24 LTS &#8211;&gt; Docker<\/mark><\/p>\n\n\n\n<p>My Windows 10 is running on a Dell Optiplex 3050 (great as a small, cheap home server). I am also using an ASUS RT-AX86U with an external HD shared over Samba \/ CIFS for the NAS storage. If your NAS supports NFS instead, then I would recommend using that, but I won&#8217;t be covering it here.<\/p>\n\n\n\n<p>We&#8217;ll end up with a working install of Paperless NGX with the data folders mounted on the NAS and the GUI available on the local network. All functionality will work, including ingesting documents using the &#8216;consume&#8217; folder and exporting (again, to the NAS).<\/p>\n\n\n\n<p>You could easily do the exports to another location, such as a second NAS or cloud storage, and also expose the GUI to the internet if you really wanted, but I prefer access over VPN. If you do want to expose the GUI, be aware that this install does not include setting up https.<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-x-large-font-size\">Why not just Docker Containers like the tutorials?<\/h2>\n\n\n\n<p>Well, I do use Docker on Windows 10 and WSL, however, I don&#8217;t suggest trying to get the mounts working properly as well as sharing the GUI on the LAN. Life is too short and the information that I could fine was dated, confusing, sometimes plain wrong and ultimately not that helpful. I even asked ChatGPT to help but it just laughed and told me not to waste its time. If anyone cracks this, then be a hero and let me know!<\/p>\n\n\n\n<h2 class=\"wp-block-heading has-x-large-font-size\">The Build<\/h2>\n\n\n\n<h3 class=\"wp-block-heading has-large-font-size\">Getting started with Hyper-V and Ubuntu<\/h3>\n\n\n\n<p>Install Hyper-V, if you don&#8217;t already have it. There are plenty of tutorials which will cover this.<\/p>\n\n\n\n<p>Next, download the ISO of <a href=\"https:\/\/ubuntu.com\/download\/server\">Ubuntu Server<\/a>. I used 24.04.2 LTS.<\/p>\n\n\n\n<p>In Hyper-V, find the Virtual Switch Manager and create a new external switch associated with your network adapter. This is my added switch:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full has-custom-border\"><img loading=\"lazy\" decoding=\"async\" width=\"717\" height=\"426\" src=\"https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image.png\" alt=\"\" class=\"wp-image-141\" style=\"border-style:none;border-width:0px;border-radius:0px;box-shadow:var(--wp--preset--shadow--deep)\" srcset=\"https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image.png 717w, https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image-300x178.png 300w\" sizes=\"auto, (max-width: 717px) 100vw, 717px\" \/><\/figure>\n\n\n\n<p>Create a new Virtual Machine (don&#8217;t use the wizard). I chose Generation 2. I made the mistake of creating a 10gb partition the first time, but this was too small and was running out of space to process large temp files. We&#8217;re not storing the actual library files on this machine, so I would recommend 20gb.<\/p>\n\n\n\n<p>You can use most of the defaults. In Configure Networking, select the switch you just created and in Installation Options, choose your Ubuntu ISO as the install source:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"698\" height=\"258\" src=\"https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image-1.png\" alt=\"\" class=\"wp-image-142\" style=\"box-shadow:var(--wp--preset--shadow--deep)\" srcset=\"https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image-1.png 698w, https:\/\/david.lidstone.me\/blog\/wp-content\/uploads\/2025\/07\/image-1-300x111.png 300w\" sizes=\"auto, (max-width: 698px) 100vw, 698px\" \/><\/figure>\n\n\n\n<p>Follow the Ubuntu installation. Ensure that you check the option to install Docker and set eth0 to the external network switch you set up on Hyper-V. I used DHCP and then fixed the IP on the router.<\/p>\n\n\n\n<h3 class=\"wp-block-heading has-large-font-size\">Mount the NAS<\/h3>\n\n\n\n<p>Note: I will be using my Ubuntu user &#8216;david&#8217; and the name of my share \/ mount &#8216;shared&#8217; for clarity and <mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">highlighting<\/mark> any sections you are likely to want to change to match your environment.<\/p>\n\n\n\n<p>The mount will vary slightly with your SMB version. Googling my router, the result was that it didn&#8217;t support SMB2, but in reality, it does. I haven&#8217;t tried 2.1, but did try 3.0 with no success.<\/p>\n\n\n\n<p>As mentioned above, this is for CIFS \/ Samba.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update\nsudo apt upgrade\nsudo apt install cifs-utils\n\n# my NAS folder is shared and will have a folder\n# called paperless\nsudo mkdir -p \/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared<\/mark><\/code><\/pre>\n\n\n\n<p>Next we will create a file with our credentials.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vim \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/.smbcredentials<\/code><\/pre>\n\n\n\n<p>and save with the user name and password in this format<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>username=<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color has-foreground-color\">your_nas_username<\/mark>\npassword=<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">your_nas_password<\/mark><\/code><\/pre>\n\n\n\n<p>and secure the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo chmod 600 \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/.smbcredentials<\/code><\/pre>\n\n\n\n<p>Set up the automatic mounting upon boot:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vim \/etc\/fstab<\/code><\/pre>\n\n\n\n<p>and add the mapping:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># NAS Samba Share\n\/\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">192.168.2.1\/shared<\/mark> \/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color has-foreground-color\">shared<\/mark> cifs credentials=\/home\/david\/.smbcredentials,iocharset=utf8,vers=<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">2.0<\/mark>,uid=1000,gid=1000,file_mode=0755,dir_mode=0755 0 0<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading has-large-font-size\">Install and configure Paperless<\/h3>\n\n\n\n<p>Start by adding your use to a docker group so you can run the containers:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd docker<br>newgrp docker<br>sudo usermod -aG docker <mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark><\/code><\/pre>\n\n\n\n<p>Use the installation script from <a href=\"https:\/\/docs.paperless-ngx.com\/setup\/\">https:\/\/docs.paperless-ngx.com\/setup\/<\/a> and install into a directory <code>paperless-ngx<\/code> under your user (don&#8217;t run as root). I also recommend using the PostgreSQL database.<\/p>\n\n\n\n<p>Edit the docker-compose.env file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vim \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/docker-compose.env<\/code><\/pre>\n\n\n\n<p>Check the time zone is set correctly. In my case I had to change to:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PAPERLESS_TIME_ZONE=<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">Europe\/London<\/mark><\/code><\/pre>\n\n\n\n<p>And add these settings to disable inotify (CIFS does not support this) and instead poll for any documents to ingest from the consume directory. These settings will poll every 20 seconds but also includes a delay as I was having failures without it.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PAPERLESS_CONSUMER_ENABLE_INOTIFY=false\nPAPERLESS_CONSUMER_POLLING=20\nPAPERLESS_CONSUMER_POLLING_DELAY=3\nPAPERLESS_CONSUMER_POLLING_RETRY_COUNT=3<\/code><\/pre>\n\n\n\n<p>Next, edit the YAML file in the same directory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vim \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/docker-compose.yml<\/code><\/pre>\n\n\n\n<p>Most of this can be left, but under the webserver block (this is the main container which relies on the other dependencies, I have highlighted in bold the changes you want to pay attention to. I changed the port I will access the web server on to 8009, turned off the restart (more on that later) and mapped the internal volumes to the correct folders on my NAS:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>webserver:\n    image: ghcr.io\/paperless-ngx\/paperless-ngx:latest\n    restart: <strong>no<\/strong>\n    depends_on:\n      - db\n      - broker\n      - gotenberg\n      - tika\n    ports:\n      - \"<strong><mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">8009<\/mark><\/strong>:8000\"\n    volumes:\n      - <strong>\/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared\/paperless<\/mark>\/data<\/strong>:\/usr\/src\/paperless\/data\n      - <strong>\/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared\/paperless<\/mark>\/media<\/strong>:\/usr\/src\/paperless\/media\n      - <strong>\/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared\/paperless<\/mark>\/export<\/strong>:\/usr\/src\/paperless\/export\n      - <strong>\/mnt\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared\/paperless<\/mark>\/consume<\/strong>:\/usr\/src\/paperless\/consume<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Configure Paperless as a service to start on reboot<\/h3>\n\n\n\n<p>I had a lot of issues with rebooting, mainly with finding a configuration which would get the NAS mounted and then correctly start the containers.<\/p>\n\n\n\n<p>Create a new service file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo vim \/etc\/systemd\/system\/paperless.service<\/code><\/pre>\n\n\n\n<p>Add the following which waits for the NAS to finish mounting and includes an addtional sleep of 10 seconds before starting the paperless service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=Paperless-ngx container stack\nRequires=snap.docker.dockerd.service network-online.target mnt-<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared<\/mark>.mount\nAfter=snap.docker.dockerd.service network-online.target mnt-<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">shared<\/mark>.mount\n\n&#91;Service]\nType=oneshot\nWorkingDirectory=\/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\nExecStart=\/bin\/bash -c 'sleep 10 &amp;&amp; \/snap\/bin\/docker compose up -d'\nExecStop=\/snap\/bin\/docker compose down\nRemainAfterExit=true\nTimeoutStartSec=0\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p>Enable the service:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl daemon-reexec\nsudo systemctl daemon-reload\nsudo systemctl enable paperless.service<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Backup \/ Export files<\/h3>\n\n\n\n<p>Ideally, if you had a second NAS available, you could map your export folder there in docker-compose.yml, but keeping things simple, we&#8217;ll set up an export to run every night at 02:30 which keeps the last 3 files. You&#8217;ll need to do something to copy the exported file elsewhere, or a failure of you NAS will lose your data AND your backup!<\/p>\n\n\n\n<p>Create a script file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>vim \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/export.sh<\/code><\/pre>\n\n\n\n<p>Save this in the file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\n\nset -e\n\n# Define paths\nPROJECT_DIR=\"\/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\"\nEXPORT_DIR=\"$PROJECT_DIR\/export\"\nTIMESTAMP=$(date +\"%Y-%m-%d_%H-%M-%S\")\nEXPORT_FILE=\"$EXPORT_DIR\/paperless_export_$TIMESTAMP.zip\"\n\n# Ensure export dir exists\nmkdir -p \"$EXPORT_DIR\"\n\n# Go to Paperless project directory\ncd \"$PROJECT_DIR\" || exit 1\n\n# Run the export\ndocker compose exec -T webserver document_exporter -z - &gt; \"$EXPORT_FILE\"\n\n# Keep only the 3 most recent backups\ncd \"$EXPORT_DIR\"\nls -1t paperless_export_*.zip | tail -n +4 | xargs -r rm --<\/code><\/pre>\n\n\n\n<p>Make it executable:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/export.sh<\/code><\/pre>\n\n\n\n<p>Edit \/ create cron:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>crontab -e<\/code><\/pre>\n\n\n\n<p>Add this to start at 02:30 and output to a log file:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>30 2 * * * \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/export.sh &gt;&gt; \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\/export.log 2&gt;&amp;1<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">The Finish Line<\/h2>\n\n\n\n<p>You should be ready to go!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/home\/<mark style=\"background-color:#ffe2c7\" class=\"has-inline-color\">david<\/mark>\/paperless-ngx\ndocker compose up -d<\/code><\/pre>\n\n\n\n<p>It takes a little while for the webserver to start (check its status with <code>docker ps<\/code>).<\/p>\n\n\n\n<p>Once the status for the for the main paperless container is up and healthy, navigate to your install (for me http:\/\/192.168.2.1:8009)and follow the instructions to set up your first user and get started with Paperless-NGX.<\/p>\n\n\n\n<p>Please feel free to ask any questions in the comments, and I hope this helps!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>What is Paperless NGX? Paperless is a mature document management system with lots of features. NGX is the latest fork. I highly recommend it, but I won&#8217;t go into the features here &#8211; just search YouTube for some videos and there is lots of information. So, if there is lots of information, why would you [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[31],"tags":[28,30,29],"class_list":["post-137","post","type-post","status-publish","format-standard","hentry","category-guides","tag-docker","tag-hyper-v","tag-paperless-ngx"],"_links":{"self":[{"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/posts\/137","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=137"}],"version-history":[{"count":12,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/posts\/137\/revisions"}],"predecessor-version":[{"id":153,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=\/wp\/v2\/posts\/137\/revisions\/153"}],"wp:attachment":[{"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=137"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=137"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/david.lidstone.me\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=137"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}