Autoalojar Paperless-ngx en un VPS con Docker Compose

13 min de lectura·Matthieu·self-hostingdocker-composeocrdocument-managementpaperless-ngx|

Despliega Paperless-ngx en un VPS con Docker Compose, PostgreSQL y Redis. Configura idiomas y modos de OCR, reglas de etiquetado automático, consumo por correo electrónico y una estrategia de copias de seguridad con sincronización remota.

Paperless-ngx convierte tu VPS en un archivo documental con búsqueda de texto completo. Escaneas o subes documentos, Paperless-ngx les aplica OCR, los hace buscables y los clasifica con etiquetas y corresponsales automáticos. Funciona como un stack de Docker Compose con PostgreSQL, Redis y Gotenberg para la conversión de documentos.

Esta guía despliega Paperless-ngx en un VPS que ya tiene Docker y un reverse proxy funcionando. Si todavía no tienes esa base, empieza con nuestra guía de instalación del stack base.

¿Qué recursos necesita Paperless-ngx en un VPS?

Paperless-ngx necesita 2 GB de RAM como mínimo, 4 GB recomendados. El stack ejecuta PostgreSQL, Redis, Gotenberg, Tika y el servidor web. En reposo, el consumo total de memoria ronda los 800 MB. Durante la ingesta OCR de PDFs escaneados, el uso de CPU sube al 100 % en un núcleo y la RAM sube a 1,5-2 GB. El espacio en disco promedia 5-10 MB por documento (original + archivo + miniatura).

Componente RAM en reposo RAM máx. (OCR) Disco por 1.000 docs
Paperless-ngx webserver ~300 MB ~900 MB 5-10 GB
PostgreSQL ~50 MB ~100 MB ~500 MB
Redis ~10 MB ~10 MB despreciable
Gotenberg ~150 MB ~300 MB
Tika ~250 MB ~400 MB
Total ~760 MB ~1.710 MB 5-10 GB

Planifica el espacio en disco según tu volumen documental. Un hogar que escanea 50 documentos al mes necesita unos 6 GB por año. Una pequeña empresa con 500 documentos/mes debería presupuestar 50-60 GB por año.

¿Cómo despliego Paperless-ngx con Docker Compose en un VPS?

Crea un directorio para el stack, genera los secretos y escribe el fichero Compose. Cada servicio tiene un health check para que Docker reinicie automáticamente los contenedores con problemas.

Crear el directorio del proyecto

mkdir -p /opt/paperless-ngx && cd /opt/paperless-ngx

Generar los secretos

Nunca uses contraseñas por defecto. Genera una contraseña fuerte para la base de datos y una clave secreta de Django:

openssl rand -base64 32 > .db_password
openssl rand -base64 48 > .secret_key
chmod 600 .db_password .secret_key

Estos ficheros permanecen en disco con permisos restringidos. El fichero Compose los lee al arrancar los contenedores.

Escribir el fichero de entorno

cat > .env << 'EOF'
COMPOSE_PROJECT_NAME=paperless
EOF

Escribir el fichero Compose

