Papra
| Description / name | Input element |
|---|---|
| Container Registry | |
| Container Configuration Root Path | |
| Timezone | |
| User ID | |
| Group ID | |
| Papra /app-data Path |
Minimalist self-hosted document management platform (Paperless alternative) on FreeBSD.
| Registry | ghcr.io/daemonless/papra |
| Daemonless | daemonless/papra |
| Source | papra-hq/papra |
| Website | papra.app |
Version Tags
| Tag | Description | Best For |
|---|---|---|
latest |
Upstream Binary. Built from official release. | Most users. Matches Linux Docker behavior. |
Root Privileges Required
Podman on FreeBSD currently requires root. All commands must be run as root (or via doas/sudo).
Before deploying, ensure your host environment is ready. See the Quick Start Guide for host setup instructions.
Deployment
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=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
DIRECTOR_PROJECT=papra
PUID=1000
PGID=1000
TZ=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
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_PATH: /app-data
volumes:
PAPRA_APP_DATA_PATH:
device: '/path/to/containers/papra/app-data'
podman run -d --name papra \
-e PUID=1000 \
-e PGID=1000 \
-e TZ=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: "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"
Interactive Configuration
Parameters
Environment Variables
| 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 |
Volumes
| Path | Description |
|---|---|
/app-data |
Application data — SQLite database, stored documents, and config |
Implementation Details
- Architectures: amd64
- User:
bsd(UID/GID set via PUID/PGID). Defaults to1000:1000. - Base: Built on
ghcr.io/daemonless/base(FreeBSD 15.0).
Need help? Join our Discord community.