Minimalist self-hosted document management platform (Paperless alternative) on FreeBSD.
| Registry | ghcr.io/daemonless/papra |
| Source | https://github.com/papra-hq/papra |
| Website | https://papra.app |
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users — recommended. |
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
services:
papra:
image: "ghcr.io/daemonless/papra:latest"
container_name: papra
environment:
- PUID=1000 # User ID for the application process
- PGID=1000 # Group ID for the application process
- TZ=Etc/UTC # Timezone for the container
- NODE_ENV=production # Node runtime mode; leave as 'production'
- PORT=1222 # Internal node backend port (nginx proxies to it); leave as 1222
- SERVER_HOSTNAME=127.0.0.1 # Internal bind address for the node backend; leave as 127.0.0.1
- SERVER_SERVE_PUBLIC_DIR=false # Whether the node backend serves the SPA itself; 'false' (nginx serves it)
- DATABASE_URL=file:/app_data/db/db.sqlite # SQLite database URL (file:/app_data/db/db.sqlite)
- DOCUMENT_STORAGE_FILESYSTEM_ROOT=/app_data/documents # Filesystem path where uploaded documents are stored (under the /app_data volume)
- PAPRA_CONFIG_DIR=/app_data # Directory Papra reads its config from (under the /app_data volume)
- INGESTION_FOLDER_ROOT=/ingestion # Watched folder for drop-in document ingestion
- EMAILS_DRY_RUN=true # If 'true', emails are logged instead of sent (no SMTP configured by default)
- BETTER_AUTH_TELEMETRY=0 # better-auth telemetry; '0' disables it
- AUTH_SECRET=${PAPRA_AUTH_SECRET} # better-auth session signing secret, >=32 chars. Optional: if unset, the container generates a strong secret on first boot and persists it under /app_data. Set one you control with `openssl rand -hex 48` to manage it yourself.
- AUTH_IS_REGISTRATION_ENABLED=true # Set to false after creating your account to lock down signups
volumes:
- "/containers/papra/app_data:/app_data"
# always (not unless-stopped) so FreeBSD's podman rc.d auto-starts it at boot
restart: alwaysSave as compose.yaml, then run podman-compose up -d.
.env:
# .env
DIRECTOR_PROJECT=papra
PUID=1000
PGID=1000
TZ=Etc/UTC
NODE_ENV=production
PORT=1222
SERVER_HOSTNAME=127.0.0.1
SERVER_SERVE_PUBLIC_DIR=false
DATABASE_URL=file:/app_data/db/db.sqlite
DOCUMENT_STORAGE_FILESYSTEM_ROOT=/app_data/documents
PAPRA_CONFIG_DIR=/app_data
INGESTION_FOLDER_ROOT=/ingestion
EMAILS_DRY_RUN=true
BETTER_AUTH_TELEMETRY=0
AUTH_SECRET=${PAPRA_AUTH_SECRET}
AUTH_IS_REGISTRATION_ENABLED=true
appjail-director.yml:
# appjail-director.yml
options:
- virtualnet: ':<random> default'
- nat:
services:
papra:
name: papra
options:
- container: 'args:--pull'
oci:
user: root
environment:
- PUID: !ENV '${PUID}'
- PGID: !ENV '${PGID}'
- TZ: !ENV '${TZ}'
- NODE_ENV: !ENV '${NODE_ENV}'
- PORT: !ENV '${PORT}'
- SERVER_HOSTNAME: !ENV '${SERVER_HOSTNAME}'
- SERVER_SERVE_PUBLIC_DIR: !ENV '${SERVER_SERVE_PUBLIC_DIR}'
- DATABASE_URL: !ENV '${DATABASE_URL}'
- DOCUMENT_STORAGE_FILESYSTEM_ROOT: !ENV '${DOCUMENT_STORAGE_FILESYSTEM_ROOT}'
- PAPRA_CONFIG_DIR: !ENV '${PAPRA_CONFIG_DIR}'
- INGESTION_FOLDER_ROOT: !ENV '${INGESTION_FOLDER_ROOT}'
- EMAILS_DRY_RUN: !ENV '${EMAILS_DRY_RUN}'
- BETTER_AUTH_TELEMETRY: !ENV '${BETTER_AUTH_TELEMETRY}'
- AUTH_SECRET: !ENV '${AUTH_SECRET}'
- AUTH_IS_REGISTRATION_ENABLED: !ENV '${AUTH_IS_REGISTRATION_ENABLED}'
volumes:
- papra_app_data: /app_data
volumes:
papra_app_data:
device: '/containers/papra/app_data'Makejail:
# Makejail
ARG tag=latest
OPTION container=boot
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/papra:${tag}
Save the files above, then run appjail-director up.
Warning
Bastille's OCI support is experimental. It requires buildah and shares the host network stack (inherit). Mount volumes with --volume HOST JAIL; without it, image-declared volumes are stored under ${bastille_volumesdir}/${jail}.
services:
papra:
name: papra
image: "ghcr.io/daemonless/papra:latest"
network:
- mode: host
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
- NODE_ENV=production
- PORT=1222
- SERVER_HOSTNAME=127.0.0.1
- SERVER_SERVE_PUBLIC_DIR=false
- DATABASE_URL=file:/app_data/db/db.sqlite
- DOCUMENT_STORAGE_FILESYSTEM_ROOT=/app_data/documents
- PAPRA_CONFIG_DIR=/app_data
- INGESTION_FOLDER_ROOT=/ingestion
- EMAILS_DRY_RUN=true
- BETTER_AUTH_TELEMETRY=0
- AUTH_SECRET=${PAPRA_AUTH_SECRET}
- AUTH_IS_REGISTRATION_ENABLED=true
volumes:
- "/containers/papra/app_data:/app_data"Save as bastille-compose.yml, then run bastille up.
| Variable | Default | Description |
|---|---|---|
PUID |
1000 |
User ID for the application process |
PGID |
1000 |
Group ID for the application process |
TZ |
Etc/UTC |
Timezone for the container |
NODE_ENV |
production |
Node runtime mode; leave as 'production' |
PORT |
1222 |
Internal node backend port (nginx proxies to it); leave as 1222 |
SERVER_HOSTNAME |
127.0.0.1 |
Internal bind address for the node backend; leave as 127.0.0.1 |
SERVER_SERVE_PUBLIC_DIR |
false |
Whether the node backend serves the SPA itself; 'false' (nginx serves it) |
DATABASE_URL |
file:/app_data/db/db.sqlite |
SQLite database URL (file:/app_data/db/db.sqlite) |
DOCUMENT_STORAGE_FILESYSTEM_ROOT |
/app_data/documents |
Filesystem path where uploaded documents are stored (under the /app_data volume) |
PAPRA_CONFIG_DIR |
/app_data |
Directory Papra reads its config from (under the /app_data volume) |
INGESTION_FOLDER_ROOT |
/ingestion |
Watched folder for drop-in document ingestion |
EMAILS_DRY_RUN |
true |
If 'true', emails are logged instead of sent (no SMTP configured by default) |
BETTER_AUTH_TELEMETRY |
0 |
better-auth telemetry; '0' disables it |
AUTH_SECRET |
${PAPRA_AUTH_SECRET} |
better-auth session signing secret, >=32 chars. Optional: if unset, the container generates a strong secret on first boot and persists it under /app_data. Set one you control with openssl rand -hex 48 to manage it yourself. |
AUTH_IS_REGISTRATION_ENABLED |
true |
Set to false after creating your account to lock down signups |
| Path | Description |
|---|---|
/app_data |
Application data — SQLite database, stored documents, and config |
Architectures: amd64
User: bsd (UID/GID via PUID/PGID, defaults to 1000:1000)
Base: FreeBSD 15
Need help? Join our Discord community.