# docker-compose.yml
services:
  broker:
    image: docker.io/library/redis:8
    restart: unless-stopped
    volumes:
      - redisdata:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 5s
      retries: 3

  db:
    image: docker.io/library/postgres:18
    restart: unless-stopped
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: paperless
      POSTGRES_USER: paperless
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U paperless"]
      interval: 30s
      timeout: 5s
      retries: 3

  gotenberg:
    image: docker.io/gotenberg/gotenberg:8
    restart: unless-stopped
    command:
      - "gotenberg"
      - "--chromium-disable-javascript=true"
      - "--chromium-allow-list=file:///tmp/.*"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      retries: 3

  tika:
    image: docker.io/apache/tika:latest
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9998/"]
      interval: 30s
      timeout: 5s
      retries: 3

  webserver:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
      broker:
        condition: service_healthy
      gotenberg:
        condition: service_healthy
      tika:
        condition: service_healthy
    ports:
      - "127.0.0.1:8000:8000"
    volumes:
      - data:/usr/src/paperless/data
      - media:/usr/src/paperless/media
      - ./export:/usr/src/paperless/export
      - ./consume:/usr/src/paperless/consume
    environment:
      PAPERLESS_REDIS: redis://broker:6379
      PAPERLESS_DBHOST: db
      PAPERLESS_DBUSER: paperless
      PAPERLESS_DBPASS_FILE: /run/secrets/db_password
      PAPERLESS_TIKA_ENABLED: 1
      PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://gotenberg:3000
      PAPERLESS_TIKA_ENDPOINT: http://tika:9998
      PAPERLESS_SECRET_KEY_FILE: /run/secrets/secret_key
      PAPERLESS_OCR_LANGUAGE: eng
      PAPERLESS_OCR_MODE: skip
      PAPERLESS_OCR_OUTPUT_TYPE: pdfa
      PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"
      PAPERLESS_URL: https://paperless.example.com
      USERMAP_UID: 1000
      USERMAP_GID: 1000
    secrets:
      - db_password
      - secret_key
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

secrets:
  db_password:
    file: .db_password
  secret_key:
    file: .secret_key

volumes:
  data:
  media:
  pgdata:
  redisdata:

Sobre esta configuración:

  • Los puertos solo escuchan en 127.0.0.1. Tu reverse proxy gestiona el acceso público por HTTPS. Exponer el puerto 8000 en 0.0.0.0 saltaría el TLS y tu firewall.
  • Los secretos usan Docker secrets (variables con el sufijo _FILE). Las contraseñas nunca aparecen en la salida de docker inspect ni en las listas de procesos.
  • USERMAP_UID/USERMAP_GID asignan el usuario del contenedor al UID 1000 en el host. Los ficheros creados en los directorios consume y export pertenecen a este usuario.
  • PAPERLESS_URL debe coincidir con tu dominio público. Paperless-ngx lo usa para generar enlaces de compartir y URLs en correos. Sustituye paperless.example.com por tu dominio real.

Arrancar el stack

docker compose up -d

Espera unos 60 segundos a que todos los servicios se inicialicen. Comprueba que cada contenedor esté sano:

docker compose ps
NAME                   SERVICE      STATUS                  PORTS
paperless-broker-1     broker       running (healthy)
paperless-db-1         db           running (healthy)
paperless-gotenberg-1  gotenberg    running (healthy)
paperless-tika-1       tika         running (healthy)
paperless-webserver-1  webserver    running (healthy)       127.0.0.1:8000->8000/tcp

Los cinco contenedores deberían mostrar (healthy). Si alguno muestra (health: starting), espera otros 30 segundos. Si uno permanece (unhealthy), revisa sus logs:

docker compose logs gotenberg --tail 20

Crear el superusuario

docker compose exec webserver createsuperuser

Sigue las indicaciones para nombre de usuario, correo y contraseña. Esta es tu cuenta de administrador para la interfaz web.

Acceder a la interfaz web

Si tu reverse proxy está configurado, abre https://paperless.example.com en un navegador. Se carga la página de inicio de sesión de Paperless-ngx. Si todavía estás configurando el reverse proxy, prueba localmente:

curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000
200

¿Qué idiomas y modos de OCR debo configurar?

Paperless-ngx usa Tesseract para el OCR. El idioma y el modo de procesamiento se configuran mediante variables de entorno. Los valores por defecto funcionan para documentos en inglés, pero la mayoría de usuarios necesitan ajustarlos.

Idioma de OCR

Configura PAPERLESS_OCR_LANGUAGE con el código de idioma Tesseract de tres letras de tus documentos. Para varios idiomas, únelos con +:

# Un solo idioma
PAPERLESS_OCR_LANGUAGE: deu

# Varios idiomas
PAPERLESS_OCR_LANGUAGE: deu+eng+fra

