Sign inSign up

mertdalbudak/pluma

By mertdalbudak

Updated about 2 months ago

Self-hosted Pluma — multi-tenant PDF template builder + renderer

Image
1

10K+

mertdalbudak/pluma repository overview

Pluma — self-hosted

Multi-tenant PDF template builder + renderer in one container. Design templates in a browser, fill them via a REST API, get back rendered PDFs. Stateless renderer, durable builder, no SaaS dependency.

This is the self-hosted image. It runs offline against a license JWT you paste into one env var.


Quick start

docker-compose.yml
services:
  pluma:
    image: mertdalbudak/pluma:latest
    container_name: pluma
    restart: unless-stopped
    env_file: .env
    ports:
      - "4100:4100"
    volumes:
      - builder-data:/app/builder/data
      # Lets the Pro+ Publish flow drive `docker buildx`. Remove this line
      # if you won't publish workspace renderer images.
      - /var/run/docker.sock:/var/run/docker.sock
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:4100/health"]
      interval: 30s
      timeout: 5s
      retries: 5

volumes:
  builder-data:
.env
PUBLIC_URL=http://localhost:4100

# Paid tiers only — leave blank for Free
LICENSE_KEY=

Open http://localhost:4100. The first person to register becomes the account owner. After that, sign-up is invite-only.

Health check: curl http://localhost:4100/health{"ok":true,"service":"builder"}


What you need to set

EnvRequiredNotes
PUBLIC_URLyesWhere the install lives. Used in invite links, emails, OAuth callbacks.
LICENSE_KEYpaid tiers onlyThe single-line JWT you got from us. Leave blank for Free.
DATABASE_URLoptionalpostgresql://USER:PASS@host:5432/pluma. Leave unset to use bundled SQLite.

The session secret, internal-API secret, and three encryption keys are generated automatically on first boot and persisted to the volume. You don't set them.


Licensing

The license is an EdDSA-signed JWT (RFC 7519). One single-line opaque string, no JSON wrapping, no files to mount.

Two ways to install it:

1. Env var (simplest)

LICENSE_KEY=eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwbHVtYS1zZWxmaG9zdGVkIi…

Set, restart, done.

2. GUI — open Settings → License, paste, save. Rotates without a redeploy.

The signature is verified locally against a public key embedded in the image. The install works during network outages. Once a day the builder POSTs { sha256(license_jwt), pluma_version } to our license API so we can flag revoked or stolen keys; the raw key never leaves the box. A 14-day grace window means a server outage on our side never breaks an online install.

If you've contracted a fully air-gapped install, set PLUMA_LICENSE_HEARTBEAT=off.


Free vs. paid

  • Free. 5 templates, 1 admin, 5 users, no S3 backup, no Publish (renderer images). Runs forever without a license.
  • Advanced, Pro, Enterprise. Higher caps, S3 backup, Publish, custom fonts. Buy a license at https://plumapdf.com/pricing.

The license never gates PDF generation. An expired support window stops auto-update checks and the support portal — it never stops rendering.


Upgrading

docker compose pull
docker compose up -d

Migrations run on boot. The image is backwards-compatible across minor versions.


Volumes & backup

The builder-data volume holds: SQLite DB (if used), uploaded PDFs/images, persisted internal secrets, license state. Snapshot it however you snapshot the rest of your stack.

If you use external Postgres + S3, the container itself is stateless except for those internal secrets — back the volume up anyway so a fresh install can read the same data.


Troubleshooting

SymptomFix
/api/runtime returns mode:"hosted"You set PLUMA_DEPLOYMENT_MODE=hosted somewhere — remove it. The self-hosted image is self-hosted by default.
License signature could not be verifiedWrong key, or copy-paste dropped a char. Re-copy from the portal; the key starts with eyJ and is one continuous line.
Set LICENSE_SIGNING_KEY … on the serverThat message is from the hosted issuer side, not this image. You're not supposed to see it on the self-hosted box.
Renderer returns renderer_unreachableRenderd crashed inside the container. Check docker compose logs pluma; restart with docker compose restart pluma.

Full guide: https://plumapdf.com/docs/self-host — covers Postgres, S3, OAuth, backups, multi-host topology, all of it.


License (of the software)

Source available, commercial. See LICENSE.md in the repo. For partner / OEM redistribution, talk to us.

Tag summary

Content type

Image

Digest

sha256:816189cb4

Size

440.1 MB

Last updated

about 2 months ago

docker pull mertdalbudak/pluma