Introduction
Everyone eventually hits the same wall: you need to merge three PDFs into one, or shrink a 40 MB scan down to something you can actually email, or strip a password off a file you forgot the password to. The usual move is to open a browser tab, find one of the free "PDF tools" websites, and upload your document to a server you know nothing about. If that file has your bank details, a signed contract, or your kid's passport scan in it, that's a rough trade to make for convenience.
Stirling-PDF fixes that by putting all of those tools on hardware you own. It's a self-hosted web app that does merging, splitting, compressing, converting, OCR, watermarking, and around 50 other PDF operations, running entirely inside your own network. Nothing leaves your server.
This guide walks you through installing Stirling-PDF inside an LXC container on Proxmox VE. You don't need any Docker experience or Linux administration background — every command gets explained as we go, and by the end you'll have a working PDF toolkit sitting at its own address on your home network.
What You Will Learn
- What Stirling-PDF actually does and who it's useful for
- Why an LXC container is a good fit for this kind of app on Proxmox VE
- How to create a Debian 13 LXC container with Docker support enabled
- How to install Docker and deploy Stirling-PDF with Docker Compose
- How to log in for the first time and change the default password
- The errors people run into most often, and what actually causes them
- A handful of habits that will save you trouble down the line
What Is This Feature?
Stirling-PDF is an open-source project that bundles a large collection of PDF utilities behind a single web interface. Instead of installing separate desktop programs for each job, you get one dashboard with buttons for merge, split, rotate, compress, add a watermark, remove pages, convert to and from Word/Excel/images, redact sensitive text, sign documents, and run OCR (Optical Character Recognition — the process of turning a scanned image of text into text you can actually search and copy) on scanned pages.
It runs as a Docker container, which means the application and everything it depends on — Java, LibreOffice for conversions, Tesseract for OCR — ships as one package you don't have to assemble by hand. You point Docker at an image, it starts a container, and you open a browser to a port on that machine.
We're going to run that Docker container inside an LXC container on Proxmox rather than inside a full virtual machine. LXC (Linux Containers) is a lighter form of virtualization than a VM — it shares the host's Linux kernel instead of emulating its own virtual hardware, so it boots in a second or two and uses only the RAM the application inside it actually needs. For something like Stirling-PDF, which mostly sits idle waiting for you to upload a file, that's a much better fit than dedicating a whole VM to it.
Why Would You Use It?
The honest answer is convenience with a privacy upside. If you already run a homelab with a Proxmox VE box humming away in a closet, spinning up one more lightweight container costs you almost nothing — maybe 1-2 GB of RAM and a few gigabytes of disk. In exchange you stop uploading tax documents, ID scans, contracts, or anything else to random "PDF converter" sites that may or may not delete your file afterward.
There's also a practical angle: online PDF tools tend to cap file sizes, nag you about premium tiers, or throttle how many operations you can run per day. Your own instance doesn't care. Merge fifty files if you want. Run OCR on a 200-page scanned book. Nobody's metering you.
Small businesses get a second benefit here — if you're processing client documents, invoices, or anything under an NDA, keeping that processing in-house instead of on a third-party site is often the difference between "compliant" and "not." I wouldn't call Stirling-PDF a compliance product on its own, but not sending documents to an unknown vendor is a reasonable piece of that puzzle.
Prerequisites
Before you start, make sure you have the following:
- A working Proxmox VE installation — this guide was written against Proxmox VE 9.2, though the steps work the same on any 8.x or 9.x release
- At least 4 GB of free RAM on the host and roughly 8 GB of free disk space for the container
- A Debian 13 (or 12) LXC template downloaded on your Proxmox node — grab it from Datacenter > your node > CT Templates > Templates if you don't already have one
- Basic comfort typing commands into a terminal — you'll be copying and pasting a handful of them, and each one is explained
- Root or an account with sufficient privileges on the Proxmox web interface
You don't need to know anything about Docker going in. We'll install it from scratch.
Step-by-Step Tutorial
Step 1: Create the LXC container
From the Proxmox web interface, click Create CT in the top right corner. Give it a hostname like stirling-pdf and set a root password (or add an SSH key if you prefer that).
On the Template screen, pick the Debian 13 template you downloaded. On Disk, 8 GB is plenty for the standard image — bump that to 15-20 GB if you plan to use the "fat" image tag that bundles extra fonts and conversion tools, since LibreOffice and Tesseract data add up.
For CPU, 2 cores is a comfortable default. For memory, set 2048 MB (2 GB) — Stirling-PDF is not a heavy app at idle, but PDF conversion and OCR jobs are CPU and memory-hungry for the seconds they run, so don't starve it.
On the Network tab, either leave DHCP on or assign a static IP if that's how you manage your homelab. You'll need to know this container's IP address later, so make a note of whatever you set.
Leave everything else at its defaults and finish the wizard, but don't start the container yet — there's one setting to change first.
Step 2: Enable nesting so Docker can run inside the container
By default, Proxmox LXC containers are unprivileged, which is a good security default but blocks Docker from working properly — Docker needs to create its own nested namespaces and control groups, and an unprivileged container without the right features denies that.
In the Proxmox web UI, select your new container, go to Resources > wait, that's not it — go to Options > Features, click Edit, and tick both nesting and keyctl. Save it.
If you'd rather do this from the shell on the Proxmox host, run:
pct set 105 --features nesting=1,keyctl=1
Swap 105 for whatever VMID Proxmox assigned your container (you'll see it in the container list).
Now start the container and open its console, either through the web UI's >_ Console button or by SSHing to its IP address.
Step 3: Update the container and install Docker
Inside the container, update the package list and installed packages first:
apt update && apt upgrade -y
Then install a couple of tools Docker's installer script needs:
apt install -y curl ca-certificates
Docker publishes an official convenience script that handles the repository setup and installation for you. Run it with:
curl -fsSL https://get.docker.com | sh
This takes about a minute on a fresh Debian 13 container. Once it finishes, start Docker and set it to launch on boot:
systemctl enable --now docker
Confirm it's actually running:
docker ps
An empty table with column headers (CONTAINER ID, IMAGE, COMMAND, and so on) means Docker is alive and just doesn't have anything running yet. That's exactly what you want to see at this point.
Step 4: Write the Docker Compose file for Stirling-PDF
Create a folder to keep things organized, and a compose file inside it:
mkdir -p /opt/stirling-pdf && cd /opt/stirling-pdf
nano compose.yaml
Paste in the following:
services:
stirling-pdf:
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
container_name: stirling-pdf
ports:
- '8080:8080'
volumes:
- ./data/tessdata:/usr/share/tessdata
- ./data/configs:/configs
- ./data/logs:/logs
environment:
- SECURITY_ENABLELOGIN=true
- SYSTEM_DEFAULTLOCALE=en-GB
restart: unless-stopped
A quick word on what each part does. The image line tells Docker exactly which build to pull — Stirling-PDF publishes it from its own registry rather than Docker Hub, which is why the address looks a bit longer than usual. The ports line maps port 8080 inside the container to port 8080 on the LXC container itself, which is what you'll browse to. The volumes lines keep your configuration, OCR language data, and logs outside the container so they survive an update or a rebuild. SECURITY_ENABLELOGIN=true keeps the built-in login screen active — you want this on unless you're absolutely certain nobody untrusted can reach this container's network.
Save the file (Ctrl+O, Enter, then Ctrl+X in nano) and start the stack:
docker compose up -d
Docker will pull the image — a few hundred megabytes, so give it a minute or two depending on your connection — and then start the container in the background.
Step 5: Log in and change the default password
Open a browser on any machine on your network and go to http://<container-ip>:8080, using the IP address you noted back in Step 1.
You'll land on a login screen. Stirling-PDF ships with a default account of admin as the username and stirling as the password. Log in with that, then immediately go to your account settings and set a real password. Leaving the default in place on a container that's reachable from your whole network is asking for trouble, even on a home LAN.
Once you're past that, you're looking at the full tool dashboard — merge, split, compress, convert, and everything else, organized into categories down the side.
Commands Explained
Here's a rundown of every command used above and what it's actually doing:
| Command | What it does |
|---|---|
pct set 105 --features nesting=1,keyctl=1 | Enables the two container features Docker needs to run inside an unprivileged LXC container, without which it can't create its internal namespaces |
apt update && apt upgrade -y | Refreshes the list of available packages and installs any pending updates before you add new software |
curl -fsSL https://get.docker.com | sh | Downloads Docker's official install script and runs it, which sets up Docker's repository and installs Docker Engine plus the Compose plugin |
systemctl enable --now docker | Starts the Docker service immediately and configures it to start automatically every time the container boots |
docker ps | Lists currently running containers — useful for confirming Docker itself is alive, or later, that Stirling-PDF is actually running |
docker compose up -d | Reads the compose.yaml file in the current folder and starts everything it defines, with -d meaning it runs in the background instead of tying up your terminal |
docker compose logs -f | Streams the live output of the containers defined in your compose file, which is the first place to look when something isn't working |
Common Errors
A few things trip people up consistently with this setup:
"Cannot connect to the Docker daemon at unix:///var/run/docker.sock." This almost always means Docker isn't running, or you skipped the nesting feature in Step 2. Run systemctl status docker to check whether the service is even active. If it refuses to start at all, double-check that nesting and keyctl are both ticked on the container.
The page at port 8080 just times out. Nine times out of ten this is a firewall or IP mismatch, not Stirling-PDF itself. Confirm the container's actual IP with ip a inside the container, and make sure you're not blocked by a Proxmox firewall rule on that VMID.
Login rejected with the default credentials. Username and password are case-sensitive — it's admin and stirling, all lowercase. If you've already changed it and forgot the new one, you'll need to reset it through the container's admin tools or wipe the ./data/configs folder and restart, which resets to defaults (and loses any saved settings).
Container gets OOM-killed during a big OCR job. Scanning a large multi-page document with OCR enabled can spike memory usage well past what a lightly-provisioned container has available. If you see the container restart mid-job, bump the LXC's memory allocation up from 2 GB to 4 GB and try again.
"No space left on device" while pulling the image. The "fat" image tag, which bundles extra fonts and LibreOffice, is considerably larger than the standard tag. If you picked that one on an 8 GB disk, you'll run out of room fast. Either resize the container's disk or switch back to the plain latest tag.
Troubleshooting
When something's not working, start with the logs rather than guessing:
docker compose logs -f stirling-pdf
This shows you exactly what the application is doing (or failing to do) in real time. Most startup failures print a clear error in the first few lines — a missing environment variable, a permissions problem on a mounted folder, or a port already in use by something else on the container.
If the container keeps restarting in a loop, check docker ps and look at the STATUS column. "Restarting (1) X seconds ago" repeating over and over means it's crashing on startup; the logs command above will tell you why nine times out of ten.
For permission errors on the mounted volumes (you'll see something like "Permission denied" writing to /configs), it's usually because the folders were created with root ownership from inside the container but Docker's internal user doesn't match. Run chown -R 1000:1000 ./data from the /opt/stirling-pdf folder and restart the stack.
And if the whole LXC container refuses to start after you changed the nesting feature, check the Proxmox task log (the little clock icon at the bottom of the web UI) — it'll usually point to a cgroup or apparmor conflict, which is rare but does happen on older kernel versions.
Best Practices
Change the default admin password the moment you log in for the first time — don't leave it for "later," because later is when you forget.
Take a snapshot of the LXC container in Proxmox before you update the Stirling-PDF image or change compose settings. Snapshots are nearly instant on ZFS-backed storage and give you an easy rollback if an update breaks something.
Keep your /opt/stirling-pdf/data folder in mind when you're planning backups. Since it lives outside the Docker container, a regular vzdump backup of the whole LXC container picks it up automatically, but it's worth confirming that's actually happening rather than assuming.
If you want to reach Stirling-PDF from outside your home network, don't just forward port 8080 straight to the router. Put it behind a reverse proxy with a proper TLS certificate instead, and keep the login screen enabled either way.
Update the image every couple of months with docker compose pull && docker compose up -d. Stirling-PDF is actively developed and new tools get added fairly often.
Frequently Asked Questions
Is Stirling-PDF actually free? Yes, it's open source under the AGPL license and free to self-host. There's no paid tier gating features away from you.
Can I run this in a VM instead of an LXC container? Sure, if you'd rather. It'll work the same way once Docker is installed, though you'll use more RAM and disk for the same job compared to a container.
Does it need internet access once it's running? No. All the PDF processing happens locally on your server. It only needs a network connection to pull the initial image and any updates.
Can more than one person use it at the same time? Yes. It's a web app, so anyone with the URL and login credentials can use it from their own browser, and multiple people can run jobs concurrently.
Is my data private? As private as your own server is. Files are processed and stored on the machine you control, not uploaded to a third party — though you're still responsible for locking down access to that machine.
What's the difference between the "latest" and "latest-fat" image tags? The standard tag covers the core PDF tools. The fat tag adds LibreOffice and extra fonts for broader document conversion support, at the cost of a noticeably larger image and disk footprint.
Conclusion
You've now got a private PDF toolkit running on hardware you control, reachable from anywhere on your network, with none of your files ever touching a third-party server. The whole setup — container, Docker, and Stirling-PDF itself — takes maybe fifteen minutes once you've done it once.
From here, it's worth putting this container behind whatever reverse proxy you're already running, so you get a proper domain name and HTTPS instead of typing an IP and port every time. And if fifteen minutes felt like a lot for one app, that's exactly the kind of task Proxmox's Community Scripts are built to shortcut — but doing it by hand once, like you just did, means you actually understand what's running under the hood.