Códigos de idioma habituales: eng (inglés), deu (alemán), fra (francés), spa (español), ita (italiano), nld (neerlandés), por (portugués). La lista completa está en la documentación de Tesseract.

La imagen del contenedor incluye la mayoría de paquetes de idioma. Añadir más idiomas a PAPERLESS_OCR_LANGUAGE incrementa el tiempo de procesamiento OCR por página.

Modos de OCR

El parámetro PAPERLESS_OCR_MODE controla cómo Paperless-ngx trata los documentos que ya contienen una capa de texto (habitual en PDFs creados digitalmente).

Modo Comportamiento Cuándo usarlo
skip Solo aplica OCR a páginas sin texto. Siempre crea una copia de archivo. Por defecto. Ideal para entrada mixta: PDFs escaneados + digitales.
skip_noarchive Como skip, pero sin crear archivo para documentos que ya tienen texto. Quieres ahorrar espacio en disco con PDFs de origen digital.
redo Re-OCR de todas las páginas, reemplazando capas de texto existentes. Recibes documentos con OCR de mala calidad.
force Rasteriza el documento y aplica OCR desde cero. Destruye el texto original. Último recurso. Produce ficheros más grandes con texto menos nítido.

Para la mayoría de instalaciones, skip es la opción adecuada. Gestiona tanto documentos escaneados como digitales sin gastar CPU reprocesando documentos que ya son buscables.

Si recibes documentos de un escáner que hace su propio OCR (de mala calidad), cambia el modo a redo. Ten en cuenta que redo es incompatible con PAPERLESS_OCR_CLEAN y PAPERLESS_OCR_DESKEW.

Tipo de salida OCR

PAPERLESS_OCR_OUTPUT_TYPE: pdfa (valor por defecto) produce ficheros PDF/A, el estándar de archivo. Son autónomos e incluyen fuentes incrustadas. Mantén este ajuste salvo que tengas un motivo concreto para cambiarlo.

Después de modificar cualquier parámetro de OCR, reinicia el contenedor del webserver:

docker compose restart webserver

Los documentos existentes no se reprocesan. Para volver a aplicar OCR a un documento, usa la acción «Redo OCR» en la interfaz web.

¿Cómo funciona el etiquetado automático en Paperless-ngx?

Paperless-ngx ofrece seis algoritmos de coincidencia para etiquetas, corresponsales y tipos de documento. Cuando llega un documento, cada etiqueta compara el contenido con su patrón definido. Si el patrón coincide, la etiqueta se aplica automáticamente.

Algoritmo Funcionamiento Cuándo usarlo Ejemplo
Any Coincide si cualquier palabra del campo de coincidencia aparece. Categorías amplias. invoice receipt bill
All Coincide si todas las palabras aparecen (cualquier orden). Coincidencia más precisa sin dependencia de posición. electricity quarterly
Exact Coincide con la frase exacta en orden. Nombres de empresa, números de cuenta. Acme Corp
Regular Expression Coincidencia por expresión regular completa. Datos estructurados: fechas, números de referencia, importes. Invoice\s+#?\d{4,}
Fuzzy Coincidencia aproximada con umbral configurable. OCR inconsistente, erratas leves. Stadtwerke (coincide con Stadtwenke)
Auto Clasificador ML que aprende de tus asignaciones manuales. Cuando tengas ~50+ documentos etiquetados manualmente. (aprende de tus correcciones)

Crear etiquetas con reglas de coincidencia

En la interfaz web, ve a Manage > Tags y crea una etiqueta. Configura el algoritmo de coincidencia y el campo Match. Algunos ejemplos prácticos:

  • Etiqueta: Invoice, Algoritmo: Any, Match: invoice rechnung facture (captura facturas en varios idiomas)
  • Etiqueta: Bank, Algoritmo: Regular Expression, Match: IBAN\s*[A-Z]{2}\d{2} (coincide con cualquier número IBAN)
  • Etiqueta: Medical, Algoritmo: All, Match: patient diagnosis (requiere ambas palabras)
  • Corresponsal: Electric Company, Algoritmo: Exact, Match: Springfield Energy Inc

