Paperless-ngx em VPS com Docker Compose
Aprenda a instalar o Paperless-ngx num VPS com Docker Compose, Postgres, PAPERLESS_URL, pasta consume, OCR em vários idiomas, HTTPS e backups.
O que você vai criar
O Paperless-ngx num VPS transforma uma pasta de documentos digitalizados num arquivo pesquisável. Coloque um PDF num diretório monitorizado, e o servidor executa OCR (reconhecimento ótico de carateres), extrai o texto, tenta determinar uma data e um correspondente e arquiva o documento. A instalação consiste num único ficheiro Docker Compose com quatro serviços. Depois disso, tudo é configuração, e este guia dedica-lhe a maior parte do conteúdo porque é aí que as instalações falham. O que não é uma biblioteca de fotografias: o OCR e a identificação de correspondentes não servem para uma pasta de JPEGs de férias. Coloque essas fotografias em um servidor de fotografias criado para esse fim e use o Paperless para documentos. O mesmo se aplica ao vídeo: uma coleção de filmes extraídos pertence a um servidor multimédia, onde algo como uma interface Jellyfin com o visual de uma videolocadora dos anos 90 torna a navegação o objetivo, em vez da pesquisa.
O Paperless-ngx é o fork comunitário mantido do projeto Paperless original. É gratuito, autoalojado e armazena os seus documentos como ficheiros simples no disco, para nunca perder o acesso ao seu próprio arquivo. Executá-lo num VPS em vez de num computador em casa significa que os seus scans ficam acessíveis a partir de qualquer lugar sem abrir uma porta no router doméstico, e combina bem com uma instância privada do Nextcloud para os ficheiros que não são em papel. A mesma lógica aplica-se ao computador ao qual o scanner está ligado, porque um relay RustDesk próprio nesse VPS permite controlar essa máquina a partir de outro local sem criar uma abertura no router.
O que a stack realmente executa
O ficheiro compose oficial inicia quatro contentores. Saber a função de cada um facilita a leitura dos logs.
webserver: a própria imagem do paperless-ngx. Executa a interface Web, a API, o consumer que monitoriza a pasta de entrada e os workers de tarefas Celery que fazem o OCR.db: PostgreSQL. Armazena metadados, tags, correspondentes e as tabelas do índice de pesquisa de texto completo. Não armazena os seus PDFs.broker: Valkey, um armazenamento de chave-valor compatível com Redis. É a fila de tarefas entre o processo Web e os workers.gotenbergetika: opcionais, apenas nas variantes compose-tika. Convertem documentos do Office (.docx,.xlsx,.odt) para PDF, para que o paperless possa indexá-los.
Em outubro de 2026, o ficheiro compose do postgres fixa docker.io/library/postgres:18 e docker.io/valkey/valkey:9-alpine e obtém a aplicação de ghcr.io/paperless-ngx/paperless-ngx:latest.
Pré-requisitos
- Um VPS KVM Ubuntu 24.04 com acesso sudo e Docker com o plugin Compose já instalado. Se esta parte for nova, comece por os fundamentos do Docker Compose para um VPS e volte depois.
- Um nome de domínio com um registo A apontado para o VPS. O Paperless não aceita um início de sessão por HTTPS num hostname que não tenha sido configurado, por isso este requisito é importante mais cedo do que possa parecer.
- A memória é a principal limitação. PostgreSQL, Valkey, o servidor Web Granian e um worker de OCR Tesseract em execução simultânea cabem em 2 GB para utilização ligeira. Disponibilize 4 GB se planear importar um backlog de centenas de digitalizações, porque o OCR de um PDF grande com várias páginas é o pico de utilização de memória que pode fazer o kernel terminar um worker por falta de memória.
- Disco: o arquivo é armazenado duas vezes, no ficheiro original e num PDF de arquivo com OCR, por isso reserve aproximadamente o dobro do tamanho das suas digitalizações.
Obter os ficheiros oficiais do compose
Existe um instalador interativo:
bash -c "$(curl --location --silent --show-error https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/main/install-paperless-ngx.sh)"O instalador faz perguntas e escreve os ficheiros por si. Fazer isto manualmente requer quatro comandos e permite saber onde está cada elemento, o que é importante num servidor que vai manter.
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/.envAs variantes ficam no mesmo diretório: docker-compose.sqlite.yml, docker-compose.mariadb.yml e uma versão -tika de cada uma. Escolha postgres numa instalação nova. SQLite é adequado para algumas centenas de documentos, mas o índice de pesquisa de texto completo fica lento muito antes do PostgreSQL.
O ficheiro .env contém uma linha, COMPOSE_PROJECT_NAME=paperless. Esse nome torna-se o prefixo de todos os contentores e volumes. Não o elimine para depois tentar perceber por que motivo docker compose down -v não encontra os seus dados.
Configure docker-compose.env antes da primeira inicialização
Duas configurações são obrigatórias. Gere a chave secreta com o comando documentado pelo projeto:
python3 -c "import secrets; print(secrets.token_urlsafe(64))"Depois 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 é fornecido com o valor literal change-me. Essa chave assina tokens de sessão, e as versões atuais tratam esse marcador como não definido: o paperless recusa iniciar quando a chave está ausente, vazia ou continua a ser change-me. Por isso, a stack não inicia enquanto não a substituir. Defina-a antes da primeira inicialização, porque alterá-la mais tarde termina a sessão de todos os utilizadores.
PAPERLESS_URL é a configuração que evita perder uma hora. O Paperless é uma aplicação Django, e o Django verifica tanto o cabeçalho Host como, num formulário de início de sessão, a origem comunicada pelo navegador. Defina PAPERLESS_URL para adicionar automaticamente o seu endereço a ALLOWED_HOSTS, CORS_ALLOWED_HOSTS e CSRF_TRUSTED_ORIGINS. Se o deixar vazio e apontar um domínio para o servidor, a página de início de sessão continua a carregar, porque PAPERLESS_ALLOWED_HOSTS assume por predefinição *. No entanto, o envio do formulário por HTTPS falha com CSRF verification failed. Request aborted., porque o seu domínio não é uma origem confiável. Escreva o valor sem barra final e sem caminho.
USERMAP_UID e USERMAP_GID definem o utilizador com que o contentor é executado. Alinhe-os com a sua própria conta, confirmada com id -u e id -g. Se não coincidirem, os ficheiros que copiar para a pasta de consumo não podem ser lidos pelo consumidor, e o log apresenta um erro de permissões em vez de importar os ficheiros.
Inicie a stack e crie o primeiro utilizador
docker compose pull
docker compose up -d
docker compose run --rm webserver createsuperuser
docker compose logs -f webservercreatesuperuser pede um nome de utilizador, um email e uma palavra-passe. Não existe um início de sessão predefinido. Se ignorar este passo, a interface web pede à primeira pessoa que a abrir para criar o superutilizador. Enquanto a porta 8000 continuar aberta à Internet, essa pessoa pode não ser você. Por isso, crie imediatamente a conta a partir da shell. Aguarde pela linha do log que indica que o servidor está à escuta na porta 8000 antes de abrir o navegador. A primeira inicialização também executa as migrações da base de dados, o que demora um ou dois minutos.
Verifique localmente antes de configurar um domínio:
curl -I http://127.0.0.1:8000Um redirecionamento de 302 para /accounts/login/ significa que a stack está operacional.
Coloque HTTPS na frente da aplicação
O ficheiro Compose padrão publica 8000:8000, que fica associado a todas as interfaces. Num VPS público, isso expõe todo o seu arquivo de documentos por HTTP simples a qualquer pessoa que descubra o endereço. Altere a linha da porta para a associar apenas ao loopback:
ports:
- "127.0.0.1:8000:8000"Termine o TLS (segurança da camada de transporte) num reverse proxy e encaminhe os pedidos para 127.0.0.1:8000. Se esta for a única aplicação no servidor, qualquer proxy com um cliente ACME (ambiente de gestão automática de certificados) será suficiente. Se estiver a executar vários contentores por trás da mesma configuração de certificados, siga o padrão de reverse proxy Traefik para várias aplicações Docker Compose e associe o serviço webserver à rede do proxy, sem publicar qualquer porta.
Independentemente do proxy utilizado, é a verificação da origem do formulário de início de sessão que costuma causar problemas nas configurações HTTPS. O Paperless ignora X-Forwarded-Proto, a menos que defina PAPERLESS_PROXY_SSL_HEADER, que está desativado por predefinição. Assim, o Django trata todos os pedidos encaminhados como HTTP simples, e a origem https:// enviada pelo navegador só é aceite se estiver listada como confiável. Definir PAPERLESS_URL com o endereço https:// exato que escreve no navegador é o que o adiciona a essa lista. Sem essa definição, verá CSRF verification failed. Request aborted. numa página que parece correta.
Aumente também o limite de tamanho dos uploads no proxy. Um scan de 40 MB enviado através de um proxy que limita o corpo dos pedidos a 1 MB é rejeitado antes de o Paperless o receber, e o navegador apresenta um erro genérico de upload.
Como funciona o diretório consume
O ficheiro compose monta ./consume através de bind mount, a partir do diretório compose para dentro do contentor. Tudo o que colocar nesse diretório é importado e depois eliminado da pasta, porque o ficheiro passa a estar no volume de media gerido pelo paperless.
cp ~/scan-2026-07-14.pdf ~/paperless/consume/
docker compose logs -f webserverDeverá ver o consumer detetar o nome do ficheiro, executar o OCR e terminar com uma linha a indicar que o documento foi adicionado. Todo o ciclo demora alguns segundos para uma digitalização de uma página e pode demorar um minuto ou mais para um documento longo.
Duas definições alteram a forma como os ficheiros são encontrados. PAPERLESS_CONSUMER_RECURSIVE=true faz com que o paperless procure em subdiretórios, e PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true transforma o nome de cada subdiretório numa etiqueta. Assim, colocar um ficheiro em consume/invoices/2026/ aplica-lhe as etiquetas invoices e 2026. É o sistema de arquivo mais simples que alguma vez irá criar.
A deteção é a outra parte. Por predefinição, PAPERLESS_CONSUMER_POLLING_INTERVAL é 0, o que significa que o paperless utiliza notificações nativas do sistema de ficheiros, que são acionadas imediatamente. Essas notificações não são fiáveis num sistema de ficheiros de rede. Se o diretório consume for uma partilha NFS ou SMB para que um scanner de rede possa escrever nele, os ficheiros podem permanecer ali sem serem detetados. Nesse caso, defina o intervalo como um número positivo de segundos para que o paperless faça a pesquisa periódica do diretório.
Idiomas de OCR e o custo associado
PAPERLESS_OCR_LANGUAGE recebe um código Tesseract de três letras, eng por predefinição. Combine idiomas com um sinal de mais, como em deu+eng. O Tesseract processa cada página com todos os idiomas indicados, por isso cada idioma adicional aumenta o tempo de CPU de cada digitalização. Num VPS com vCPU partilhada, isto pode fazer a diferença entre uma digitalização terminar em dez segundos ou demorar um minuto. Indique apenas os idiomas em que os seus documentos estão efetivamente escritos.
A imagem inclui inglês, alemão, italiano, espanhol e francês. Para qualquer outro idioma, adicione-o a PAPERLESS_OCR_LANGUAGES como uma lista separada por espaços, por exemplo PAPERLESS_OCR_LANGUAGES=tur ces, e reinicie. O contentor descarrega os pacotes de dados do Tesseract durante o arranque, por isso o primeiro arranque depois dessa alteração é mais lento.
Faça backup do banco de dados e dos ficheiros multimédia
Copiar os volumes do Docker enquanto o PostgreSQL está em execução produz um backup que pode não ser restaurável. O Paperless inclui o seu próprio exportador, que grava os documentos e um manifesto JSON com todos os metadados no bind mount ./export:
docker compose exec -T webserver document_exporter ../export --delete --no-progress-bar--delete remove os ficheiros exportados que já não correspondem a um documento atual, para que a pasta permaneça um espelho em vez de crescer indefinidamente. --no-progress-bar mantém a saída limpa quando isto é executado pelo cron, e -T impede que o Compose peça um terminal que o cron não tem.
A restauração é document_importer na mesma pasta, numa stack nova, o que significa que só precisa de manter o diretório de exportação em segurança. Envie-o para um local externo segundo um agendamento, usando backups restic encriptados e deduplicados do seu VPS, e execute primeiro a exportação para que o restic nunca capture um arquivo escrito apenas parcialmente.
Verifique um backup confirmando que export/manifest.json existe e que a contagem de ficheiros corresponde à contagem de documentos apresentada na interface. Um backup que nunca foi listado não é um backup. Pior ainda é uma exportação noturna que começa a falhar silenciosamente. Por isso, faça o cron enviar o estado de saída para o seu próprio servidor ntfy e saberá na semana em que a falha ocorrer, em vez de só descobrir no dia em que precisar de restaurar.
FAQ
Por que todas as páginas retornam "Bad Request (400)" depois de apontar o meu domínio para a aplicação?
Com os ficheiros padrão, isso não deveria acontecer, porque PAPERLESS_ALLOWED_HOSTS assume * por padrão. Um 400 com DisallowedHost no log do contentor significa que PAPERLESS_ALLOWED_HOSTS foi definido como uma lista que não inclui o seu domínio. Defina PAPERLESS_URL=https://paperless.example.com em docker-compose.env, sem barra final, e o paperless adicionará esse nome de anfitrião à lista, ou remova a linha PAPERLESS_ALLOWED_HOSTS. Depois execute docker compose up -d para recriar o contentor. Editar apenas o ficheiro de ambiente não produz efeito, porque o contentor em execução mantém o ambiente com que foi iniciado.
Coloquei um PDF na pasta consume e nada aconteceu. O que está errado?
Verifique primeiro docker compose logs webserver. Um erro de permissões significa que USERMAP_UID e USERMAP_GID não correspondem à conta proprietária do ficheiro; corrija esses valores e recrie o contentor. A ausência total de uma linha no log significa que o evento do ficheiro nunca chegou ao serviço. Isto acontece em partilhas de rede porque as notificações nativas do sistema de ficheiros não são fiáveis nesse contexto. Defina PAPERLESS_CONSUMER_POLLING_INTERVAL com um valor como 30, e o paperless passará a verificar a pasta a cada 30 segundos.
Posso executar o paperless-ngx com SQLite em vez de PostgreSQL?
Sim, docker-compose.sqlite.yml é suportado e utiliza menos memória, o que é adequado para um VPS pequeno. A desvantagem torna-se evidente à medida que o arquivo cresce: a pesquisa de texto completo e as alterações em massa de etiquetas ficam visivelmente mais lentas quando existem milhares de documentos. Migrar mais tarde exige uma exportação e uma importação. Por isso, escolha PostgreSQL agora se espera que o arquivo continue a crescer.
De quanto espaço em disco precisa realmente um arquivo de documentos digitalizados?
Aproximadamente o dobro do tamanho dos ficheiros de origem. O Paperless mantém o original inalterado e armazena um segundo PDF processado por OCR, com uma camada de texto pesquisável, além de miniaturas pequenas. Um documento digitalizado apenas com texto, de 200 KB, ocupa pouco espaço. Um documento digitalizado a cores, de 30 MB, com um contrato longo, ocupa cerca de 60 MB. Some o diretório de exportação se o mantiver no mesmo disco; nesse caso, o mesmo arquivo ocupa três vezes esse espaço no disco.
Preciso dos contentores Tika e Gotenberg?
Apenas se quiser indexar ficheiros Word, Excel ou OpenDocument juntamente com os PDFs. Eles convertem esses formatos para PDF, para que o paperless possa aplicar OCR e pesquisar o conteúdo. Também acrescentam dois contentores em execução e algumas centenas de megabytes de memória. Por isso, não os use num servidor pequeno se todos os ficheiros arquivados já forem PDFs ou imagens.