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. Matches Linux Docker behavior. |
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:
- "/path/to/containers/papra/app_data:/app_data"
restart: unless-stopped.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:
options:
- virtualnet: ':<random> default'
- nat:
services:
papra:
name: papra
options:
- container: 'boot 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: '/path/to/containers/papra/app_data'Makejail:
ARG tag=latest
OPTION overwrite=force
OPTION from=ghcr.io/daemonless/papra:${tag}
podman run -d --name papra \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=Etc/UTC \
-e NODE_ENV=production \
-e PORT=1222 \
-e SERVER_HOSTNAME=127.0.0.1 \
-e SERVER_SERVE_PUBLIC_DIR=false \
-e DATABASE_URL=file:/app_data/db/db.sqlite \
-e DOCUMENT_STORAGE_FILESYSTEM_ROOT=/app_data/documents \
-e PAPRA_CONFIG_DIR=/app_data \
-e INGESTION_FOLDER_ROOT=/ingestion \
-e EMAILS_DRY_RUN=true \
-e BETTER_AUTH_TELEMETRY=0 \
-e AUTH_SECRET=${PAPRA_AUTH_SECRET} \
-e AUTH_IS_REGISTRATION_ENABLED=true \
-v /path/to/containers/papra/app_data:/app_data \
ghcr.io/daemonless/papra:latest- name: Deploy papra
containers.podman.podman_container:
name: papra
image: "ghcr.io/daemonless/papra:latest"
state: started
restart_policy: always
env:
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:
- "/path/to/containers/papra/app_data:/app_data"| 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.0
Need help? Join our Discord community.