Entrenar el clasificador Auto

El algoritmo Auto utiliza un clasificador de machine learning que Paperless-ngx reentrena automáticamente. Para entrenarlo:

  1. Etiqueta manualmente al menos 50 documentos en tus categorías
  2. Asegúrate de que esos documentos no están en tu bandeja de entrada (el clasificador ignora los documentos de la bandeja de entrada)
  3. El clasificador se reentrena según un calendario (por defecto: cada hora vía document_create_classifier)
  4. Los nuevos documentos empiezan a recibir asignaciones automáticas

Puedes lanzar un reentrenamiento manual:

docker compose exec webserver document_create_classifier

El clasificador mejora con cada corrección que hagas. Cada corrección alimenta el siguiente ciclo de entrenamiento.

¿Cómo configuro el consumo de documentos en Paperless-ngx?

Paperless-ngx ingiere documentos por tres canales: subida manual por la interfaz web, un directorio vigilado en disco y obtención de correos por IMAP.

Subida por la interfaz web

Arrastra y suelta ficheros en la interfaz web. Funciona inmediatamente tras la instalación. Formatos soportados: PDF, PNG, JPEG, TIFF y (con Tika habilitado) DOCX, XLSX, ODT y otros formatos ofimáticos.

Directorio vigilado (directorio consume)

El directorio ./consume mapeado en el fichero Compose está monitorizado por un watcher de inotify. Coloca un fichero y Paperless-ngx lo procesa en segundos.

cp /tmp/scan-001.pdf /opt/paperless-ngx/consume/

El fichero desaparece del directorio consume cuando el procesamiento termina. Para organizar el consumo por etiqueta, habilita el etiquetado por subdirectorio:

PAPERLESS_CONSUMER_RECURSIVE: true
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: true

Con este ajuste, un fichero en consume/invoices/scan.pdf recibe automáticamente la etiqueta invoices.

Consumo por correo electrónico (IMAP)

El consumo por correo se configura por completo en la interfaz web en Manage > Mail. Añades cuentas de correo y reglas.

Añadir una cuenta de correo:

  1. Ve a Manage > Mail > Mail Accounts
  2. Introduce el servidor IMAP, el puerto (993 para TLS), nombre de usuario y contraseña
  3. Indica la carpeta a monitorizar (p. ej. INBOX)

Añadir una regla de correo:

  1. Ve a Manage > Mail > Mail Rules
  2. Selecciona la cuenta de correo
  3. Elige qué consumir: solo los adjuntos o el correo completo como PDF
  4. Define una acción para los correos procesados: marcar como leído, mover a una carpeta o eliminar
  5. Opcionalmente, asigna una etiqueta, corresponsal o tipo de documento

Configuración típica: reenvía documentos a una dirección de correo dedicada (p. ej. scan@tudominio.com), configura Paperless-ngx para vigilar esa bandeja y consumir los adjuntos. Los originales se mueven a una carpeta IMAP «Archived» tras el procesamiento.

¿Cómo organizo las rutas de almacenamiento y los nombres de fichero?

Paperless-ngx almacena documentos en dos lugares: el volumen media contiene los ficheros en disco, mientras que PostgreSQL gestiona los metadatos. La variable PAPERLESS_FILENAME_FORMAT controla el nombrado de los ficheros archivados dentro del directorio media.

El fichero Compose anterior usa:

PAPERLESS_FILENAME_FORMAT: "{created_year}/{correspondent}/{title}"

Esto crea una estructura como:

media/documents/archive/
  2026/
    Springfield Energy Inc/
      Electricity Bill January 2026.pdf
    Dr. Smith/
      Lab Results March 2026.pdf

