Setting up a Document Store under Docker

Setting up a Document Store under Docker

Installing and Securing Paperless-ngx

Mac Mini · Docker · Nginx Proxy Manager · PostgreSQL · Mailrelay


Paperless-ngx is my self-hosted document management system. It provides a searchable archive for scanned documents, PDFs, correspondence, bills and other important paperwork.

This installation runs on a Mac Mini using Docker, with PostgreSQL providing the database, Redis handling background tasks, Apache Tika and Gotenberg providing document conversion, and Nginx Proxy Manager providing HTTPS access.

There is one particularly important difference from some of my other self-hosted services:

Paperless contains sensitive personal documents, so it should not be exposed to the public Internet.

The installation therefore uses Nginx Proxy Manager to restrict access to the home network while still allowing the Paperless container to be monitored internally by Uptime Kuma.


Architecture

The important point is that only Nginx Proxy Manager provides the externally reachable HTTP/HTTPS entry point. PostgreSQL, Redis, Tika, Gotenberg and Paperless itself are not directly exposed to the Internet.


Key lessons from this installation

💡
The important lessons

Several details in this installation are the result of problems encountered while setting up the service.

  • The old ghcr.io/paperless-ngx/tika image is no longer publicly accessible. Use docker.io/apache/tika instead.
  • All Paperless data and media are stored on /Volumes/Media-Home/paperless, rather than consuming space on the Mac’s system SSD.
  • Paperless is deliberately restricted to the home network because it contains sensitive personal documents.
  • Nginx Proxy Manager needs 192.168.65.0/24 in its allow list on macOS because Docker Desktop uses this network as its VM gateway.
  • The consume directory is the simplest way to ingest documents, so I’ve added it to the Finder sidebar.
  • PostgreSQL is persistent and separately backed up.
  • The processed document collection in media is also backed up because those documents are irreplaceable.

Before you start

You will need:

  • Docker Desktop running
  • The proxy-net Docker network already created
  • The Mailrelay container running
  • Ports 80 and 443 forwarded on your router
  • A Cloudflare A record for docs.plainshawk.co.uk
  • The Cloudflare record set to DNS only — grey cloud
Security first

Paperless is different from a public-facing blog.

Your Paperless library may contain:

  • bank statements
  • tax documents
  • insurance documents
  • medical correspondence
  • utility bills
  • contracts
  • identification documents

For that reason, this installation deliberately restricts access to the home network.


Installation

Stage 1 — Create the directory structure

The Docker Compose configuration will live on the Mac’s internal storage, but the Paperless data itself belongs on the large external SSD.

Create the directories:

mkdir -p ~/docker/paperless

mkdir -p /Volumes/Media-Home/paperless/consume
mkdir -p /Volumes/Media-Home/paperless/media
mkdir -p /Volumes/Media-Home/paperless/export
mkdir -p /Volumes/Media-Home/paperless/data

cd ~/docker/paperless

The resulting structure is:

Directory

Purpose

consume

Documents waiting to be imported

media

Processed documents and thumbnails

export

Paperless export files

data

Search index and application data

The PostgreSQL database will be stored separately in:

~/docker/paperless/postgres

Stage 2 — Generate a secret key

Paperless needs a secret key for cryptographic operations.

Generate one with:

openssl rand -base64 48

Copy the resulting value somewhere temporarily.

You will need it in the Compose file in the next step.

Keep the secret key private

Treat the Paperless secret key in the same way as a password. Don’t publish it in your blog, commit it to a public Git repository or share it unnecessarily.

Stage 3 — Create the Compose file

Create the Docker Compose configuration:

nano docker-compose.yml

Paste the following:

