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 o que cada um faz torna os logs mais fáceis de interpretar.
webserver: a própria imagem do paperless-ngx. Executa a interface Web, a API, o consumer que monitoriza a sua pasta de entrada e os workers de tarefas Celery que fazem OCR.db: PostgreSQL. Armazena metadados, etiquetas, correspondentes e as tabelas do índice de pesquisa de texto completo. Não armazena os seus PDFs.broker: Valkey, um armazenamento de pares chave-valor compatível com Redis. É a fila de tarefas entre o processo Web e os workers.gotenbergetika: opcionais, apenas nas variantes de compose-tika. Convertem documentos do Office (.docx,.xlsx,.odt) para PDF, para que o paperless os possa indexar.
Em julho 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 a partir de ghcr.io/paperless-ngx/paperless-ngx:latest.
Pré-requisitos
- Uma VPS KVM com Ubuntu 24.04, acesso sudo e Docker com o plugin Compose já instalado. Se esta parte for nova para si, comece por os fundamentos do Docker Compose para uma VPS e depois volte aqui.
- Um nome de domínio com um registo A a apontar para a VPS. O Paperless recusa servir num hostname que não tenha sido configurado, por isso isto é importante mais cedo do que pode esperar.
- A memória é a principal limitação. PostgreSQL, Valkey, gunicorn e um worker de OCR do Tesseract, todos residentes ao mesmo tempo, cabem em 2 GB para uma utilização ligeira. Atribua 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 memória que pode levar o kernel a terminar um worker através do out-of-memory killer.
- 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 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 o 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 vem com o valor literal change-me. Esta chave assina os cookies de sessão. Se a mantiver, qualquer pessoa que conheça o valor predefinido poderá forjar uma sessão. 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 valida o cabeçalho Host de todos os pedidos. Defina PAPERLESS_URL para que os valores ALLOWED_HOSTS, CORS_ALLOWED_HOSTS e CSRF_TRUSTED_ORIGINS sejam preenchidos automaticamente. Se o deixar vazio, aponte um domínio para o servidor e todas as páginas devolverão Bad Request (400), com DisallowedHost no log do contentor. Escreva o valor sem uma barra final e sem um caminho.
USERMAP_UID e USERMAP_GID definem o utilizador com que o contentor é executado. Faça-os corresponder à sua própria conta, confirmada com id -u e id -g. Se não corresponderem, os ficheiros copiados para a pasta de consumo não poderão ser lidos pelo consumidor, e o log mostrará um erro de permissões em vez de uma importação.
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, por isso, se ignorar este passo, ficará numa página de início de sessão que nunca aceitará nenhum acesso. Aguarde pela linha do log que indica que o servidor está a escutar 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 envolver um domínio:
curl -I http://127.0.0.1:8000Um redirecionamento de 302 para /accounts/login/ significa que a stack está saudável.
Colocar HTTPS na frente do serviço
O ficheiro compose padrão publica 8000:8000, que fica associado a todas as interfaces. Num VPS público, isso disponibiliza todo o seu arquivo de documentos por HTTP sem encriptação a qualquer pessoa que descubra o endereço. Altere a linha da porta para ficar associada apenas ao loopback:
ports:
- "127.0.0.1:8000:8000"Em seguida, 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 com uma única 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, ele tem de enviar X-Forwarded-Proto: https. Sem esse cabeçalho, o Django considera que o pedido chegou por HTTP, a verificação da origem no formulário de início de sessão falha e aparece CSRF verification failed. Request aborted. numa página que parece correta. A outra parte da correção consiste em definir PAPERLESS_URL exatamente para o endereço https:// que escreve no navegador.
Aumente também o limite de tamanho dos uploads no proxy. Uma digitalização de 40 MB enviada através de um proxy que limita o corpo dos pedidos a 1 MB é rejeitada antes de o paperless a receber, e o navegador apresenta um erro genérico de upload.
Como funciona o diretório consume
O ficheiro compose monta ./consume no container através de um bind mount a partir do diretório do compose. Tudo o que colocar nesse diretório é importado e depois eliminado da pasta, porque o ficheiro passa a existir no volume de media sob a gestão do 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 o paperless procurar em subdiretórios, e PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS=true transforma o nome de cada subdiretório numa tag. Assim, colocar um ficheiro em consume/invoices/2026/ atribui-lhe as tags invoices e 2026. É o sistema de arquivo mais barato que alguma vez vai criar.
A deteção é a outra parte. Por predefinição, PAPERLESS_CONSUMER_POLLING_INTERVAL é 0, o que significa que o paperless usa notificações do sistema de ficheiros do kernel, acionadas imediatamente. Essas notificações não atravessam um 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, nada será detetado. A correção consiste em definir o intervalo como um número positivo de segundos, para que o paperless faça a verificação periódica do diretório.
Idiomas de OCR e respetivos custos
PAPERLESS_OCR_LANGUAGE aceita 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 tenta cada idioma e mantém o melhor resultado. Por isso, cada idioma adicional multiplica o tempo de CPU gasto em cada página. Numa VPS com vCPU partilhada, isso pode fazer a diferença entre terminar uma digitalização em dez segundos ou em um minuto. Liste 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 transfere os pacotes de dados do Tesseract durante o arranque. Por isso, o primeiro arranque depois dessa alteração é mais demorado.
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 restaurado. 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 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.
A restauração é document_importer nessa 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 fora do servidor 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 parcialmente gravado.
Verifique um backup confirmando que export/manifest.json existe e que a contagem de ficheiros corresponde à contagem de documentos na interface. Um backup que nunca foi listado não é um backup. Pior ainda é uma exportação noturna que começa a falhar silenciosamente. Faça o cron job enviar o respetivo estado de saída para o seu próprio servidor ntfy. Assim, saberá na semana em que falha, em vez de 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?
O Django rejeitou o cabeçalho Host porque o seu domínio não está em ALLOWED_HOSTS. Defina PAPERLESS_URL=https://paperless.example.com em docker-compose.env, sem uma barra no final, e execute docker compose up -d para recriar o contentor. Editar apenas o ficheiro de ambiente não tem 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-os 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 do kernel não atravessam essas partilhas. Defina PAPERLESS_CONSUMER_POLLING_INTERVAL para algo 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 usa menos memória, o que é adequado para um VPS pequeno. A desvantagem torna-se visível à medida que o arquivo cresce: a pesquisa de texto completo e as alterações em massa de etiquetas ficam significativamente mais lentas quando há 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 digitalizações?
Aproximadamente o dobro do tamanho dos ficheiros de origem. O Paperless mantém o original inalterado e guarda um segundo PDF com OCR e uma camada de texto pesquisável, além de pequenas miniaturas. Uma digitalização apenas com texto de 200 KB continua pequena. Uma digitalização a cores de 30 MB de um contrato longo ocupa cerca de 60 MB. Se mantiver o diretório de exportação no mesmo disco, acrescente esse espaço. Nesse caso, o mesmo arquivo ocupará três vezes o espaço em 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 executar OCR e pesquisá-los. Também acrescentam mais dois contentores em execução e algumas centenas de megabytes de memória. Por isso, não os use num servidor pequeno se tudo o que arquiva já for um PDF ou uma imagem.