Las variables de plantilla disponibles incluyen {created_year}, {created_month}, {created_day}, {correspondent}, {document_type}, {title}, {tag_list} y {owner_username}. Si una variable queda vacía (sin corresponsal asignado), Paperless-ngx la reemplaza por none.

Después de cambiar el formato de nombre, aplícalo a los documentos existentes:

docker compose exec webserver document_renamer

¿Cuál es la mejor estrategia de copias de seguridad para Paperless-ngx?

Una copia de seguridad funcional cubre la base de datos PostgreSQL, los ficheros de documentos (originales + archivos + miniaturas) y los metadatos de Paperless-ngx (etiquetas, corresponsales, reglas de coincidencia). El exportador de documentos captura todo esto en un formato portable. Combínalo con un volcado de base de datos para restauraciones más rápidas solo de la base.

Volcado de la base de datos

docker compose exec db pg_dump -U paperless paperless | gzip > /opt/paperless-ngx/backups/db-$(date +%Y%m%d).sql.gz

Exportador de documentos

El exportador crea una instantánea completa: documentos, metadatos y manifiesto. Es el método de backup canónico.

docker compose exec webserver document_exporter ../export -c -d

El flag -c compara checksums (solo exporta ficheros modificados). El flag -d elimina del export los ficheros que ya no existen en Paperless-ngx. Juntos mantienen el directorio de exportación como un espejo del estado actual.

Automatizar con cron

Crea un script de backup:

cat > /opt/paperless-ngx/backup.sh << 'SCRIPT'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/paperless-ngx/backups"
EXPORT_DIR="/opt/paperless-ngx/export"

mkdir -p "$BACKUP_DIR"

# Database dump
docker compose -f /opt/paperless-ngx/docker-compose.yml exec -T db \
  pg_dump -U paperless paperless | gzip > "$BACKUP_DIR/db-$(date +%Y%m%d).sql.gz"

# Document exporter
docker compose -f /opt/paperless-ngx/docker-compose.yml exec -T webserver \
  document_exporter ../export -c -d

# Remove database dumps older than 30 days
find "$BACKUP_DIR" -name "db-*.sql.gz" -mtime +30 -delete

echo "[$(date -Is)] Backup completed" >> "$BACKUP_DIR/backup.log"
SCRIPT

chmod 700 /opt/paperless-ngx/backup.sh

Prográmalo con cron (esto conserva las entradas existentes de crontab):

(crontab -l 2>/dev/null; echo "0 3 * * * /opt/paperless-ngx/backup.sh") | crontab -

Esto añade una tarea nocturna a las 03:00.

Sincronización remota

El directorio de exportación y los volcados de base de datos deben salir del servidor. Usa rsync o rclone para enviarlos a una segunda ubicación. Para un destino compatible con S3:

rclone sync /opt/paperless-ngx/export remote:paperless-backup/export
rclone sync /opt/paperless-ngx/backups remote:paperless-backup/db-dumps

Para otro servidor vía SSH:

rsync -az --delete /opt/paperless-ngx/export/ backup-server:/backups/paperless/export/
rsync -az /opt/paperless-ngx/backups/ backup-server:/backups/paperless/db-dumps/

Para profundizar en estrategias de backup de volúmenes Docker, consulta nuestra guía sobre backup de volúmenes Docker.

¿Cómo restauro una copia de seguridad de Paperless-ngx?

La restauración requiere una instalación limpia de Paperless-ngx. Levanta el stack y después importa.

Restauración solo de la base de datos (más rápida, para corrupción o migración):

gunzip < /opt/paperless-ngx/backups/db-20260320.sql.gz | \
  docker compose exec -T db psql -U paperless paperless

Restauración completa desde el exportador (documentos, etiquetas, corresponsales, todo):

docker compose exec webserver document_importer ../export

Ejecuta el importador contra una base de datos vacía. Recrea todos los metadatos y enlaza los ficheros desde el directorio de exportación.

