Skip to content

Papra

Description / nameInput element
Container Registry
Container Configuration Root Path
Timezone
User ID
Group ID
Papra /app-data Path

Build Status Last Commit

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'
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=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 to 1000:1000.
  • Base: Built on ghcr.io/daemonless/base (FreeBSD 15.0).

Need help? Join our Discord community.