Skip to content

Storage and the database

Paprika keeps its records in PostgreSQL and the bytes they describe on the data volume. Both are needed: a backup of one without the other can’t be restored.

The API, the workers and the one-off services connect with PAPRIKA_DATABASE_URL. The Compose file runs PostgreSQL 18 in the db service, with its files in the db volume; to use a database server of your own, point PAPRIKA_DATABASE_URL at it and leave the db service out.

The schema is brought up to date by the migrate command, which the Compose file runs before the API starts. Migrations only ever move forward, and take a lock so that two at once can’t collide. An API whose database is behind its schema answers /ready with 503 until it has been migrated.

Paprika makes a database role of its own, paprika_app, and runs every tenant’s queries as it, so that the database itself keeps tenants apart (security). The user in PAPRIKA_DATABASE_URL needs the rights to create that role and the tables: the owner of the database.

PAPRIKA_STORAGE=disk keeps the bytes under PAPRIKA_STORAGE_DIR, /data in the image: uploaded fonts, images and colour profiles, template packages, records files for batches and finished outputs, each under tenants/<tenant>/. The api and every worker must share it.

An hourly sweep, run by one API at a time, removes what is past its time:

What Kept Setting
Uploaded fonts, images and profiles 30 days since last used the tenant’s retentionDays (admin API)
Outputs (rendered files) 24 hours PAPRIKA_OUTPUT_RETENTION_HOURS
Finished jobs, batches and their records 7 days PAPRIKA_JOB_RETENTION_DAYS
Webhook deliveries 7 days PAPRIKA_JOB_RETENTION_DAYS
The audit log 400 days
Usage events and storage measures 400 days, also past their tenant’s deletion, for its last bill PAPRIKA_USAGE_KEEP_DAYS
A deleted tenant 30 days, while it can be restored; then every row and file of it is purged
Templates and their versions until deleted

A tenant’s library fonts and named resources stay while it has them.

Back up the database and the data volume together, the database first: a file the database doesn’t list is harmless, a listed file that’s missing is not. With the Compose file:

Terminal window
docker compose exec db pg_dump -U paprika -Fc paprika > paprika.dump
docker run --rm -v paprika_data:/data -v "$PWD":/backup busybox tar czf /backup/paprika-data.tgz -C /data .

The keys volume needs no backup: editor-key issues a new key when the old one no longer works.

What one request may send is limited, to keep a single tenant from tying up the service:

Limit Default Setting
A template package 100 MB PAPRIKA_MAX_PACKAGE_MB
A package, unpacked 250 MB PAPRIKA_MAX_EXPANDED_MB
An image, in pixels 40 megapixels PAPRIKA_MAX_IMAGE_MEGAPIXELS
Jobs queued or running per tenant 1,000 PAPRIKA_MAX_QUEUED_JOBS
Records in a batch 10,000 PAPRIKA_MAX_BATCH_RECORDS
Batches open per tenant 10 PAPRIKA_MAX_OPEN_BATCHES
Records merged into one PDF 1,000 PAPRIKA_MAX_MERGED_RECORDS
A batch’s combined output 1,000 MB PAPRIKA_MAX_COMBINED_MB

The configuration reference lists them all.