Garabato

Sign your PDFs with your own certificate.

Garabato is a self-hosted app for signing PDFs with your PKCS#12 digital certificates. Drop a file, place the visible signature, sign and download. Certificates and documents are stored encrypted, and every signature becomes a new version.

Install on a server
curl -fsSL https://raw.githubusercontent.com/Nonetss/garabato/main/scripts/bootstrap.sh | bash

Run it in an empty directory. It asks a few questions, generates every secret and starts the stack with Docker Compose.

garabato.example.com/

What you get

  1. Place the signature and sign.

    Pick a certificate, choose a visible or invisible signature, drag a rectangle on the page and add an optional reason and place. The signature is PAdES with the certificate chain embedded, and with a time-stamping authority configured it carries a timestamp that proves when it was made.

    garabato.example.com/documents/:id
  2. Every signature is a new version.

    Signing never overwrites a document. The viewer shows the visible stamps, the version history, each one downloadable, and who signed with which certificate.

    garabato.example.com/documents/:id
  3. A library that stays tidy.

    PDF thumbnails in nested folders with their own icon, colored tags and pinned documents. Search and filter across the whole library, select several at once and move them by dragging.

    garabato.example.com/documents
  4. Your certificates, kept encrypted.

    Import a PKCS#12 file once and sign with it from then on. The file and its password are sealed with their own key, and the list shows the holder, tax id, issuer and how long each one is still valid.

    garabato.example.com/certificates
  5. A trace of everything you do.

    Every signature with its document, certificate, version, placement and hashes, next to every import, upload, merge, page edit, download, rename, move and deletion, filterable by type, certificate, name and dates.

    garabato.example.com/traces

And the rest of the app

Search everywhere
⌘K or Ctrl+K jumps to any page, document or folder of the library.
Light, dark and mobile
Light, dark or system theme, a layout that works on a phone, and an installable PWA.
Sign-in your way
Email and password, plus any OIDC provider (Keycloak, Authentik, Google…) turned on with three variables.
Admin panel
Users, sessions, organizations and teams, API keys and an activity log, for the admin role only.
A typed API
Every procedure is also plain HTTP under /api/v1, documented at /scalar for admins. Call it from scripts with an API key.
Your storage
Documents go to the bundled MinIO or to any S3-compatible store you already run.

How it fits together

Three app images plus PostgreSQL and object storage, run with Docker Compose. Browsers only talk to the Caddy gateway, which sends the API to the backend and everything else to the frontend. The backend owns the database and the document store.

Request paths

Opening a page
  1. Browser
    → HTTPS
  2. Gateway:80
    Caddy
    → /*
  3. Frontend:4321
    Astro SSR
Signing a document
  1. Browser
    → /rpc
  2. Gateway:80
    Caddy
  3. Backend:3000
    Hono · oRPC
    PostgreSQL
    → S3
  4. Object storage:9000
    MinIO · S3

Gateway routing

RequestGoes to
/rpc/* /api/* /scalar* /openapi.jsonbackend:3000
/healthok
any other pathfrontend:4321

Only the gateway's port 80 is published, as FRONTEND_PORT (4444 by default). The bundled MinIO's console listens on the host's loopback only, and the rest of the services are reachable only inside the Compose network.

Everything sensitive is encrypted at rest.
Each certificate and document gets its own random data key, wrapped by the master key in CERTIFICATE_ENCRYPTION_KEY. PKCS#12 files, their passwords and every PDF version are sealed with AES-256-GCM, bound to the record and slot they belong to.
Signatures a validator can check.
Garabato signs PAdES baseline (ETSI.CAdES.detached) with the signing-certificate-v2 attribute and embeds the issuer chain, so a validator can build the path to the certificate authority. Set TSA_URL and every signature also gets an RFC 3161 timestamp (B-T), or fails rather than be stored without one.
Nothing is overwritten.
A signature produces a new version of the document and a signature record, listed in the traces. Earlier versions stay downloadable, and another user's documents and certificates answer as not found.
Read the architecture docs

Up in one command.

On a Linux host with Docker, curl and openssl, from an empty directory:

curl -fsSL https://raw.githubusercontent.com/Nonetss/garabato/main/scripts/bootstrap.sh | bash

Back up the generated .env, above all CERTIFICATE_ENCRYPTION_KEY, together with the database and the documents. Losing it makes every stored certificate and document unrecoverable.

Prefer to set it up by hand? Deploy with Docker Compose

What the script does

  1. 1Asks for the port, the public URL and the first admin, and whether documents go to the bundled MinIO or to an external S3-compatible store.
  2. 2Generates every secret with openssl, including the master key that encrypts certificates and documents.
  3. 3Writes .env and downloads compose.prod.yml into the current directory.
  4. 4Starts the stack with the images from ghcr.io. The backend migrates the database and creates the admin on startup.
garabato.example.com