services:
  paperless-db:
    image: postgres:16
    container_name: paperless-db
    restart: unless-stopped
    environment:
      - POSTGRES_DB=paperless
      - POSTGRES_USER=paperless
      - POSTGRES_PASSWORD=choose_a_db_password
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  paperless-redis:
    image: redis:alpine
    container_name: paperless-redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s

  paperless-gotenberg:
    image: docker.io/gotenberg/gotenberg:8
    container_name: paperless-gotenberg
    restart: unless-stopped
    command:
      - gotenberg
      - --chromium-disable-javascript=true
      - --chromium-allow-list=file:///tmp/.*

  paperless-tika:
    image: docker.io/apache/tika:latest
    container_name: paperless-tika
    restart: unless-stopped

  paperless:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    container_name: paperless
    restart: unless-stopped
    depends_on:
      paperless-db:
        condition: service_healthy
      paperless-redis:
        condition: service_healthy
      paperless-gotenberg:
        condition: service_started
      paperless-tika:
        condition: service_started
    environment:
      # Database
      - PAPERLESS_DBHOST=paperless-db
      - PAPERLESS_DBNAME=paperless
      - PAPERLESS_DBUSER=paperless
      - PAPERLESS_DBPASS=choose_a_db_password

      # Redis
      - PAPERLESS_REDIS=redis://paperless-redis:6379

      # Tika and Gotenberg
      - PAPERLESS_TIKA_ENABLED=1
      - PAPERLESS_TIKA_ENDPOINT=http://paperless-tika:9998
      - PAPERLESS_TIKA_GOTENBERG_ENDPOINT=http://paperless-gotenberg:3000

      # Application
      - PAPERLESS_URL=https://docs.plainshawk.co.uk
      - PAPERLESS_SECRET_KEY=your_generated_secret_key_here
      - PAPERLESS_TIME_ZONE=Europe/London
      - PAPERLESS_OCR_LANGUAGE=eng
      - PAPERLESS_ADMIN_USER=admin
      - PAPERLESS_ADMIN_PASSWORD=choose_an_admin_password
      - PAPERLESS_ADMIN_MAIL=gavin@plainshawk.co.uk

      # File locations
      - PAPERLESS_CONSUMPTION_DIR=/consume
      - PAPERLESS_MEDIA_ROOT=/media
      - PAPERLESS_DATA_DIR=/data
      - PAPERLESS_EXPORT_DIR=/export

      # OCR behaviour
      - PAPERLESS_OCR_MODE=skip
      - PAPERLESS_OCR_SKIP_ARCHIVE_FILE=with_text

      # Performance — conservative for Mac Mini
      - PAPERLESS_TASK_WORKERS=2
      - PAPERLESS_THREADS_PER_WORKER=1

      # Email via mailrelay
      - PAPERLESS_EMAIL_HOST=mailrelay
      - PAPERLESS_EMAIL_PORT=25
      - PAPERLESS_EMAIL_USE_TLS=false
      - PAPERLESS_EMAIL_FROM=paperless@plainshawk.co.uk

    volumes:
      - /Volumes/Media-Home/paperless/consume:/consume
      - /Volumes/Media-Home/paperless/media:/media
      - /Volumes/Media-Home/paperless/data:/data
      - /Volumes/Media-Home/paperless/export:/export

    networks:
      - default
      - proxy-net

networks:
  default:
  proxy-net:
    external: true
Change the passwords and secret key

Before starting the containers, replace:

  • choose_a_db_password
  • choose_an_admin_password
  • your_generated_secret_key_here

with your own values.

The PostgreSQL password must match in both the PostgreSQL container and the Paperless configuration.


Why these containers are needed

Paperless-ngx is more than a single container.

Container

Function

paperless

Main Paperless application

paperless-db

PostgreSQL database

paperless-redis

Background task queue

paperless-tika

Text and document extraction

paperless-gotenberg

Office/document conversion

This is why the initial startup can take several minutes.


A note about Apache Tika

One of the lessons from this installation is particularly worth documenting.

The old image:

ghcr.io/paperless-ngx/tika

is no longer publicly accessible.

The Compose configuration therefore uses:

image: docker.io/apache/tika:latest
💡
If Tika won’t start

Don’t substitute the old Paperless Tika image from an older installation guide.

Use the Apache Tika image shown in the Compose file above.


Connect Paperless to the Internet — safely

Stage 4 — Add the DNS record

In Cloudflare, create:

Type

Name

Content

Proxy

A

docs

Your public IP

Grey cloud — DNS only

