Introduction
Every household and small office ends up with the same pile: tax forms, warranty receipts, insurance papers, the occasional letter from the city about a parking permit. Most of it gets scanned once, shoved into a folder called "Documents2024_final_FINAL," and never found again.
Paperless-ngx fixes that. It's a free, self-hosted document management system that takes a photo or scan of a document, reads the text on it with OCR, and lets you search for it later by typing a word that appears anywhere on the page. Run it on your own Proxmox VE server and you own your data completely — no subscription, no cloud upload, no company deciding your tax documents aren't worth keeping anymore.
This guide walks you through installing Paperless-ngx inside an LXC container on Proxmox VE, using Docker to run the actual application. If you've never touched Docker or LXC containers before, don't worry — every command below is explained as we go.
What You Will Learn
- What Paperless-ngx actually does and why it's different from "just scanning to a folder"
- How to create a Debian LXC container on Proxmox VE that's ready to run Docker
- How to install Docker Engine inside that container
- How to download and configure the official Paperless-ngx Docker Compose files
- How to create your admin account and log in for the first time
- Common errors people hit during setup, and how to fix each one
- A few habits that will save you a headache six months from now
What Is This Feature?
Paperless-ngx is an open-source project (the "ngx" stands for "next generation," since it's a community-maintained fork of an older project called Paperless). At its core it does three things: it watches a folder for new scans, it runs OCR (Optical Character Recognition, which is just software that reads text out of an image) on anything that shows up, and it stores the result in a searchable database along with the original file.
Once a document is in Paperless-ngx, you can tag it, assign it to a "correspondent" (who sent it to you), sort it into document types, and search the full text later. Scan a phone bill and six months from now you can type "internet" into the search bar and it'll turn up, even though you never typed that word into a filename.
We're going to run it inside an LXC container rather than a full virtual machine. An LXC container is a lightweight way to run an isolated Linux environment on Proxmox — it shares the host's kernel instead of emulating its own hardware, so it starts in a couple of seconds and uses a fraction of the RAM a VM would need for the same job. Inside that container, we'll use Docker, a tool that packages an application together with everything it needs to run, so you don't have to manually install Python, a database, a search index, and a dozen other dependencies by hand.
Why Would You Use It?
If you've got a drawer full of paper, or a Downloads folder with 400 PDFs named "scan1.pdf" through "scan400.pdf," this solves that problem properly. A few reasons people set this up specifically on Proxmox rather than paying for a cloud service:
- Privacy. Tax returns, medical letters, and bank statements never leave your network.
- No recurring cost. Cloud document services often charge monthly. Paperless-ngx is free forever.
- It fits naturally next to your other self-hosted apps. If you're already running Proxmox for a homelab, this is one more lightweight container alongside the rest.
- Full-text search that actually works. You're not relying on how carefully you named a file two years ago.
Honestly, if you only scan two or three documents a year, this might be overkill and a simple scanned-PDF folder would do fine. Where Paperless-ngx earns its keep is once you're dealing with a steady stream of paper — freelancers with receipts, families with school and medical paperwork, anyone running a small business.
Prerequisites
Before you start, make sure you have:
- A working Proxmox VE host (this guide was written against Proxmox VE 8.x and 9.x — the steps are identical on both)
- At least 8 GB of free space on the storage you'll use for the container's root disk, more if you plan to archive a lot of documents
- A container ID that isn't already in use (this guide uses
205— swap in whatever's free on your system) - A Debian 12 LXC template downloaded, or the ability to download one through the Proxmox web interface
- Root or sudo access to the Proxmox shell
- A rough idea of the static IP you want to give the container, if you don't want to rely on DHCP
You don't need any Docker experience. You don't need to know Python. You do need about twenty-five minutes and a working scanner or phone camera to test with afterward.
Step-by-Step Tutorial
Step 1: Download a Debian 12 template (if you don't have one)
Log in to the Proxmox shell (either through the web UI's >_ Shell button on your node, or over SSH) and check what's available:
pveam update
pveam available | grep debian-12
Pick the version that comes back and download it:
pveam download local debian-12-standard_12.7-1_amd64.tar.zst
Step 2: Create the LXC container
Docker needs a couple of extra permissions inside the container that a default LXC container doesn't have. The important bit here is --features nesting=1,keyctl=1:
pct create 205 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
--hostname paperless \
--cores 2 \
--memory 2048 \
--swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--features nesting=1,keyctl=1
A quick word on the resource numbers: 2 CPU cores and 2 GB of RAM is comfortable for a household's worth of documents. OCR is the part that actually chews through CPU, and it only runs briefly each time you add a document, so you're not paying that cost constantly. If you're archiving thousands of pages at once, bump the memory to 4 GB temporarily and drop it back down after.
The 8 GB root disk is enough for the OS and Docker images, but not much document storage on top of that. We'll talk about growing it later — don't stress over getting the number exactly right now.
Step 3: Start the container and log in
pct start 205
pct enter 205
pct enter drops you straight into a root shell inside the container, which is the fastest way to work through the next few steps.
Step 4: Update the container and install Docker
First, update the package list and pull in the tools we need to add Docker's own repository:
apt update && apt full-upgrade -y
apt install -y ca-certificates curl gnupg
Now add Docker's official GPG key and repository. This is the same method Docker documents for any Debian system, container or not:
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
> /etc/apt/sources.list.d/docker.list
Install Docker Engine and the Compose plugin:
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker
Check it's actually running before moving on:
docker ps
An empty table with column headers (no error) means Docker is alive and you're good to continue.
Step 5: Download the Paperless-ngx Compose files
The Paperless-ngx project publishes ready-made Docker Compose files for a few different database backends. We'll use the SQLite version, which needs no separate database container and is genuinely enough for most personal and small business use:
mkdir -p /opt/paperless-ngx
cd /opt/paperless-ngx
curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.sqlite.yml -o docker-compose.yml
curl -L https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env -o docker-compose.env
mkdir -p consume export
The consume folder is the one that matters day to day — drop a scanned PDF or photo in there and Paperless-ngx picks it up automatically within about a minute.
Step 6: Set a real secret key and timezone
Open the env file:
nano docker-compose.env
Find the line PAPERLESS_SECRET_KEY=change-me and replace it with something random. You can generate one with:
openssl rand -base64 48
Paste the output in place of change-me. While you're in there, uncomment and set your timezone, for example:
PAPERLESS_TIME_ZONE=America/New_York
If most of your documents are in a language other than English, also uncomment PAPERLESS_OCR_LANGUAGE and set the appropriate three-letter code (deu for German, fra for French, and so on). Save and exit — in nano that's Ctrl+O, Enter, then Ctrl+X.
Step 7: Bring the stack up
docker compose up -d
The first run pulls three images (the webserver, the broker, and a small helper), so give it a few minutes on a typical home internet connection — it's usually somewhere around 800 MB total. Watch the logs while it settles:
docker compose logs -f webserver
You're looking for a line that mentions Gunicorn listening on port 8000. Press Ctrl+C to stop watching the logs once you see it (the containers keep running in the background either way).
Step 8: Create your admin account
docker compose run --rm webserver createsuperuser
You'll be asked for a username, email, and password. This is a one-time setup command, not something that runs alongside the app — it starts a temporary container, runs the command, and exits.
Step 9: Give the container a fixed IP (optional but recommended)
If you'd rather not depend on DHCP handing out a different address after a reboot, back on the Proxmox host set a static one:
pct set 205 --net0 name=eth0,bridge=vmbr0,ip=192.168.1.60/24,gw=192.168.1.1
pct reboot 205
Swap in an address and gateway that fit your own network, obviously.
Step 10: Log in
Open a browser and go to http://192.168.1.60:8000 (or whatever IP your container ended up with). You should see the Paperless-ngx login screen. Sign in with the superuser account you just created, and try dragging a PDF onto the dashboard, or drop a file into the /opt/paperless-ngx/consume folder from the Proxmox shell and watch it appear in the web interface a minute later.
Commands Explained
| Command | What It Does |
|---|---|
pveam download | Downloads a container template (a pre-built base OS image) to Proxmox local storage |
pct create | Creates a new LXC container from a template with the settings you specify |
--features nesting=1,keyctl=1 | Grants the container the extra kernel capabilities Docker needs to run containers inside a container |
pct enter | Opens a root shell directly inside a running container, no SSH required |
docker compose up -d | Reads docker-compose.yml and starts every service it defines, in the background (-d for "detached") |
docker compose logs -f | Streams live log output from the running containers so you can see what's happening |
docker compose run --rm | Starts a one-off container to run a single command, then deletes it (--rm) once it's done |
pct set | Changes a running or stopped container's configuration, like its network settings |
Common Errors
"Cannot connect to the Docker daemon" usually means the Docker service never started, or it crashed. Run systemctl status docker to see what happened, and systemctl enable --now docker to bring it up again.
Container creation fails with something about cgroups or AppArmor when you try to install Docker inside the LXC container. This almost always traces back to a missing nesting=1 feature flag. Stop the container, run pct set 205 --features nesting=1,keyctl=1, then start it again.
The web page just spins or shows a 502/Bad Gateway right after docker compose up -d. The webserver container starts before the broker is fully ready sometimes. Give it another sixty seconds and refresh — if it's still broken after that, check docker compose logs broker.
Documents dropped in the consume folder never show up. Nine times out of ten this is a file permission issue. The container process expects to own files as UID 1000. From the LXC shell, run chown -R 1000:1000 /opt/paperless-ngx/consume and try again.
"You are using an insecure secret key" warning in the logs. This means you skipped Step 6, or saved the file before actually changing the value. Go back, set a real PAPERLESS_SECRET_KEY, then run docker compose down && docker compose up -d to apply it.
Troubleshooting
When something doesn't work, resist the urge to immediately tear it all down and start over — the logs almost always tell you what's wrong. Start with:
docker compose ps
This shows every service and whether it's "running" or "restarting." A service stuck in a restart loop is your first clue about where to look.
docker compose logs webserver --tail 100
Swap webserver for broker to check the other service. Most errors that show up here are plain English — a missing environment variable, a permissions error, a port already in use.
If the container itself seems unresponsive from the Proxmox side, check whether it's actually running at the host level:
pct status 205
And if you genuinely need to start fresh without losing your documents, stopping the stack doesn't touch your data — it lives in Docker named volumes and the local consume/export folders, not inside the containers themselves:
docker compose down
docker compose up -d
Best Practices
A few things worth doing once the basic setup is working:
- Grow the root disk before you need to, not after. Eight gigabytes fills up faster than you'd expect once you're archiving PDFs and photos. Resize it with
pct resize 205 rootfs +12Gwhenever you're getting close. - Back up the whole container, not just the files. Because everything lives inside one LXC container, a regular
vzdumpbackup job in Proxmox captures your documents, your database, and your configuration together in one shot. - Don't expose port 8000 straight to the internet. If you want remote access, put a reverse proxy with HTTPS in front of it and set
PAPERLESS_URLaccordingly. - Turn on autostart so the container comes back up automatically after a host reboot, rather than you noticing three days later that it's been down.
- Every few months, run
docker compose pull && docker compose up -dto pick up security fixes. Paperless-ngx ships updates fairly often.
One thing I'd genuinely skip: don't bother switching to PostgreSQL unless you're archiving tens of thousands of documents or running this for an actual office. SQLite handles a normal household's paperwork without any fuss, and it's one less container to maintain.
Frequently Asked Questions
Do I need a scanner to use this?
No. A phone camera works fine for most documents. Apps like Adobe Scan or even your phone's built-in document scanner produce clean enough images for OCR.
Does OCR need a GPU?
No, Paperless-ngx uses Tesseract OCR, which runs entirely on the CPU. A couple of cores is plenty.
Can I run this in a VM instead of an LXC container?
Yes, it works identically in a VM. LXC is just lighter on resources for something that's mostly idle between scans.
What happens if I lose the secret key?
Nothing catastrophic, but every logged-in session gets invalidated. Just don't change it after documents are already stored unless you have a reason to.
Is Paperless-ngx really free?
Yes, it's open source under the GPLv3 license. No paid tier, no feature paywall.
Can multiple people in my house use it?
Yes, create additional user accounts from the Django admin or the Users section once you're logged in, and you can restrict what each one sees.
Conclusion
You've now got a document archive that lives on hardware you control, searches the actual text of every scan you feed it, and didn't cost a monthly fee to set up. The LXC container it's running in barely touches your Proxmox host's resources, which means there's no real downside to leaving it running permanently.
From here, the natural next step is building the habit — scan things as they arrive instead of letting them pile up, and let the consume folder do the sorting. Give it a month of real use and the search bar alone will make you wonder why you ever kept paper filing systems in the first place.