Skip to Content

Production Guide: Deploy Stirling PDF with Docker Compose + Caddy + OCR on Ubuntu

Self-host a private PDF toolbox with HTTPS, local storage, OCR, backup routines, and operational guardrails.

PDF workflows are still full of sensitive operational data: contracts, onboarding packets, invoices, signed statements, export reports, and forms that should not be uploaded to random online converters. Stirling PDF gives teams a private browser-based toolbox for splitting, merging, compressing, rotating, OCR processing, redacting, and converting documents without sending files to a third-party service. In this guide, we will deploy Stirling PDF on Ubuntu with Docker Compose, publish it through Caddy with automatic HTTPS, keep the container bound to localhost, and add practical routines for verification, backups, updates, and safe day-to-day operations.

The target audience is a small business, internal IT team, or operations group that wants a dependable PDF utility behind a company domain. The pattern is intentionally simple: one application container, a reverse proxy, local persistent storage, and clear operational checks. You can place it behind an identity-aware proxy or VPN later, but this baseline keeps the service maintainable for teams that already run other Docker Compose applications.

Architecture and flow overview

The public request flow is straightforward. Users open https://pdf.example.com. Caddy terminates TLS, applies basic security headers, and proxies traffic to 127.0.0.1:8087. Docker maps that host-only port to Stirling PDF inside the container on port 8080. The application stores configuration, logs, temporary working data, and OCR training files under /opt/stirling-pdf. That directory becomes the single backup and restore boundary.

Keeping the container port on 127.0.0.1 is important. It means the application is reachable through Caddy, not directly from the internet. Caddy owns certificates and HTTP exposure, while Docker focuses on running the application. If you later move the service behind SSO, a private network, or a Web Application Firewall, the same separation of responsibilities still works.

Prerequisites

  • Ubuntu 22.04 or 24.04 with a non-root sudo user.
  • A DNS record such as pdf.example.com pointing to the server.
  • Ports 80 and 443 reachable from the internet for Caddy certificate issuance.
  • At least 2 CPU cores and 4 GB RAM for comfortable OCR and conversion tasks.
  • A backup target outside the server, such as S3, a NAS, or a managed backup system.

Step-by-step deployment

1) Install Docker, Compose, Caddy, and firewall basics

Install the container runtime and Caddy from trusted package repositories, then allow only SSH, HTTP, and HTTPS through UFW. If your organization already manages firewalls centrally, mirror the same policy there.

sudo apt update
sudo apt install -y ca-certificates curl gnupg ufw
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
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin caddy
sudo systemctl enable --now docker caddy
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

If the copy button is unavailable in your browser, select the command text manually and copy it.

2) Create the application layout and local secrets

Place everything under /opt/stirling-pdf so operators know exactly what to back up. The session secret file is created with restricted permissions. Even when a secret is only used locally, treating it as sensitive prevents accidental disclosure in support bundles and screenshots.

sudo mkdir -p /opt/stirling-pdf/{data,configs,logs,trainingData,backups}
sudo chown -R 1000:1000 /opt/stirling-pdf
cd /opt/stirling-pdf
openssl rand -hex 32 | sudo tee /opt/stirling-pdf/.session-secret >/dev/null
sudo chmod 600 /opt/stirling-pdf/.session-secret

If the copy button is unavailable in your browser, select the command text manually and copy it.

3) Define the Docker Compose stack

The Compose file uses the official Stirling PDF image, binds the web port only to localhost, enables login-oriented security settings, and mounts persistent folders. Review image tags during your change process; pinning to a tested version is preferable for regulated environments, while latest can be acceptable for a lab or lightweight internal utility.

cd /opt/stirling-pdf
sudo tee compose.yml >/dev/null <<'YAML'
services:
  stirling-pdf:
    image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
    container_name: stirling-pdf
    restart: unless-stopped
    ports:
      - "127.0.0.1:8087:8080"
    environment:
      DOCKER_ENABLE_SECURITY: "true"
      SECURITY_ENABLE_LOGIN: "true"
      SYSTEM_DEFAULTLOCALE: "en-US"
      UI_APPNAME: "Company PDF Tools"
      INSTALL_BOOK_AND_ADVANCED_HTML_OPS: "true"
    volumes:
      - ./trainingData:/usr/share/tessdata
      - ./configs:/configs
      - ./logs:/logs
      - ./data:/usr/share/stirling-pdf/data
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080"]
      interval: 30s
      timeout: 10s
      retries: 5
YAML
sudo docker compose pull
sudo docker compose up -d

If the copy button is unavailable in your browser, select the command text manually and copy it.

4) Configure Caddy for HTTPS

Replace pdf.example.com with your real host name before applying the Caddy file. The reverse proxy points to the host-local Docker port, so the Compose port publishing and Caddy upstream must match exactly.

sudo tee /etc/caddy/Caddyfile.d/stirling-pdf.caddy >/dev/null <<'CADDY'
pdf.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8087
    header {
        X-Content-Type-Options nosniff
        X-Frame-Options DENY
        Referrer-Policy no-referrer
    }
}
CADDY
sudo caddy fmt --overwrite /etc/caddy/Caddyfile.d/stirling-pdf.caddy
sudo sh -c 'cat /etc/caddy/Caddyfile.d/*.caddy > /etc/caddy/Caddyfile'
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

If the copy button is unavailable in your browser, select the command text manually and copy it.

