Tools: How to Install Paperless-ngx with Docker Compose on Ubuntu

Tools: How to Install Paperless-ngx with Docker Compose on Ubuntu

Introduction

Prerequisites

Step 1: Prepare the Directory Structure

Step 2: Create the docker-compose.yml File

Step 3: Start the Containers

Step 4: Create an Admin User

Step 5: Working with Documents

Uploading Files

Step 6: ⚠️ Domain Configuration (IMPORTANT)

Why is this necessary?

Common Issues

1. Can't log in

2. OCR not working

3. Slow processing

Best Practices

Conclusion πŸ“„ This article is a translation and adaptation of the original post published in Bulgarian at itpraktika.com. Managing documents in a digital environment can quickly become overwhelming β€” especially as the number of files (PDFs, DOC files, scanned images) grows. Paperless-ngx is a modern Document Management System (DMS) that offers automatic indexing, OCR (Optical Character Recognition), and a powerful search engine. In this guide, we'll walk through step by step how to install and configure Paperless-ngx using Docker Compose on Ubuntu, so it runs reliably and stably. Before we begin, make sure you have: πŸ’‘ If you don't have Docker installed yet, check the official Docker documentation or a guide for your specific distro. Create the necessary folders for storing data: Here's what each folder is for: Then paste in the following configuration: πŸ” Important: Change paperless_password_change_me and PAPERLESS_SECRET_KEY to strong, unique values before deploying. After a few seconds, the system will be accessible at: No default user is created automatically. You need to create one manually: You'll be prompted to enter: After that, you can log into the web interface. You have several options: All files placed in that folder will be processed automatically. If you plan to use a domain (e.g., behind Cloudflare or nginx), you must add the following environment variables: Without these settings, you will get a 403 CSRF verification failed error. This is a built-in security mechanism that requires the domain to be explicitly trusted. Once you've completed the setup, you'll quickly appreciate how much easier document management becomes. Paperless-ngx isn't just a file store β€” it's a tool that genuinely saves time when searching, sorting, and organizing information. A few long-term recommendations: Don't be afraid to experiment. Paperless-ngx has a lot of automation and customization options you can tune to your workflow. With the right setup and a small initial investment of time, you'll have a fast, reliable, and user-friendly document management system. πŸ“„ Original article in Bulgarian: Как Π΄Π° инсталирамС Paperless-ngx с Docker Compose β€” itpraktika.com Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ mkdir -p /data/paperless/{data,media,export,consume,db} mkdir -p /data/paperless/{data,media,export,consume,db} mkdir -p /data/paperless/{data,media,export,consume,db} nano -weight: 500;">docker-compose.yml nano -weight: 500;">docker-compose.yml nano -weight: 500;">docker-compose.yml version: "3.8" services: broker: image: redis:7 container_name: paperless-redis -weight: 500;">restart: unless-stopped command: redis-server --save "" --appendonly no db: image: postgres:15 container_name: paperless-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: paperless POSTGRES_USER: paperless POSTGRES_PASSWORD: paperless_password_change_me volumes: - /data/paperless/db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U paperless"] interval: 10s timeout: 5s retries: 5 webserver: image: ghcr.io/paperless-ngx/paperless-ngx:latest container_name: paperless -weight: 500;">restart: unless-stopped depends_on: db: condition: service_healthy broker: condition: service_started ports: - "8000:8000" environment: PAPERLESS_REDIS: redis://broker:6379 PAPERLESS_DBHOST: db PAPERLESS_DBNAME: paperless PAPERLESS_DBUSER: paperless PAPERLESS_DBPASS: paperless_password_change_me # Uncomment if using a domain: # PAPERLESS_URL: https://paperless.yourdomain.com # PAPERLESS_CSRF_TRUSTED_ORIGINS: https://paperless.yourdomain.com PAPERLESS_SECRET_KEY: "change_me_to_something_long_and_random" USERMAP_UID: 1000 USERMAP_GID: 1000 PAPERLESS_OCR_LANGUAGE: eng PAPERLESS_TIME_ZONE: Europe/London PAPERLESS_OCR_LANGUAGES: eng PAPERLESS_CONSUMER_POLLING: 60 PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}" volumes: - /data/paperless/data:/usr/src/paperless/data - /data/paperless/media:/usr/src/paperless/media - /data/paperless/export:/usr/src/paperless/export - /data/paperless/consume:/usr/src/paperless/consume version: "3.8" services: broker: image: redis:7 container_name: paperless-redis -weight: 500;">restart: unless-stopped command: redis-server --save "" --appendonly no db: image: postgres:15 container_name: paperless-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: paperless POSTGRES_USER: paperless POSTGRES_PASSWORD: paperless_password_change_me volumes: - /data/paperless/db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U paperless"] interval: 10s timeout: 5s retries: 5 webserver: image: ghcr.io/paperless-ngx/paperless-ngx:latest container_name: paperless -weight: 500;">restart: unless-stopped depends_on: db: condition: service_healthy broker: condition: service_started ports: - "8000:8000" environment: PAPERLESS_REDIS: redis://broker:6379 PAPERLESS_DBHOST: db PAPERLESS_DBNAME: paperless PAPERLESS_DBUSER: paperless PAPERLESS_DBPASS: paperless_password_change_me # Uncomment if using a domain: # PAPERLESS_URL: https://paperless.yourdomain.com # PAPERLESS_CSRF_TRUSTED_ORIGINS: https://paperless.yourdomain.com PAPERLESS_SECRET_KEY: "change_me_to_something_long_and_random" USERMAP_UID: 1000 USERMAP_GID: 1000 PAPERLESS_OCR_LANGUAGE: eng PAPERLESS_TIME_ZONE: Europe/London PAPERLESS_OCR_LANGUAGES: eng PAPERLESS_CONSUMER_POLLING: 60 PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}" volumes: - /data/paperless/data:/usr/src/paperless/data - /data/paperless/media:/usr/src/paperless/media - /data/paperless/export:/usr/src/paperless/export - /data/paperless/consume:/usr/src/paperless/consume version: "3.8" services: broker: image: redis:7 container_name: paperless-redis -weight: 500;">restart: unless-stopped command: redis-server --save "" --appendonly no db: image: postgres:15 container_name: paperless-db -weight: 500;">restart: unless-stopped environment: POSTGRES_DB: paperless POSTGRES_USER: paperless POSTGRES_PASSWORD: paperless_password_change_me volumes: - /data/paperless/db:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U paperless"] interval: 10s timeout: 5s retries: 5 webserver: image: ghcr.io/paperless-ngx/paperless-ngx:latest container_name: paperless -weight: 500;">restart: unless-stopped depends_on: db: condition: service_healthy broker: condition: service_started ports: - "8000:8000" environment: PAPERLESS_REDIS: redis://broker:6379 PAPERLESS_DBHOST: db PAPERLESS_DBNAME: paperless PAPERLESS_DBUSER: paperless PAPERLESS_DBPASS: paperless_password_change_me # Uncomment if using a domain: # PAPERLESS_URL: https://paperless.yourdomain.com # PAPERLESS_CSRF_TRUSTED_ORIGINS: https://paperless.yourdomain.com PAPERLESS_SECRET_KEY: "change_me_to_something_long_and_random" USERMAP_UID: 1000 USERMAP_GID: 1000 PAPERLESS_OCR_LANGUAGE: eng PAPERLESS_TIME_ZONE: Europe/London PAPERLESS_OCR_LANGUAGES: eng PAPERLESS_CONSUMER_POLLING: 60 PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}" volumes: - /data/paperless/data:/usr/src/paperless/data - /data/paperless/media:/usr/src/paperless/media - /data/paperless/export:/usr/src/paperless/export - /data/paperless/consume:/usr/src/paperless/consume -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d -weight: 500;">docker compose up -d http://YOUR_SERVER_IP:8000 http://YOUR_SERVER_IP:8000 http://YOUR_SERVER_IP:8000 -weight: 500;">docker exec -it paperless python3 manage.py createsuperuser -weight: 500;">docker exec -it paperless python3 manage.py createsuperuser -weight: 500;">docker exec -it paperless python3 manage.py createsuperuser cp *.pdf /data/paperless/consume/ cp *.pdf /data/paperless/consume/ cp *.pdf /data/paperless/consume/ PAPERLESS_URL: https://your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com PAPERLESS_URL: https://your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com PAPERLESS_URL: https://your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com,https://www.your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com,https://www.your-domain.com PAPERLESS_CSRF_TRUSTED_ORIGINS: https://your-domain.com,https://www.your-domain.com rsync -av /data/paperless /backup/ rsync -av /data/paperless /backup/ rsync -av /data/paperless /backup/ - Ubuntu server (20.04 or newer) - Docker installed - Docker Compose installed - Terminal access (SSH) - data – metadata - media – the actual document files - export – exported documents - consume – incoming folder for automatic import - db – database files - Email (optional) - Drag & Drop in the browser - The Upload button in the UI - Automatic import via the consume folder: - Do NOT add a trailing slash / at the end of the URL - If you use www, add both versions: - Make sure you created a superuser (Step 4) - Try clearing your browser cache/cookies - Verify that all containers are running: -weight: 500;">docker compose ps - Check language settings in your environment variables - This is normal during the initial indexing phase - OCR is CPU-intensive β€” be patient on lower-end hardware - Use strong, unique passwords - Set up regular backups: - Use the /consume folder to automate document ingestion - Organize documents with tags and correspondents from the -weight: 500;">start - Set up automated backups from day one - Use a domain with HTTPS if accessible over the internet - Restrict access if the instance is publicly reachable