This creates:

docs.plainshawk.co.uk
💡
DNS does not mean public access

Although the hostname is publicly resolvable, the Paperless web application itself will be protected by the Nginx Proxy Manager access rules configured later.

A person outside your home network should receive a 403 Forbidden response.

That is intentional.


Stage 5 — Start Paperless-ngx

Start the complete stack:

docker compose up -d

Then follow the Paperless logs:

docker compose logs -f paperless

The first startup can take 2–3 minutes.

Wait until Paperless reports that it is ready before continuing.

Press Ctrl+C once it is running.

Then check all five containers:

docker compose ps

You should see:

  • paperless
  • paperless-db
  • paperless-redis
  • paperless-gotenberg
  • paperless-tika

running.


Configure Nginx Proxy Manager

Stage 6 — Add the Proxy Host

Log into Nginx Proxy Manager.

Go to:

Proxy Hosts → Add Proxy Host

Details

Field

Value

Domain

docs.plainshawk.co.uk

Scheme

http

Forward Hostname

paperless

Forward Port

8000

Cache Assets

Off

Block Common Exploits

On

Websockets Support

On

SSL

On the SSL tab:

  • Request a new Let’s Encrypt certificate
  • Force SSL: On
  • HTTP/2 Support: On

Restrict Paperless to the home network

This is the most important part of the Nginx configuration.

Open the Advanced tab and add:

# Home network restriction
# 192.168.178.0/24 = your home LAN
# 192.168.65.0/24  = Docker Desktop VM gateway (required on macOS)
# 172.16.0.0/12    = Docker internal networks

allow 192.168.178.0/24;
allow 192.168.65.0/24;
allow 172.16.0.0/12;
allow 10.0.0.0/8;
allow 127.0.0.1;
deny all;

# Upload size for large PDFs and scanned documents
client_max_body_size 100M;

proxy_read_timeout 300s;
proxy_send_timeout 300s;
proxy_buffering off;

# Block scanners — Paperless uses Python, no PHP
location ~ \.(php|php\d|phtml|asp|aspx|cgi)$ {
    return 444;
}

location ~ /(wp-admin|wp-login|wp-content|wp-includes|xmlrpc\.php) {
    return 444;
}

location ~ /\.(env|git|svn|aws|ssh|npmrc) {
    return 444;
}

location ~ /(phpmyadmin|pma|adminer|mysql)/ {
    return 444;
}

location ~ /(shell|cmd|exec|system|passthru|eval)/ {
    return 444;
}

Save the Proxy Host.

Why is 192.168.65.0/24allowed?

Docker Desktop on macOS runs containers inside a lightweight Linux VM.

As a result, traffic reaching Nginx Proxy Manager from the Docker environment can appear to originate from the Docker Desktop VM network rather than your normal home LAN.

For this installation, 192.168.65.0/24 therefore needs to be explicitly allowed.

💡
Check your actual home subnet

The example above assumes:

192.168.178.0/24

If your router uses a different subnet, change this rule accordingly.

On the Mac, check your current address with:

ipconfig getifaddr en0

For example, if the Mac reports:

192.168.178.42

then 192.168.178.0/24 is the appropriate LAN rule.


First login

Stage 7 — Log in and verify

Open:

https://docs.plainshawk.co.uk

Log in using the values specified by:

  • PAPERLESS_ADMIN_USER
  • PAPERLESS_ADMIN_PASSWORD

You should see the Paperless dashboard.

Go to:

Administration → Tasks

The system should be idle and healthy.


Configure Paperless

Stage 8 — Configure settings

Open Settings in the Paperless interface.

General

Configure:

Setting

Value

Application title

Plainshawk Documents

Default language

English

Basic metrics

On

Basic metrics are useful for monitoring the installation.

Document Processing

Set:

Setting

Value

Default OCR language

English

Archive file override

Skip

The archive setting corresponds with:

PAPERLESS_OCR_SKIP_ARCHIVE_FILE=with_text

Notifications

Configure your email address for system notifications.

These will be sent through the internal Mailrelay container.


Organise your document library

