PDF documents are among the most sensitive data holdings in companies and public authorities: employment contracts, invoices, tax records and internal reports must never, for data-protection and GDPR reasons, be sent unchecked to external cloud services. Web-based tools such as Smallpdf or Adobe Cloud do solve everyday tasks like merging or redacting pages, but in a business environment they regularly violate compliance rules.
Stirling PDF resolves this dilemma as a fully open-source, locally hosted complete solution. The application runs as a lightweight Docker container, processes all documents locally in the RAM of your own server and leaves no persistent file remnants on external platforms after processing is finished.
Stirling PDF is deployed on a server with Ubuntu 24.04 LTS (Noble Numbat): with an integrated OCR engine for German and English text, a hardened Nginx reverse proxy with TLS termination, and reproducible update and backup routines for production.
π‘ Note: A working online demo of the software is provided by the project at pdf.ipx64.xyz. For your own infrastructure we use the official container distribution.
Architecture and components
At its core Stirling PDF is based on a Java application (Spring Boot) and under the hood orchestrates specialized Linux command-line tools. Instead of reinventing the wheel, the container wraps proven open-source libraries:
- Apache PDFBox and OpenPDF: Handle low-level PDF operations (split pages, merge, rotate, edit metadata).
- LibreOffice (headless): Converts office formats (DOCX, ODT, PPTX, XLSX) losslessly into standards-compliant PDF files.
- Tesseract OCR and OCRmyPDF: Search scanned documents, recognize text layers and embed searchable OCR text into existing PDFs.
- Ghostscript and QPDF: Optimize file sizes, repair damaged PDF structures and decrypt password-protected files.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STIRLING PDF ARCHITECTURE MODEL β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β Web browser (client) β
β β β
β βΌ HTTPS (port 443) β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Nginx reverse proxy (TLS termination and SSL) β β
β β client_max_body_size 100M; proxy_read_timeout 300s; β β
β ββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HTTP (127.0.0.1:8080) β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Docker container (stirlingtools/stirling-pdf) β β
β β β β
β β βββββββββββββββββ βββββββββββββββββ βββββββββββββ β β
β β β Spring Boot β β LibreOffice β β Tesseract β β β
β β β Web / Auth β β Converter β β OCR β β β
β β βββββββββ¬ββββββββ βββββββββ¬ββββββββ βββββββ¬ββββββ β β
β ββββββββββββΌββββββββββββββββββΌββββββββββββββββΌβββββββββ β
β βΌ βΌ βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Host filesystem / persistent volumes (/opt/...) β β
β β ./configs ./customFiles ./trainingData β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The split between reverse proxy on the host and the Stirling container on 127.0.0.1 ensures that unencrypted HTTP traffic never reaches the external network unprotected.
System requirements and resource planning
Before we start the deployment, we check the hardware resources of the target server. PDF operations with LibreOffice and optical character recognition (OCR) are CPU- and memory-intensive.
Minimum vs. recommended system resources:
| Resource | Minimum operation (1β2 users) | Recommended for production and OCR |
|---|---|---|
| CPU | 2 vCPUs | 4 vCPUs (speeds up parallel OCR jobs) |
| Memory | 2 GB RAM | 4 to 8 GB RAM (Tesseract + JVM) |
| Disk | 10 GB free space | 25 GB SSD/NVMe (for temp files and Docker images) |
| Operating system | Ubuntu 24.04 LTS (x86_64 or ARM64) | Ubuntu 24.04 LTS (x86_64 or ARM64) |
β οΈ Watch the OOM killer: When Stirling PDF OCRs a 200-page PDF or renders complex Word documents through LibreOffice, memory use can briefly rise by 1.5 to 2 GB. On systems with only 2 GB RAM an active swap file is mandatory, otherwise the Linux kernel terminates the container with an out-of-memory signal (
SIGKILL).
Step 1: Update the system and install Docker
Ubuntu 24.04 still ships the old docker.io package and the outdated Python version of Docker Compose in its default package sources. For stable and secure operation we use the official repository from Docker Inc. with the modern Compose v2 extension (docker-compose-plugin).
Refresh package sources and install base tools:
sudo apt update && sudo apt upgrade -y
sudo apt install -y ca-certificates curl gnupg lsb-release
Add the official Docker GPG key and configure the repository:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
Install Docker Engine and Docker Compose v2:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Validate service status and autostart:
sudo systemctl enable --now docker
docker compose version
# Expected output:
Docker Compose version v2.29.x (or newer)
Step 2: Create the directory structure and permissions
We place the container configuration in a structured way under /opt/stirling-pdf. All persistent data, language models and custom settings remain isolated in dedicated subdirectories:
sudo mkdir -p /opt/stirling-pdf/{configs,customFiles,trainingData,pipeline}
cd /opt/stirling-pdf
Meaning of the directories:
configs/: Holds the application configuration (settings.yml), databases for user accounts and API keys.customFiles/: Allows storing your own logos, CSS customizations or watermark templates.trainingData/: Storage for Tesseract OCR language packs (.traineddata), e.g. German (deu) and English (eng).pipeline/: Optional folder for automated processing pipelines (e.g. automatic OCR for dropped scans).
Step 3: Download OCR language files
By default the Stirling PDF base image ships English language data. To process German scans without errors (including umlauts Γ€, ΓΆ, ΓΌ and Γ), we download the optimized Fast Tessdata model directly into our volume:
cd /opt/stirling-pdf/trainingData
sudo curl -fsSL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/deu.traineddata
sudo curl -fsSL -O https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata
cd /opt/stirling-pdf
β Tip on model quality: For archival requirements with maximum character precision you can alternatively use the
tessdata_bestrepository. Thetessdata_fastmodel, however, needs only about a quarter of the RAM and, with standard fonts, delivers nearly identical recognition rates at significantly higher throughput.
Step 4: Production-ready Docker Compose configuration
Stirling PDF offers two primary image variants:
stirlingtools/stirling-pdf:latest: Standard image with the full feature set (LibreOffice, OCR, PDFBox).stirlingtools/stirling-pdf:latest-fat: Already contains all worldwide Tesseract language files (requires over 4 GB of disk space on pull).
We use the standard image and bind the required language models in a targeted way through the mounted trainingData volume.
Create the file /opt/stirling-pdf/docker-compose.yml:
sudo nano /opt/stirling-pdf/docker-compose.yml
Insert the following configuration:
services:
stirling-pdf:
image: stirlingtools/stirling-pdf:latest
container_name: stirling-pdf
restart: unless-stopped
ports:
# Bind to localhost only β protection via Nginx reverse proxy
- "127.0.0.1:8080:8080"
volumes:
- ./trainingData:/usr/share/tessdata:rw
- ./configs:/configs:rw
- ./customFiles:/customFiles:rw
- ./pipeline:/pipeline:rw
environment:
# User interface and localization
- SYSTEM_DEFAULTLOCALE=en-US
- UI_APPNAME=AdminDocs PDF Studio
# Security and authentication options
- DOCKER_ENABLE_SECURITY=true
- SECURITY_ENABLE_LOGIN=true
- SECURITY_CSRF_DISABLED=false
# Performance and resource settings
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
- SYSTEM_MAXFILESIZE=100
deploy:
resources:
limits:
cpus: '3.00'
memory: 3500M
reservations:
memory: 1024M
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Explanation of the central parameters:
127.0.0.1:8080:8080: Binds the service exclusively to the local loopback adapter. External requests on port 8080 are dropped immediately and must pass the TLS-secured proxy.DOCKER_ENABLE_SECURITY=trueandSECURITY_ENABLE_LOGIN=true: Enables Stirling PDF's built-in role and user system.SYSTEM_MAXFILESIZE=100: Sets the upload limit for input files to 100 megabytes.limits.memory: 3500M: Protects the host from uncontrolled memory hunger on faulty or manipulated PDF files.
Step 5: Start the container and create the initial administrator
We start the container in the background:
cd /opt/stirling-pdf
sudo docker compose up -d
Follow the start process in the logs:
sudo docker compose logs -f stirling-pdf
On first start with the security option enabled (DOCKER_ENABLE_SECURITY=true), Stirling PDF automatically creates a temporary administrator account. Watch the logs for the following string:
#################################################################
# Generated Admin Username: admin #
# Generated Admin Password: <generated-one-time-password> #
#################################################################
Copy this initial password to the clipboard. We use it in the web interface to set a permanent password immediately.
π‘ Note on the initial password: If the password was missed in the logs, it can always be read in
/opt/stirling-pdf/configs/settings.ymlor regenerated by stopping the container and resetting the configuration.
Step 6: Hardening with Nginx reverse proxy and Let's Encrypt
For productive use on the intranet or the internet we attach Stirling PDF through an Nginx reverse proxy with a secure TLS certificate.
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginx
Create the Nginx configuration for the domain:
Replace pdf.example.com with your desired domain name:
sudo nano /etc/nginx/sites-available/stirling-pdf.conf
Insert the following server block:
server {
listen 80;
listen [::]:80;
server_name pdf.example.com;
# Generous limits for large document uploads
client_max_body_size 100M;
client_body_timeout 300s;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# Headers to forward client information
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support for progress displays
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts for compute-heavy OCR and conversion jobs
proxy_connect_timeout 90s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
}
Why client_max_body_size and timeouts are indispensable:
By default Nginx limits requests to 1 megabyte (client_max_body_size 1M). Every upload of a scanned PDF or a large handbook would otherwise be rejected with 413 Request Entity Too Large. The extended timeouts (proxy_read_timeout 300s) prevent connection drops (504 Gateway Timeout) when Tesseract analyzes hundreds of pages.
Enable the site and check the configuration:
sudo ln -s /etc/nginx/sites-available/stirling-pdf.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Generate a TLS certificate with Let's Encrypt:
sudo certbot --nginx -d pdf.example.com
Certbot automatically extends the Nginx configuration with TLSv1.2/TLSv1.3 directives and sets up automatic certificate renewal via a systemd timer.
Step 7: Configure the firewall with UFW
We make sure that only the secure web ports are reachable from outside:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
# Check the status:
sudo ufw status verbose
Internal port 8080 is isolated thanks to our Compose binding to 127.0.0.1 and does not appear in the external firewall rules at all.
Step 8: Verification and system check
After setup we check the operating state of all components on the Linux console.
1. Check port binding:
ss -tulpn | grep 8080
# Expected result (must show 127.0.0.1, never 0.0.0.0):
tcp LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=...))
2. Check the Docker healthcheck:
docker compose -f /opt/stirling-pdf/docker-compose.yml ps
# Expected result (status "healthy"):
NAME IMAGE COMMAND SERVICE STATUS
stirling-pdf stirlingtools/stirling-pdf:latest "tini -- ./entrypoinβ¦" stirling-pdf Up 5 minutes (healthy)
3. Query API status and Tesseract support:
curl -s http://127.0.0.1:8080/api/v1/info/status
The JSON response must report status UP and list the installed OCR languages.
Maintenance, updates and backup strategy
Professional operation requires reliable maintenance routines for container updates and configuration backups.
Performing container updates
Because Stirling PDF is actively developed, monthly updates should be planned:
cd /opt/stirling-pdf
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
sudo docker image prune -f
Creating a consistent configuration backup
Because the actual PDF files are deleted from temporary storage immediately after processing, for disaster recovery we only need to back up configuration data and user settings:
#!/bin/bash
# Backup script for Stirling PDF
BACKUP_DIR="/var/backups/stirling-pdf"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"
tar -czf "$BACKUP_DIR/stirling_config_$DATE.tar.gz" -C /opt/stirling-pdf configs customFiles trainingData
# Clean backups older than 30 days
find "$BACKUP_DIR" -type f -name "*.tar.gz" -mtime +30 -delete
This script can be stored as a daily cron job under /etc/cron.daily/backup-stirling-pdf.
Typical errors and troubleshooting
In practice, specific error states can appear during PDF processing:
1. Error 413: Request Entity Too Large
- Symptom: When uploading a PDF larger than 1 MB, the browser aborts with HTTP 413.
- Cause: The Nginx directive
client_max_body_sizeis missing or set too low. - Fix: Set
client_max_body_size 100M;in/etc/nginx/sites-available/stirling-pdf.confand runsudo systemctl reload nginx.
2. Tesseract does not recognize German umlauts
- Symptom: OCR texts contain hieroglyphs or spaces instead of
Γ€,ΓΆ,ΓΌ. - Cause: The language file
deu.traineddatawas not found in the mounted directory or has wrong file permissions. - Diagnosis and fix:
``bash ls -lh /opt/stirling-pdf/trainingData/ sudo chmod 644 /opt/stirling-pdf/trainingData/*.traineddata sudo docker compose restart stirling-pdf ``
3. Container is terminated unexpectedly during OCR processing
- Symptom:
docker compose psshowsExited (137). - Cause: Exit code 137 means
SIGKILLby the Linux out-of-memory (OOM) killer. - Diagnosis: Check
dmesg -T | grep -i oom. - Fix: Raise the memory limit in
docker-compose.yml(memory: 4096M) or set up a 4 GB swap file on the host:
``bash sudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab ``
Command Reference (Cheatsheet)
The most important operational commands for management and fault diagnosis at a glance:
| Task / command | Function / explanation |
|---|---|
docker compose -f /opt/stirling-pdf/docker-compose.yml up -d |
Starts the Stirling PDF stack in the background |
docker compose -f /opt/stirling-pdf/docker-compose.yml logs -f |
Shows real-time logs of the application and the web server |
docker compose -f /opt/stirling-pdf/docker-compose.yml restart |
Restarts the container (e.g. after a configuration change) |
docker stats stirling-pdf |
Monitors live CPU and memory use of the container |
nginx -t && systemctl reload nginx |
Validates the Nginx reverse-proxy configuration and reloads rules |
certbot renew --dry-run |
Simulates automatic renewal of the TLS certificate |
ss -tulpn | grep 8080 |
Checks whether port 8080 is listening exclusively on 127.0.0.1 |
Further Resources
The following documentation provides deeper information on the technologies and components used:
| Resource / documentation | Description |
|---|---|
| Official Stirling PDF documentation | Detailed documentation of all parameters, API endpoints and pipeline functions |
| Stirling-Tools GitHub repository | Official source code, issue tracker and release notes |
| Docker Engine documentation for Ubuntu | Best practices for installing and maintaining Docker on Ubuntu Linux |
| Tesseract OCR project | Background on the text-recognition engine and language models |
| AdminDocs: Docker and Docker Compose guide | Fundamental container orchestration and hardening on Linux |
| Portainer CE web GUI | Graphical management of Docker containers and stacks on Ubuntu 24.04 LTS |
Conclusion
With the setup built here, Ubuntu 24.04 LTS has a full, GDPR-compliant document hub ready. Through strict encapsulation in the Docker container, decoupling via a hardened Nginx reverse proxy and binding to the local loopback adapter, the security architecture stays transparent and low-maintenance.
π‘ Practical tip for administrators: In Stirling PDF under the admin settings, set user quotas and session timeouts. When employees edit confidential documents in the browser, temporary browser caches should be invalidated automatically after the session ends. Also enable the option for automatic metadata cleanup so that hidden author and printer information is reliably removed before PDF documents are passed on.
That completely removes the need for unsecured third-party web services on the company network, while data sovereignty remains 100 percent on your own server infrastructure.