Prueba tu restauración periódicamente. Una copia de seguridad que nunca has restaurado no es una copia de seguridad.

¿Cómo endurecer los contenedores de Paperless-ngx?

El fichero Compose anterior ya cubre lo básico: ficheros de secretos en vez de contraseñas en texto plano, puerto ligado a localhost y mapeo de usuarios. Aquí van medidas de endurecimiento adicionales.

Eliminar capabilities innecesarias

Añade opciones de seguridad al servicio webserver:

webserver:
  # ... configuración existente ...
  security_opt:
    - no-new-privileges:true

Esto impide que el proceso del contenedor obtenga privilegios adicionales a través de binarios setuid.

Establecer límites de recursos

Evita que un proceso OCR descontrolado consuma todos los recursos del servidor:

webserver:
  # ... configuración existente ...
  deploy:
    resources:
      limits:
        memory: 2g
        cpus: "2.0"
      reservations:
        memory: 512m

Para más información sobre límites de recursos, consulta nuestra guía sobre límites de recursos en Docker Compose.

Ocultar información de versión

Configura tu reverse proxy para que elimine la cabecera Server. Con Nginx:

server_tokens off;

Paperless-ngx no expone su versión en las cabeceras HTTP, pero tu reverse proxy podría hacerlo.

¿Cómo actualizo Paperless-ngx?

Descarga las imágenes más recientes y recrea los contenedores. La imagen de Paperless-ngx ejecuta las migraciones de base de datos automáticamente al arrancar.

cd /opt/paperless-ngx
docker compose pull
docker compose up -d

Tras la actualización, revisa los logs para ver la salida de migraciones:

docker compose logs webserver --tail 30

Busca líneas como Applying documents.XXXX_migration_name... OK. Si las migraciones fallan, el contenedor se detiene. Consulta las notas de lanzamiento antes de saltos de versión mayores.

Ejecuta siempre tu script de backup antes de actualizar.

¿Algo no funciona?

El contenedor permanece unhealthy: Revisa los logs con docker compose logs <service> --tail 50. Causas habituales: contraseña de PostgreSQL incorrecta (regenera .db_password y recrea el volumen de la base de datos), conexión a Redis rechazada (el broker aún no ha arrancado).

El OCR produce texto ilegible: Idioma incorrecto configurado. Comprueba que PAPERLESS_OCR_LANGUAGE coincida con tus documentos. Para documentos multilingües, añade todos los códigos de idioma relevantes separados por +.

Los documentos no aparecen tras la subida: Revisa el log del consumer:

docker compose logs webserver --tail 50 | grep -i consumer

Causa habitual: desajuste de permisos. El directorio consume debe tener permisos de escritura para el UID 1000 (o el valor de USERMAP_UID):

chown -R 1000:1000 /opt/paperless-ngx/consume

El etiquetado automático no funciona: El clasificador necesita al menos ~50 documentos etiquetados manualmente. Comprueba si el clasificador ha sido entrenado:

docker compose exec webserver document_create_classifier

El espacio en disco crece rápido: Comprueba qué documentos son los más grandes:

docker compose exec webserver document_sanity_checker

Revisa también tu modo de OCR. El modo force crea ficheros de archivo considerablemente más grandes que skip.

El consumo por email no obtiene correos: Comprueba las credenciales IMAP en la interfaz web. Lanza una obtención manual para ver errores:

docker compose exec webserver mail_fetcher

Para otras guías de autoalojamiento, consulta nuestra guía de Immich para gestión de fotos en el mismo stack.


Copyright 2026 Virtua.Cloud. Todos los derechos reservados. Este contenido es una obra original del equipo de Virtua.Cloud. La reproducción, republicación o redistribución sin permiso escrito está prohibida.

¿Listo para probarlo?

Despliega tu propio servidor en segundos. Linux, Windows o FreeBSD.

Ver planes VPS
Autoalojar Paperless-ngx en un VPS con Docker Compose