TPTito Pinardo Gutiérrez
← Back to the rack
Español
1U · PAPERLESSPaperless-ngx · OCR · no SSH In production

Slot 7 of 25 in the rack · In production

1U unit · Personal documents

Paperless-ngx · the home archive

137 documents migrated 1:1

I scan a paper and Paperless reads it with OCR, gives it a title, tags and type with help from a local model, and files it where I can find it by searching. These are identity and property documents, so it's the most locked-down container on the server.

Why so locked down
It isn't ordinary paperwork: land registry, residence certificates, official documents. If anything in the homelab leaks, it mustn't be this.
How you get in
From home or the VPN, with two-factor at the internal door, over the container's own HTTPS. The service itself only listens inwards.
How it's managed
It has no SSH and no users. Ansible goes in through the hypervisor, and the firewall drops everything else.
  • 0SSH ports and accounts in the container
  • 137documents migrated and checked one by one
  • 3sources allowed by the firewall
  • spa+engOCR in Spanish and English

Its code, in the open Public version of the repo: no IPs or secrets.

Inside the archive

Papers that mustn't leave home live here. Pick a flow to see how many doors you have to go through to reach them.

Hermesvisiting the unit

Click a part or pick a flow. If you came from a skill, the parts where I use it are already lit.

      I open a document
      1. My laptop — From home I open the archive.
      2. Caddy — The internal door checks I'm coming from the home network.
      3. Authelia — Authelia asks for password and code.
      4. Caddy — With the session, Caddy carries on.
      5. Container firewall — The container firewall only lets Caddy through.
      6. nginx in the container — nginx terminates TLS inside the container.
      7. Paperless on localhost only — And passes the request to Paperless, which only listens inwards.
      8. Paperless-ngx — The app receives the request.
      9. Paperless login — Its own login, the last key.
      10. My laptop — I see the document.
      A new paper comes in
      1. My laptop — I upload a scan.
      2. Paperless-ngx — Paperless receives it.
      3. Redis — The task goes into the queue.
      4. OCR and classification — OCR extracts the text, in Spanish and English.
      5. Local model (Ollama) — The local model suggests title, tags and type.
      6. OCR and classification — The automatic workflow applies the classification.
      7. PostgreSQL — Everything is indexed in PostgreSQL.
      8. Paperless-ngx — And I find it by searching any word it contains.
      Ansible manages it
      1. Encrypted secrets — Ansible takes the credentials from the encrypted vault.
      2. Ansible through the hypervisor — It connects to the hypervisor, not the container.
      3. Hypervisor — And runs the tasks inside with «pct exec».
      4. No SSH — The container has no SSH: there's no other way in.
      5. Paperless-ngx — Paperless ends up updated and configured.
      The migration to code
      1. Terraform — Terraform creates the new container, with no accounts.
      2. Database dump — On the old one, a database dump and an archive of the files, opening nothing.
      3. Hypervisor — They move through the hypervisor.
      4. Database dump — They're restored into a clean database.
      5. PostgreSQL — Checked: 137 documents and the same size in both.
      6. Paperless-ngx — The app answers through the same door, on the same IP.
      7. Zabbix — Monitoring registered again.
      8. Wazuh — And security, with a new agent.
      9. The old container — The old one, stopped but not deleted.
      Someone on the network tries
      1. My laptop — A device on the network tries to connect to the container.
      2. Container firewall — The firewall drops everything that isn't the internal door or monitoring.
      3. My laptop — It tries the app directly.
      4. Paperless on localhost only — It only listens on localhost: there's nothing on the other side.
      1. My laptop

        From home or with the VPN.

      2. Caddy

        Only accepts the home network and validates the container's certificate before passing anything on.

      3. Authelia

        Two-factor login before reaching Paperless.

        • Linux / SSH hardening
      4. My GPU PC

        Where the local language model runs.

      5. Local model (Ollama)

        A vision model and an embedding model. The PC only accepts connections from this container on that port.

        • LLM locales (Ollama, Qdrant, RAG)
      6. Hypervisor

        The only way into the container: «pct exec» from Proxmox itself.

        • Proxmox VE
      7. Container firewall

        Inbound policy: drop. Only the internal door and monitoring reach the HTTPS, plus ping.

        • Proxmox VE
        • Linux / SSH hardening
      8. nginx in the container

        Terminates TLS inside the container itself: not even inside the house do the documents travel unencrypted.

        • Linux / SSH hardening
      9. Paperless on localhost only

        The app only listens inside the container. From the network you can't reach it without going through nginx.

        • Docker
        • Linux / SSH hardening
      10. Paperless login

        Behind everything, its own user. One more key.

        • Linux / SSH hardening
      11. Paperless-ngx

        The official app, on Docker. It classifies, searches and stores.

        • Docker
      12. OCR and classification

        Reads the text of each scan and suggests title, tags and type with the local model. An automatic workflow applies it on arrival.

        • Docker
      13. PostgreSQL

        Metadata, tags and the search index.

        • Docker
        • PostgreSQL
      14. Redis

        The task queue: each document's OCR runs in the background.

        • Docker
        • Redis
      15. Terraform

        Creates the container with no user account at all: the only one in the repo like that.

        • Terraform
      16. Ansible through the hypervisor

        A connection plugin that talks to the hypervisor and runs inside the container. There's never SSH to the container.

        • Ansible
        • Proxmox VE
      17. No SSH

        No service, no keys, no firewall rule. Nothing to attack that way.

        • Linux / SSH hardening
      18. Encrypted secrets

        Credentials live encrypted in the Ansible vault, never in the documentation.

        • Ansible
      19. Zabbix

        Checks the HTTPS answers. It's one of the three allowed sources.

        • Zabbix
      20. Wazuh

        Watches for changes in the stack's configuration.

        • Wazuh
      21. Database dump

        The migration used a database dump and an archive of the files, without opening any document.

        • PostgreSQL
      22. The old container

        Stopped, not destroyed: a safety net until there are regular backups.

      What I use from my skill tree

      Pick a skill: the parts where I use it light up on the map and I tell you what for.

      Terraform
      Creates the container with no user account.
      Ansible
      Deploys the stack through the hypervisor and keeps credentials in the vault.
      Proxmox VE
      The container firewall and management with «pct exec» from the hypervisor.
      LXC
      An unprivileged container with nesting so it can run Docker.
      Docker
      Paperless, its database and its queue, published only inwards.
      Zabbix
      Checks the container's HTTPS answers.
      Wazuh
      Watches for configuration changes.
      Linux / SSH hardening
      Drop by default, no SSH, TLS inside the container, two-factor in front and its own login behind.
      PostgreSQL
      Paperless' database, migrated with a dump without opening documents.
      Redis
      The queue where OCR jobs wait.
      LLM locales (Ollama, Qdrant, RAG)
      A local model suggests title, tags and type: documents don't leave home.

      Incident log

      Real failures. Read the symptom, pick the cause you think it was and see how it was fixed.

      1. #1

        An SSH that shouldn't have been there

        17-09-2026 · audit

        The design said «no SSH». Before migrating, I checked the container as it actually was.

        What do you think was going on?

        Show the fix
        Cause
        At some point SSH access was added to fix something and never removed, nor written into the documentation. Design and reality had drifted apart.
        Fix
        The new container is born from Terraform with no accounts and Ansible manages it through the hypervisor: there's no SSH left for anyone to reopen for convenience.
        Lesson
        Documentation says what you meant to do; the system, what there is. Before trusting the design, check the real state.
      2. #2

        Ansible couldn't write the firewall

        17-09-2026 · migration

        The container's firewall file couldn't be written from Ansible: «Operation not permitted», neither with the normal copy nor the direct one.

        What do you think was going on?

        Show the fix
        Cause
        The firewall configuration lives on Proxmox's special file system. Ansible writes a temporary file and renames it, and that file system doesn't allow it.
        Fix
        A task that compares the content and, only if it changed, writes it with a plain redirect. Still idempotent.
        Lesson
        Tools assume a normal file system. When it isn't, keep what the tool guarantees (idempotence) even if you have to do it another way.

      How it was built

      1. 09-09-2026

        Deployment

        • Paperless with Spanish OCR, behind the internal door.
        • Local AI for classification, with the model on my PC.
      2. 17-09-2026

        Migration to code

        • A new container with Terraform, no accounts, and Ansible through the hypervisor.
        • A drop-by-default firewall with three allowed sources.
        • Data migrated without opening any document and checked one by one.

      Decisions (and why)

      • How it's managed

        No SSH, through the hypervisor

        Whoever reaches the hypervisor already has everything; opening SSH on the container as well only adds a door.

      • Where it's encrypted

        Inside the container itself

        The internal door already encrypts, but the hop to the container also crosses the network. With nginx inside, documents never travel in the clear.

      • The old container

        Stopped, not destroyed

        With no regular backups yet, the original is the way back. It gets deleted once there's a real backup.

      What I learned

      • The most sensitive things are designed from the «no»: what must not be able to happen.
      • Check the real state before migrating: an SSH turned up that wasn't on any paper.

      What's next

      • Regular encrypted backups off the server; then delete the old container.
      • Turn on Paperless' own second factor.

      Shall we talk?

      I can walk through any of these decisions in detail in an interview.

      Download CV Get in touch ← Back to the rack

      Rebuilt from my documentation. No document was opened for this and none appears here.