Stage 9 — Create correspondents, tags and document types

I recommend setting up the basic classification structure before importing your existing document collection.

This allows Paperless’s automatic matching to start working immediately.

Correspondents

Go to:

Correspondents → Add

Create correspondents for organisations such as:

Correspondent

HMRC

Your bank(s)

Utility providers

Insurance companies

NHS / GP

Mortgage lender / landlord

Document Types

Go to:

Document Types → Add

Suggested types:

Document Type

Invoice

Statement

Letter

Contract

Receipt

Tax Return

Policy

Tags

Go to:

Tags → Add

Suggested tags:

Tag

Important

Action Required

Archive

Personal

Financial

Medical

Property

Automatic matching

Paperless can automatically assign correspondents and tags based on document contents.

For example:

Correspondent

Algorithm

Pattern

HMRC

Any word

HMRC, HM Revenue, tax

Your bank

Any word

Your bank name

Start with simple rules and refine them as you learn how your documents are being classified.


Test document ingestion

Stage 10 — Test the consume folder

The easiest way to add documents to Paperless is to place them in the consume directory.

For a quick test, copy a PDF into it:

cp any_document.pdf /Volumes/Media-Home/paperless/consume/

Paperless should automatically detect the file and process it.

You can watch the logs with:

docker compose logs -f paperless | grep -i consume

Within approximately 30–60 seconds, the document should appear in the Paperless interface with OCR text extracted.

💡
The consume folder is the easy way in

Rather than opening Paperless every time you want to add a document, you can simply drag files into the consume folder.

Paperless takes care of:

  1. Detecting the document
  2. Processing it
  3. Performing OCR where necessary
  4. Extracting metadata
  5. Storing the processed document
  6. Making it searchable

Stage 11 — Add the consume folder to Finder

Make the consume folder easily accessible from anywhere on the Mac.

In Finder:

  1. Press Cmd+Shift+G
  2. Enter:
/Volumes/Media-Home/paperless/consume
  1. Press Return
  2. Drag the resulting folder into the Finder sidebar

You can now drag documents directly into the Paperless consume folder.


Backups

Stage 12 — Add Paperless to the backup script

Paperless contains information that would be extremely difficult to replace, so backups are particularly important.

Open your backup script:

nano ~/docker/backups/backup.sh

Confirm that the PostgreSQL database backup is present:

# Paperless-NGX database
docker exec paperless-db pg_dump \
  -U paperless \
  paperless > $BACKUP_DIR/paperless_$DATE.sql

echo "Paperless backup done"

Back up the processed documents

The database alone isn’t enough.

The actual processed documents are stored in the media directory, so back those up too:

# Paperless media (processed documents)
rsync -a \
  /Volumes/Media-Home/paperless/media/ \
  $BACKUP_DIR/paperless-media-$DATE/

echo "Paperless media backup done"
💡
The media directory is the important one

Your Paperless database contains metadata, indexing information and document relationships.

The media directory contains the processed documents themselves.

Back up both.

As the document collection grows, the media backup will also grow. Once the library becomes substantial, consider including the Paperless media directory in your Time Machine backup scope rather than copying a complete additional version every night.


Automatic startup and monitoring

Stage 13 — Add Paperless to the startup script

Open the startup script:

nano ~/docker/start-all.sh

Confirm that Paperless is started before Nginx Proxy Manager:

echo "Starting Paperless-NGX..."
cd /Users/gavin/docker/paperless && docker compose up -d

Paperless needs to be available on proxy-net before Nginx Proxy Manager attempts to connect to it.


Stage 14 — Add Paperless to Uptime Kuma

Open:

https://status.plainshawk.co.uk

Add a new monitor:

Field

Value

Name

Paperless-NGX

Monitor Type

HTTP(s)

URL

http://paperless:8000

Heartbeat Interval

60s

Retries

3

💡
Why use the Docker hostname?

Paperless is deliberately restricted by the Nginx Proxy Manager access list.

Uptime Kuma can bypass that restriction by connecting directly to:

http://paperless:8000

from inside the Docker environment.

