Instalar paperless-ngx en un VPS con Docker Compose
Guía para desplegar paperless-ngx en un VPS con Docker Compose: Postgres oficial, PAPERLESS_URL, carpeta consume, OCR, HTTPS y copias de seguridad.
Qué va a crear
Paperless-ngx en un VPS convierte una carpeta de documentos escaneados en un archivo con búsqueda. Coloque un PDF en un directorio supervisado y el servidor ejecutará OCR (reconocimiento óptico de caracteres), extraerá el texto, estimará una fecha y un corresponsal, y archivará el documento. La instalación consta de un archivo de Docker Compose con cuatro servicios. Después sólo hay que configurar el sistema. Esta guía dedica la mayor parte de su contenido a esa configuración, porque es donde suelen fallar las instalaciones. No es una biblioteca de fotos: OCR y la estimación del corresponsal no sirven para una carpeta de JPEG de vacaciones. Guárdelos en un servidor de fotos diseñado para ello y reserve paperless para los documentos. Con el vídeo ocurre lo mismo: una colección de películas ripeadas corresponde a un servidor multimedia, donde algo como una interfaz de Jellyfin ambientada como un videoclub de los años 90 hace que explorar sea el objetivo, en lugar de buscar.
Paperless-ngx es la bifurcación comunitaria mantenida del proyecto original Paperless. Es gratuito, se aloja en sus propios sistemas y almacena los documentos como archivos normales en el disco, por lo que nunca pierde el acceso a su propio archivo. Ejecutarlo en un VPS en lugar de un equipo doméstico permite acceder a los documentos escaneados desde cualquier lugar sin abrir un puerto en el router de casa. Además, se integra bien con una instancia privada de Nextcloud para los archivos que no están en papel. La misma lógica se aplica al equipo al que está conectado el escáner: un relay propio de RustDesk en ese VPS permite controlar ese equipo desde otro lugar sin abrir tampoco un puerto en el router.
Qué ejecuta realmente la pila
El archivo compose oficial inicia cuatro contenedores. Saber qué hace cada uno facilita la lectura de los registros.
webserver: la imagen de paperless-ngx. Ejecuta la interfaz web, la API, el consumidor que supervisa la carpeta de entrada y los workers de tareas de Celery que realizan el OCR.db: PostgreSQL. Almacena los metadatos, las etiquetas, los corresponsales y las tablas del índice de búsqueda de texto completo. No almacena los archivos PDF.broker: Valkey, un almacén de pares clave-valor compatible con Redis. Actúa como cola de tareas entre el proceso web y los workers.gotenbergytika: opcionales, sólo en las variantes de compose-tika. Convierten documentos de Office (.docx,.xlsx,.odt) a PDF para que paperless pueda indexarlos.
En octubre de 2026, el archivo compose de postgres fija docker.io/library/postgres:18 y docker.io/valkey/valkey:9-alpine, y extrae la aplicación de ghcr.io/paperless-ngx/paperless-ngx:latest.
Requisitos previos
- Un VPS KVM con Ubuntu 24.04 y acceso mediante sudo, con Docker y el complemento Compose ya instalados. Si esta parte es nueva para usted, empiece por los fundamentos de Docker Compose para un VPS y vuelva después.
- Un nombre de dominio con un registro A que apunte al VPS. Paperless no aceptará un inicio de sesión mediante HTTPS en un nombre de host que no se le haya indicado. Por eso este requisito es importante antes de lo esperado.
- La memoria es la principal limitación. PostgreSQL, Valkey, el servidor web Granian y un worker de OCR de Tesseract pueden ejecutarse al mismo tiempo en 2 GB para un uso ligero. Asigne 4 GB si piensa importar cientos de documentos pendientes, porque el OCR de un PDF grande de varias páginas provoca el pico de memoria que puede hacer que el kernel termine un worker mediante el OOM killer.
- Disco: el archivo se almacena dos veces: el archivo original y un PDF de archivo con OCR. Reserve aproximadamente el doble del tamaño de sus escaneos.
Obtenga los archivos oficiales de Compose
Existe un instalador interactivo:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"Hace preguntas y escribe los archivos por usted. Hacerlo manualmente requiere cuatro comandos y le permite saber dónde está cada elemento. Eso es lo que necesita en un servidor que va a mantener.
mkdir -p ~/paperless && cd ~/paperless
curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.postgres.yml
curl -fsSL -o docker-compose.env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/docker-compose.env
curl -fsSL -o .env https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/docker/compose/.envLas variantes están en el mismo directorio: docker-compose.sqlite.yml, docker-compose.mariadb.yml y una versión -tika de cada una. Elija postgres para una instalación nueva. SQLite funciona bien con unos cientos de documentos, pero el índice de búsqueda de texto completo se vuelve lento mucho antes que PostgreSQL.
El archivo .env contiene una línea: COMPOSE_PROJECT_NAME=paperless. Ese nombre se convierte en el prefijo de cada contenedor y volumen. No lo elimine y luego se pregunte por qué docker compose down -v no puede encontrar sus datos.
Configure docker-compose.env antes del primer inicio
Hay dos ajustes obligatorios. Genere la clave secreta con el comando documentado por el proyecto:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Después edite docker-compose.env:
PAPERLESS_SECRET_KEY=<the long string you just generated>
PAPERLESS_URL=https://paperless.example.com
PAPERLESS_TIME_ZONE=Europe/Berlin
PAPERLESS_OCR_LANGUAGE=deu+eng
USERMAP_UID=1000
USERMAP_GID=1000PAPERLESS_SECRET_KEY se incluye con el valor literal change-me. Esta clave firma los tokens de sesión. Las versiones actuales consideran que ese valor de marcador no está configurado: paperless no se inicia si la clave falta, está vacía o sigue siendo change-me. Por tanto, la pila no se iniciará hasta que lo sustituya. Configúrela antes del primer inicio. Si la cambia después, se cerrará la sesión de todos los usuarios.
PAPERLESS_URL es el ajuste que evita perder una hora. Paperless es una aplicación Django, y Django comprueba tanto la cabecera Host como el origen que comunica el navegador en un formulario de inicio de sesión. Configure PAPERLESS_URL y añadirá su dirección a ALLOWED_HOSTS, CORS_ALLOWED_HOSTS y CSRF_TRUSTED_ORIGINS automáticamente. Si lo deja vacío, apunta un dominio al servidor y la página de inicio de sesión sigue cargando porque PAPERLESS_ALLOWED_HOSTS tiene * como valor predeterminado. Sin embargo, el envío por HTTPS falla con CSRF verification failed. Request aborted. porque el dominio no es un origen de confianza. Escríbalo sin barra final y sin ruta.
USERMAP_UID y USERMAP_GID establecen el usuario con el que se ejecuta el contenedor. Asígneles los valores de su propia cuenta, comprobados con id -u y id -g. Si no coinciden, el consumidor no puede leer los archivos que copie en la carpeta consume y el registro muestra un error de permisos en lugar de una importación.
Inicie la pila y cree el primer usuario
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser solicita un nombre de usuario, un correo electrónico y una contraseña. No hay credenciales predeterminadas. Si omite este paso, la interfaz web pide a la primera persona que la abra que cree el superusuario. Mientras el puerto 8000 siga expuesto a Internet, esa persona podría no ser usted. Por eso, cree la cuenta inmediatamente desde el shell. Espere a que aparezca en el registro la línea que indica que el servidor escucha en el puerto 8000 antes de probarlo desde el navegador. El primer inicio también ejecuta las migraciones de la base de datos, lo que tarda uno o dos minutos.
Compruébelo localmente antes de configurar un dominio:
curl -I http://127.0.0.1:8000Una redirección de 302 a /accounts/login/ indica que la pila funciona correctamente.
Anteponer HTTPS a la aplicación
El archivo Compose incluido publica 8000:8000, que se enlaza a todas las interfaces. En un VPS público, esto expone todo el archivo de documentos mediante HTTP sin cifrar a cualquiera que encuentre la dirección. Cambie la línea del puerto para que se enlace sólo a loopback:
ports:
- "127.0.0.1:8000:8000"Termine TLS (seguridad de la capa de transporte) en un reverse proxy y reenvíe las solicitudes a 127.0.0.1:8000. Si esta es la única aplicación del servidor, cualquier proxy con un cliente ACME (entorno de gestión automática de certificados) será suficiente. Si ejecuta varios contenedores detrás de una única configuración de certificados, siga el patrón de reverse proxy Traefik para varias aplicaciones de Docker Compose y conecte el servicio webserver a la red del proxy sin publicar ningún puerto.
Independientemente del proxy que use, la comprobación del origen del formulario de inicio de sesión es el punto donde suelen fallar las configuraciones HTTPS. Paperless ignora X-Forwarded-Proto a menos que configure PAPERLESS_PROXY_SSL_HEADER, que está desactivado de forma predeterminada. Por ello, Django trata cada solicitud reenviada por el proxy como HTTP sin cifrar, y el origen https:// que envía el navegador sólo se acepta si figura en la lista de orígenes de confianza. Configurar PAPERLESS_URL con la dirección https:// exacta que escribe en el navegador es lo que añade ese origen a la lista. Sin esta configuración, aparece CSRF verification failed. Request aborted. en una página que parece correcta.
Aumente también el límite de carga del proxy. Un escaneo de 40 MB enviado a través de un proxy que limita el cuerpo de las solicitudes a 1 MB se rechaza antes de que paperless lo reciba, y el navegador muestra un error de carga genérico.
Cómo funciona el directorio consume
El archivo compose monta ./consume mediante un bind mount desde el directorio compose al contenedor. Todo lo que coloque allí se importa y después se elimina del directorio, porque el archivo pasa a estar en el volumen de medios gestionado por paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverDebería ver que consumer detecta el nombre del archivo, ejecuta OCR y termina con una línea que indica que el documento se añadió. Todo el ciclo tarda unos segundos para un escaneo de una página y puede tardar un minuto o más en un documento largo.
Dos opciones cambian la forma en que se encuentran los archivos. PAPERLESS_CONSUMER_RECURSIVE=true hace que paperless busque en subdirectorios, y PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true convierte el nombre de cada subdirectorio en una etiqueta. Por tanto, colocar un archivo en consume/invoices/2026/ le asigna las etiquetas invoices y 2026. Es el sistema de clasificación más sencillo que puede implementar.
La detección es la otra parte. De forma predeterminada, PAPERLESS_CONSUMER_POLLING_INTERVAL es 0, lo que significa que paperless usa notificaciones nativas del sistema de archivos que se generan de inmediato. Estas notificaciones no son fiables en un sistema de archivos de red. Si el directorio consume es un recurso compartido NFS o SMB para que un escáner de red pueda escribir en él, los archivos pueden permanecer allí sin detectarse. Para solucionarlo, establezca el intervalo en un número positivo de segundos para que paperless explore el directorio.
Idiomas de OCR y su coste
PAPERLESS_OCR_LANGUAGE acepta un código de Tesseract de tres letras, eng de forma predeterminada. Combine los idiomas con el signo más, como en deu+eng. Tesseract procesa cada página con todos los idiomas indicados, por lo que cada idioma adicional añade tiempo de CPU a cada análisis. En una VPS con vCPU compartida, esa puede ser la diferencia entre que un análisis termine en diez segundos o en un minuto. Incluya sólo los idiomas en los que estén escritos realmente sus documentos.
La imagen incluye inglés, alemán, italiano, español y francés. Para cualquier otro idioma, añádalo a PAPERLESS_OCR_LANGUAGES como una lista separada por espacios, por ejemplo PAPERLESS_OCR_LANGUAGES=tur ces, y reinicie el servicio. El contenedor descarga los paquetes de datos de Tesseract al arrancar, por lo que el primer arranque después de ese cambio tarda más.
Crear una copia de seguridad de la base de datos y los archivos multimedia
Copiar los volúmenes de Docker mientras PostgreSQL está en ejecución produce una copia de seguridad que puede no restaurarse correctamente. Paperless incluye su propio exportador, que escribe los documentos y un manifiesto JSON con todos los metadatos en el montaje bind ./export:
docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar--delete elimina los archivos exportados que ya no corresponden a un documento actual, por lo que la carpeta se mantiene como un espejo en lugar de crecer indefinidamente. --no-progress-bar mantiene limpia la salida cuando se ejecuta desde cron, y -T evita que Compose solicite un terminal que cron no tiene.
La restauración se realiza document_importer desde esa misma carpeta en un stack nuevo. Por tanto, el directorio de exportación es lo único que debe conservar de forma segura. Envíelo a otra ubicación según una programación mediante copias de seguridad cifradas y deduplicadas con restic desde su VPS, y ejecute primero la exportación para que restic nunca capture un archivo escrito parcialmente.
Verifique una copia de seguridad comprobando que exista export/manifest.json y que el número de archivos coincida con el número de documentos mostrado en la interfaz. Una copia de seguridad que nunca ha listado no es una copia de seguridad. Peor aún es que una exportación nocturna empiece a fallar sin avisar. Configure el trabajo de cron para enviar su estado de salida a su propio servidor ntfy y sabrá que ha fallado durante esa semana, en lugar de descubrirlo el día que necesite restaurar.
FAQ
¿Por qué todas las páginas devuelven "Bad Request (400)" después de apuntar mi dominio al servicio?
Con los archivos predeterminados no debería ocurrir, porque PAPERLESS_ALLOWED_HOSTS usa * de forma predeterminada. Un 400 con DisallowedHost en el registro del contenedor significa que PAPERLESS_ALLOWED_HOSTS se ha definido con una lista que no incluye el dominio. Defina PAPERLESS_URL=https://paperless.example.com en docker-compose.env, sin barra final, y paperless añadirá ese nombre de host a la lista, o elimine la línea PAPERLESS_ALLOWED_HOSTS. Después, ejecute docker compose up -d para recrear el contenedor. Editar sólo el archivo de entorno no sirve, porque el contenedor en ejecución conserva el entorno con el que se inició.
He dejado un PDF en la carpeta consume y no ha ocurrido nada. ¿Cuál es el problema?
Compruebe primero docker compose logs webserver. Un error de permisos significa que USERMAP_UID y USERMAP_GID no coinciden con la cuenta propietaria del archivo, por lo que debe corregirlos y recrear el contenedor. Si no aparece ninguna línea en el registro, el evento del archivo no ha llegado. Esto ocurre en recursos compartidos de red porque las notificaciones nativas del sistema de archivos no son fiables allí. Defina PAPERLESS_CONSUMER_POLLING_INTERVAL con un valor como 30 y paperless examinará la carpeta cada 30 segundos.
¿Puedo ejecutar paperless-ngx con SQLite en lugar de PostgreSQL?
Sí, docker-compose.sqlite.yml es compatible y usa menos memoria, por lo que resulta adecuado para un VPS pequeño. La diferencia se nota a medida que crece el archivo: la búsqueda de texto completo y las modificaciones masivas de etiquetas se ralentizan de forma apreciable cuando hay miles de documentos. Migrar más adelante requiere exportar e importar los datos, así que elija PostgreSQL desde ahora si espera que el archivo siga creciendo.
¿Cuánto espacio en disco necesita realmente un archivo de documentos escaneados?
Aproximadamente el doble del tamaño de los archivos originales. Paperless conserva el original sin modificar y almacena un segundo PDF procesado mediante OCR con una capa de texto consultable, además de miniaturas pequeñas. Un escaneo de sólo texto de 200 KB ocupa poco espacio. Un escaneo en color de 30 MB de un contrato largo ocupa unos 60 MB. Añada el directorio de exportación si lo conserva en el mismo disco; en ese caso, el mismo archivo ocupa tres veces ese espacio en disco.
¿Necesito los contenedores de Tika y Gotenberg?
Sólo si quiere indexar archivos de Word, Excel u OpenDocument junto con sus PDF. Estos contenedores convierten esos formatos a PDF para que paperless pueda aplicar OCR y realizar búsquedas. También añaden otros dos contenedores en ejecución y varios cientos de megabytes de memoria, así que puede omitirlos en un sistema pequeño si todos los archivos que almacena ya son PDF o imágenes.