Configuration and secrets handling best practices

Start with authentication enabled, then decide whether Stirling PDF should be exposed to the public internet, restricted by IP, protected behind a VPN, or placed behind an identity provider. For many companies, the safest default is to make it available only to staff through SSO or a private network. PDF tools often process confidential documents, so the access model matters as much as the container itself.

Avoid pasting production credentials into Compose files when you integrate SMTP, SSO, or external storage. Use an environment file with restrictive permissions, a secret manager, or the orchestration platform's native secret mechanism. Keep logs long enough for troubleshooting but short enough to avoid retaining document names or user activity longer than necessary. If your workflow involves regulated documents, document retention rules before inviting users.

Verification checklist

After the first start, verify the container state, application logs, local response, and public HTTPS endpoint. These checks catch the most common problems: a failed image pull, a port mismatch, a missing DNS record, or a Caddy certificate issue.

cd /opt/stirling-pdf
sudo docker compose ps
sudo docker compose logs --tail=80 stirling-pdf
curl -I http://127.0.0.1:8087
curl -I https://pdf.example.com

If the copy button is unavailable in your browser, select the command text manually and copy it.

  • The Compose service should show as running or healthy.
  • The local curl request should return an HTTP response from Stirling PDF.
  • The public curl request should return through Caddy with a valid certificate.
  • Upload a harmless sample PDF and test merge, split, and OCR workflows.
  • Confirm users cannot reach the app on the server's public IP and port 8087.

Backups and recovery routine

Stirling PDF is mostly stateless, but its configuration, OCR training data, and persistent application data still need protection. The following lightweight backup routine creates a compressed archive and keeps two weeks of local copies. In production, ship the archive to an external location after creation; local-only backups do not protect against disk failure or accidental server deletion.

sudo tee /usr/local/sbin/backup-stirling-pdf >/dev/null <<'BASH'
#!/usr/bin/env bash
set -euo pipefail
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
cd /opt
sudo tar -czf "/opt/stirling-pdf/backups/stirling-pdf-${STAMP}.tar.gz"   stirling-pdf/configs stirling-pdf/data stirling-pdf/trainingData stirling-pdf/compose.yml
find /opt/stirling-pdf/backups -type f -name 'stirling-pdf-*.tar.gz' -mtime +14 -delete
BASH
sudo chmod +x /usr/local/sbin/backup-stirling-pdf
sudo /usr/local/sbin/backup-stirling-pdf
sudo crontab -l 2>/dev/null | { cat; echo "15 2 * * * /usr/local/sbin/backup-stirling-pdf"; } | sudo crontab -

If the copy button is unavailable in your browser, select the command text manually and copy it.

Test recovery on a second VM before you rely on this service. A realistic drill is simple: install Docker and Caddy, copy the latest archive, extract it under /opt, run docker compose up -d, and validate the same merge, split, and OCR tasks. Document the restore time so business owners understand what to expect during an outage.

Updates and maintenance

Schedule updates during a low-traffic window. PDF conversion libraries and OCR components change over time, so verify common workflows after every upgrade instead of assuming the web page loading is enough. For stricter environments, pin the image tag in Compose, test the new tag on staging, then promote it to production.

cd /opt/stirling-pdf
sudo docker compose pull
sudo docker compose up -d
sudo docker image prune -f
sudo docker compose logs --tail=60 stirling-pdf

If the copy button is unavailable in your browser, select the command text manually and copy it.

Common issues and fixes

Caddy returns 502 Bad Gateway

Check that the container is running and that Compose publishes 127.0.0.1:8087:8080. A common mistake is using expose only, which makes the port available to other containers but not to the host-level Caddy process.

Large PDFs fail or conversions are slow

Increase CPU and memory before changing application settings. OCR and office document conversions can be resource intensive. If many users process large documents, move the service to a larger VM and define clear usage expectations.

OCR output is poor

Confirm the correct language training data is installed and mounted. Poor scans, rotated pages, and low-resolution images also reduce OCR quality. Test with known-good samples before troubleshooting the server.

Users bypass HTTPS

Do not publish the Docker port on all interfaces. Keep the application bound to localhost and let Caddy handle public traffic. If you need internal-only access, restrict DNS and firewall rules further.

FAQ

Is Stirling PDF safe for confidential documents?

Self-hosting reduces third-party exposure, but safety depends on access controls, patching, logging, backups, and user behavior. Treat it like any internal document-processing service.

Can I run this behind SSO?

Yes. Many teams put Caddy behind an identity-aware proxy or use a separate access gateway. Keep the app bound to localhost and add SSO at the edge.

Should I keep uploaded PDFs on disk?

Minimize retention. Use defaults that favor temporary processing, review application settings, and avoid retaining user documents unless the business has a clear reason.

How much RAM do I need?

For light internal use, 4 GB is a practical starting point. Increase memory if OCR, compression, or office conversions are slow under real workloads.

Can this replace Adobe Acrobat for every workflow?

No. It is excellent for common server-side PDF operations, but legal review, complex form workflows, and advanced editing may still require desktop tools.

How do I make it more private?

Restrict it to VPN users, add SSO, disable direct public access, keep logs minimal, and regularly test that Docker ports are not exposed externally.

Internal links

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Header image: Unsplash, no watermark.

Production Guide: Deploy NetBox with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
A practical runbook for running NetBox as a reliable source of truth with TLS, backups, secrets, and operational checks.