This is intentional and means Uptime Kuma can monitor Paperless without making the Paperless web interface publicly accessible.


Quick reference

Location

Purpose

https://docs.plainshawk.co.uk

Paperless web interface

/Volumes/Media-Home/paperless/consume

Drop documents here

/Volumes/Media-Home/paperless/media

Processed documents

/Volumes/Media-Home/paperless/data

Application/search data

/Volumes/Media-Home/paperless/export

Paperless exports

~/docker/paperless/postgres

PostgreSQL database

Updating Paperless

Paperless-ngx is containerised, so updating it is straightforward.

First change to the application directory:

cd ~/docker/paperless

Pull the latest images:

docker compose pull

Then recreate the containers:

docker compose down
docker compose up -d

Finally, monitor the startup:

docker compose logs -f paperless

Paperless performs database migrations automatically when required.

Before updating

Because Paperless contains important documents, I recommend taking a current database and media backup before performing an update.

Also check the Paperless-ngx release notes before moving to a new version, particularly if you are jumping across major versions.


Troubleshooting

403 Forbidden from outside the home network

This is working as intended.

The Nginx Proxy Manager access list only permits the configured home network and the Docker networks.

An external connection should be rejected.


403 Forbidden from inside the home network

Your home LAN may use a different subnet from the example:

192.168.178.0/24

Check your Mac’s address:

ipconfig getifaddr en0

Then adjust the Nginx Proxy Manager allow rule to match your actual LAN.


Documents are stuck in the consume folder

Check the Paperless logs:

docker compose logs --tail=30 paperless | grep -i error

Also check that the Paperless container can see the consume directory and that Docker Desktop has access to /Volumes/Media-Home.


Tika isn’t processing Office documents

First check that the container is running:

docker compose ps paperless-tika

It should be listed as running.

Also confirm that the Compose file contains:

image: docker.io/apache/tika:latest

rather than the obsolete Paperless-specific Tika image.


Search returns no results

The search index can be rebuilt with:

docker exec paperless python3 manage.py document_index reindex

Depending on the size of the library, rebuilding the index can take some time.


OCR quality is poor

OCR quality depends heavily on the quality of the original scan.

For best results:

  • Scan at 300 DPI or higher
  • Use clean, high-contrast documents
  • Avoid heavily compressed images
  • Keep pages straight when scanning

Paperless can perform very well with clean source documents, but it can’t recover information that isn’t present clearly in the scan.


502 Bad Gateway on first access

Paperless takes longer to start than some of the simpler services running on the Mac Mini.

On the first startup, allow approximately three minutes before assuming something is wrong.

Check:

docker compose ps

and:

docker compose logs --tail=50 paperless

If Paperless eventually reports that it is ready, retry the web interface.


Final security and backup checklist

💡
Paperless-ngx installation checklist
  • Docker Desktop is running
  • proxy-net exists
  • PostgreSQL is running and healthy
  • Redis is running and healthy
  • Tika is running
  • Gotenberg is running
  • Paperless is running
  • Paperless data is stored on /Volumes/Media-Home
  • The PostgreSQL database has a persistent volume
  • The Paperless media directory is backed up
  • The database is included in the nightly backup
  • docs.plainshawk.co.uk resolves correctly
  • HTTPS works through Nginx Proxy Manager
  • Access from the home network works
  • Access from outside the home network is rejected
  • The consume folder works
  • OCR has been tested
  • Email notifications have been tested
  • Uptime Kuma is monitoring Paperless
  • Paperless is included in the startup script

Conclusion

Paperless-ngx is now running as a persistent Docker service on the Mac Mini, with PostgreSQL providing the database and the document library stored safely on the external SSD.

The installation is deliberately more restrictive than a typical self-hosted web application. Paperless isn’t intended to be a public service: Nginx Proxy Manager provides HTTPS access while its access list limits the application to the home network.

The combination of persistent storage, database backups, media backups, automatic startup and Uptime Kuma monitoring means that Paperless is not just running — it is integrated into the wider Plainshawk home server environment.

And, perhaps most importantly, adding a document to the system is now as simple as dragging it into the Paperless consume folder in Finder.