papra Documentation

The minimalistic document archiving platform.

5,038 stars TypeScript TypeScript 1 file ยท ~8,954 tokens #app#archive#document#documents#self-hosted
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy papra on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name papra -p 1221:1221 --restart always papra-hq/papra:latest

File: README.md

Papra - Document management platform

Introduction

Papra is a minimalistic document management and archiving platform. It is designed to be simple to use and accessible to everyone. Papra is a platform for long-term document storage and management, like a digital archive for your documents.

Forget about that receipt of that gift you bought for your friend last year, or that warranty for your new phone. With Papra, you can easily store, forget, and retrieve your documents whenever you need them.

A live demo of the platform is available at demo.papra.app (no backend, client-side local storage only).

Features

  • Document management: Upload, store, and manage your documents in one place.
  • Organizations: Create organizations to manage documents with family, friends, or colleagues.
  • Search: Quickly search for documents with full-text search and advanced filters.
  • Authentication: User accounts and authentication.
  • Dark Mode: A dark theme for those late-night document management sessions.
  • Responsive Design: Works on all devices, from desktops to mobile phones.
  • Open Source: The project is open-source and free to use.
  • Self-hosting: Host your own instance of Papra using Docker or other methods.
  • Tags: Organize your documents with tags.
  • Email ingestion: Send/forward emails to a generated address to automatically import documents.
  • Content extraction: Automatically extract text from images or scanned documents for search.
  • Tagging Rules: Automatically tag documents based on custom rules.
  • Folder ingestion: Automatically import documents from a folder.
  • CLI: Manage your documents from the command line.
  • API, SDK and webhooks: Build your own applications on top of Papra.
  • i18n: Support for multiple languages.
  • Custom properties: Define per-organization custom properties to store additional information about documents.
  • Document sharing: Share documents with external users with optional expiration dates and password protection.
  • Coming soon: Document requests: Generate upload links for people to add documents.
  • Coming maybe one day: Mobile app: Access and upload documents on the go.
  • Coming maybe one day: Desktop app: Access and upload documents from your computer.
  • Coming maybe one day: Browser extension: Upload documents from your browser.
  • Coming maybe one day: AI: Use AI to help you manage or tag your documents.

Support

Papra is a free and open-source project, but it is not free to run and develop. If you want to support the project, you can become a sponsor on GitHub Sponsors or Buy me a coffee.
If you are a company, you can also contact me to discuss a sponsorship.

Self-hosting

Papra is dedicated to providing a simple yet highly configurable self-hosting experience.

For a quick start, simply run the following command:

bash
docker run -d --name papra -p 1221:1221 -e AUTH_SECRET=a-dummy-secret-for-testing-purposes-only ghcr.io/papra-hq/papra:latest

The AUTH_SECRET above is fine for kicking the tires, but for any real instance you should generate your own with openssl rand -hex 48 (or similar).

Please refer to the self-hosting documentation for more information and configuration options.

Contributing

Contributions are welcome! Please refer to the CONTRIBUTING.md file for guidelines on how to get started, report issues, and submit pull requests.
You can find easy-to-pick-up tasks with the good first issue or PR welcome labels.

License

This project is licensed under the AGPL-3.0 License - see the LICENSE file for details.

Community

Join the community on Papra's Discord server to discuss the project, ask questions, or get help.

Credits

This project is crafted with โค๏ธ by Corentin Thomasset.
If you find this project helpful, please consider supporting my work.

Acknowledgements

Stack

Papra would not have been possible without the following open-source projects:

  • Frontend
    • SolidJS: A declarative JavaScript library for building user interfaces.
    • Shadcn Solid: UI components library for SolidJS based on Shadcn designs.
    • UnoCSS: An instant on-demand atomic CSS engine.
    • Tabler Icons: A set of open-source icons.
    • And other dependencies listed in the client package.json
  • Backend
    • HonoJS: A small, fast, and lightweight web framework for building APIs.
    • Drizzle: A simple and lightweight ORM for Node.js.
    • Better Auth: A simple and lightweight authentication library for Node.js.
    • CadenceMQ: A self-hosted-friendly job queue for Node.js, made by Papra.
    • And other dependencies listed in the server package.json
  • Documentation
  • Project
  • Infrastructure

Inspiration

This project would not have been possible without the inspiration and work of others. Here are some projects that inspired me:

Sponsors

Shout-out to our current sponsors:


File: apps/papra-server/README.md

Papra - App server

This is the server for Papra.

Development

To start the development server, run:

bash
# Navigate to the docs directory
cd apps/papra-server

# Install dependencies
pnpm install

# Start the development server
pnpm dev

The development server will be available at http://localhost:1221.


File: apps/papra-client/README.md

Papra - App Client

This is the server for Papra.

Development

[!IMPORTANT]
Unless you are developing for the demo mode (VITE_IS_DEMO_MODE=true in adjacent .env file), you will need to have the server running locally. See the server README for instructions on how to start the server.

To start the development server, run:

bash
# Navigate to the docs directory
cd apps/papra-client

# Install dependencies
pnpm install

# Start the development server
pnpm dev

The development server will be available at http://localhost:3000.


File: apps/docs/src/content/docs/03-guides/01-intake-emails-with-owlrelay.mdx


title: Setup Intake Emails with OwlRelay slug: guides/intake-emails-with-owlrelay

import { Aside } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';

This guide will show you how to setup Papra to receive emails using OwlRelay.

OwlRelay is an open-source agnostic email-to-http service developed by the Papra team. It's a free service that allows you to receive emails and forward them to your self-hosted Papra instance.

Prerequisites

In order to follow this guide, your Papra instance needs to be running and accessible from the internet.

How it works

By integrating Papra with OwlRelay, your instance will generate email addresses on OwlRelay through it's API (specifying a webhook on your Papra instance). And OwlRelay will forward the emails to your Papra instance through a webhook.

Setup

  1. Create an account on OwlRelay

    Go to owlrelay.email and create an account.

  2. Create an OwlRelay API key

    Once you have created your account, you can create an API key by going to the API keys page.

  3. Configure your Papra instance

    Once you have created your API key, you can configure your Papra instance to receive emails by setting the OWLRELAY_API_KEY and INTAKE_EMAILS_WEBHOOK_SECRET environment variables.

    bash
    # Enable intake emails
    INTAKE_EMAILS_IS_ENABLED=true
    
    # Tell your Papra instance to use OwlRelay
    INTAKE_EMAILS_DRIVER=owlrelay
    
    # This is your OwlRelay API key
    OWLRELAY_API_KEY=owrl_*****
    
    # Set a random key that will be transmitted to OwlRelay to sign the requests,
    # this is to authenticate that the emails are coming from OwlRelay
    INTAKE_EMAILS_WEBHOOK_SECRET=a-random-key
    
    # [Optional]
    # This is the URL that OwlRelay will send the emails to,
    # if not provided, the webhook will be inferred from the server URL.
    # Can be relevant if you have multiple urls pointing to your Papra instance
    # or when using tunnel services
    OWLRELAY_WEBHOOK_URL=https://your-instance.com/api/intake-emails/ingest
  4. That's it!

    You can now generate intake emails in your Papra instance and send emails with attachments to the email address generated.

Troubleshooting

If you encounter any issues, you can:

  • Check the logs of your Papra instance to see if there are any errors.
  • Connect to your OwlRelay account to see if the emails address are generated and emails are received.

File: apps/docs/src/content/docs/03-guides/02-intake-emails-with-cf-email-worker.mdx


title: Setup Intake Emails with CF Email Workers slug: guides/intake-emails-with-cloudflare-email-workers

import { Aside } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';

This guide will show you how to setup a Cloudflare Email worker to receive emails in your Papra instance.

Prerequisites

In order to follow this guide, you need:

  • a Cloudflare account
  • a custom domain name available on Cloudflare
  • a publicly accessible Papra instance
  • basic development skills (git and node.js to setup the Email Worker)

If you prefer a simpler solution, you can use the official OwlRelay integration guide.

How it works

In order to receive emails in your Papra instance, we need to convert the email to an HTTP request. This is currently done by setting up a Cloudflare Email Worker that will forward the email to your Papra instance, basically acting as a bridge between the email and your Papra instance.

The code for the Email Worker proxy is available in the papra-hq/email-proxy repository.

Setup

  1. Create the Email Worker

    There are two ways to create an Email Worker, either from the Cloudflare dashboard or by cloning and deploying the code from the papra-hq/email-proxy repository.

    • Option 1: From the Cloudflare dashboard (easier).

      • Go to the Cloudflare dashboard.
      • Select your domain.
      • Go to the Compute (Workers) tab.
      • Click on the Create Worker button.
      • Name your worker (e.g. email-proxy).
      • Copy the code from the index.js file (from the papra-hq/email-proxy repository) and paste it in the editor.
    • Option 2: Build and deploy the Email Worker

      Clone the papra-hq/email-proxy repository and deploy the worker using Wrangler cli. You will need to have Node.js v24 and pnpm installed.

      bash
      # Clone the repository
      git clone https://github.com/papra-hq/email-proxy.git
      
      # Change directory
      cd email-proxy
      
      # Install dependencies
      pnpm install
      
      # Build the worker
      pnpm build
      
      # Deploy the worker (you will be prompted to login to Cloudflare through wrangler)
      pnpm run deploy
  2. Configure the Email Worker

    Add the following environment variables to the worker:

    • WEBHOOK_URL: The email intake endpoint in your Papra instance (basically. https://<your-papra-instance.com>/api/intake-emails/ingest).
    • WEBHOOK_SECRET: The secret key to authenticate the webhook requests, set the same as the INTAKE_EMAILS_WEBHOOK_SECRET environment variable in your Papra instance.
  3. Configure your Papra instance

    In your Papra instance, add the following environment variables:

    bash
    # Enable intake emails
    INTAKE_EMAILS_IS_ENABLED=true
    
    # Tell your Papra instance that it can generate any email address from the
    # domain you setup in the Email Worker as it's a wildcard redirection
    INTAKE_EMAILS_DRIVER=catch-all
    
    # This is the domain from which the intake email will be generated 
    # eg. `domain.com`
    INTAKE_EMAILS_CATCH_ALL_DOMAIN=papra.email
    
    # This is the secret key to authenticate the webhook requests
    # set the same as the `WEBHOOK_SECRET` variable in the Email Worker
    INTAKE_EMAILS_WEBHOOK_SECRET=a-random-key
  4. Configure the Email Routing

    • Go to the Email Routing tab in the Cloudflare dashboard.
    • Follow the email onboarding process.
    • Add a catch-all rule to forward all emails to the Email Worker you created.
  5. Test the setup

    In your Papra instance, go to the Integrations page in your organization and generates an intake email URL, setup an allowed sender (basically your email address), and copy the generated email address. Send an email to the generated address with a file attached, and check if the file is uploaded to your Papra instance.

Troubleshooting

View logs using wrangler tail

To view real-time logs associated with any deployed Worker using Wrangler:

  • Go to your Worker project directory.
  • Run npx wrangler tail.

This will log any incoming requests to your application available in your local terminal.

Email Worker not receiving emails

If the Email Worker is not receiving emails, make sure that the email routing is correctly configured in the Cloudflare dashboard.
Also, check the logs in the Email Worker dashboard for any errors.

Papra instance returning 403 Forbidden

If your Papra instance is returning a 403 Forbidden error, make sure that the INTAKE_EMAILS_IS_ENABLED environment variable is set to true in your Papra instance.

Papra instance is returning 401 Unauthorized

If your Papra instance is returning a 401 Unauthorized error, make sure that the INTAKE_EMAILS_WEBHOOK_SECRET environment variable is correctly set in your Papra instance.

The worker is not forwarding the email to the Papra instance

Make sure that the WEBHOOK_URL and WEBHOOK_SECRET environment variables are correctly set in the Email Worker, and the INTAKE_EMAILS_WEBHOOK_SECRET environment variable is correctly set in your Papra instance.
Also, check the logs in the Email Worker dashboard or the logs in your Papra instance for any errors.

TypeError: Too many redirects.

If you get "too many redirects" (check log from wrangler), check that your domain is configured with SSL/TLS Encryption as Full (strict).
Cloudflare dashboard โ†’ (domain) โ†’ SSL/TLS โ†’ Overview โ†’ set encryption mode to Full (strict) or at minimum Full. Save, then re-send a test email.


File: apps/docs/src/content/docs/03-guides/03-setup-ingestion-folder.mdx


title: Setup Ingestion Folder slug: guides/setup-ingestion-folder

import { Steps } from '@astrojs/starlight/components';
import { Aside } from '@astrojs/starlight/components';
import { FileTree } from '@astrojs/starlight/components';

The ingestion folder is a special folder that is watched by Papra for new files. When a new file is added to the ingestion folder, Papra will automatically import it.

Multi-Organization Structure

Papra supports multiple organizations within a single instance, each requiring a dedicated ingestion folder. The ingestion system uses a hierarchical structure where:

- ingestion-folder - org_abc123 - document.pdf - report.docx - org_def456 - file.txt - foo.txt # Ignored as it's not in an organization

This allows you to have a single instance of Papra watching multiple organizations' ingestion folders.

Setup

Add the following to your docker-compose.yml file:

yaml title="docker-compose.yml" ins={9,12}
services:
papra:
   container_name: papra
   image: ghcr.io/papra-hq/papra:latest
   restart: unless-stopped
   ports:
      - "1221:1221"
   environment:
      - INGESTION_FOLDER_IS_ENABLED=true
   volumes:
      - ./app-data:/app/app-data
      - <your-ingestion-folder>:/app/ingestion
   user: "${UID}:${GID}"

Then add files to a folder named with the organization id (available in Papra URL, e.g. https://papra.example.com/organizations/<organization-id>, the format is org_<random>).

bash
mkdir -p <your-ingestion-folder>/<org_id>
touch <your-ingestion-folder>/<org_id>/hello.txt

Post-processing

Once a file has been ingested in your Papra organization, you can configure what happens to it by setting the INGESTION_FOLDER_POST_PROCESSING_STRATEGY environment variable. There are two strategies:

  • delete: The file is deleted from the ingestion folder (default strategy)
  • move: The file is moved to the INGESTION_FOLDER_POST_PROCESSING_MOVE_FOLDER_PATH folder (default: ./ingestion-done)

Note that the INGESTION_FOLDER_POST_PROCESSING_MOVE_FOLDER_PATH path is relative to the organization ingestion folder.

So with INGESTION_FOLDER_POST_PROCESSING_MOVE_FOLDER_PATH=ingestion-done, the file /<org_id>/file.pdf will be moved to /<org_id>/ingestion-done/file.pdf once ingested.

Safeguards

To avoid accidental data loss, if for some reason the ingestion fails, the file is moved to the INGESTION_FOLDER_ERROR_FOLDER_PATH folder (default: ./ingestion-error).

Polling

By default, Papra uses native file watchers to detect changes in the ingestion folder. On some OS (like Windows), this can be flaky with Docker. To avoid this issue, you can enable polling by setting the INGESTION_FOLDER_WATCHER_USE_POLLING environment variable to true.

The default polling interval is 2 seconds, you can change it by setting the INGESTION_FOLDER_WATCHER_POLLING_INTERVAL_MS environment variable.

yaml title="docker-compose.yml" ins={2-3}
environment:
   - INGESTION_FOLDER_WATCHER_USE_POLLING=true
   - INGESTION_FOLDER_WATCHER_POLLING_INTERVAL_MS=2000

Configuration

You can find the list of all configuration options in the configuration reference, the related variables are prefixed with INGESTION_FOLDER_.

Edge cases and behaviors

  • The ingestion folder is watched recursively.
  • Files in the ingestion folder done and error folders are ignored.
  • When a file from the ingestion folder is already present (and not in the trash) in the organization, no ingestion is done, but the file is post-processed (deleted or moved) as successfully ingested.
  • When a file is moved to "done" or "error" folder
    • If a file with the same name and same content is present in the destination folder, the original file is deleted
    • If a file with the same name but different content is present in the destination folder, the original file is moved and a timestamp is added to the filename
  • Some files are ignored by default (.DS_Store, Thumbs.db, desktop.ini, etc.) see ingestion-folders.constants.ts for the list of ignored files and patterns. You can change this by setting the INGESTION_FOLDER_IGNORED_PATTERNS environment variable.

File: apps/docs/src/content/docs/03-guides/04-setup-custom-oauth2.mdx


title: Setup Custom OAuth2 Providers slug: guides/setup-custom-oauth2-providers

import { Aside } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';

This guide will show you how to configure custom OAuth2 providers for authentication in your Papra instance.

Prerequisites

In order to follow this guide, you need:

  • A custom OAuth2 provider
  • An accessible Papra instance
  • Basic understanding of OAuth2 flows

Configuration

To set up custom OAuth2 providers, you'll need to configure the AUTH_PROVIDERS_CUSTOMS environment variable with an array of provider configurations. Here's an example:

bash
AUTH_PROVIDERS_CUSTOMS='[
  {
    "providerId": "custom-oauth2",
    "providerName": "Custom OAuth2",
    "providerIconUrl": "https://api.iconify.design/tabler:login-2.svg",
    "clientId": "your-client-id",
    "clientSecret": "your-client-secret",
    "type": "oidc",
    "discoveryUrl": "https://your-provider.tld/.well-known/openid-configuration",
    "scopes": ["openid", "profile", "email"]
  }
]'

Each provider configuration supports the following fields:

  • providerId: A unique identifier for the OAuth provider
  • providerName: The display name of the provider
  • providerIconUrl: URL of the icon to display (optional) you can use a base64 encoded image or an url to a remote image.
  • clientId: OAuth client ID
  • clientSecret: OAuth client secret
  • type: Type of OAuth flow ("oauth2" or "oidc")
  • discoveryUrl: URL to fetch OAuth 2.0 configuration (recommended for OIDC providers)
  • authorizationUrl: URL for the authorization endpoint (required for OAuth2 if not using discoveryUrl)
  • tokenUrl: URL for the token endpoint (required for OAuth2 if not using discoveryUrl)
  • userInfoUrl: URL for the user info endpoint (required for OAuth2 if not using discoveryUrl)
  • scopes: Array of OAuth scopes to request
  • redirectURI: Custom redirect URI (optional)
  • responseType: OAuth response type (defaults to "code")
  • prompt: Controls the authentication experience ("select_account", "consent", "login", "none")
  • pkce: Whether to use PKCE (Proof Key for Code Exchange)
  • accessType: Access type for the authorization request

Setup

  1. Configure your OAuth2 Provider

    First, you'll need to register your application with your OAuth2 provider. This typically involves:

    • Creating a new application in your provider's dashboard
    • Setting up the redirect URI (usually https://<your-papra-instance>/api/auth/oauth2/callback/:providerId)
    • Obtaining the client ID and client secret
    • Configuring the required scopes
  2. Configure Papra

    Add the AUTH_PROVIDERS_CUSTOMS environment variable to your Papra instance. Here are some examples:

    For OIDC providers:

    json
    [
      {
        "providerId": "custom-oauth2",
        "providerName": "Custom OAuth2",
        "providerIconUrl": "https://api.iconify.design/tabler:login-2.svg",
        "clientId": "your-client-id",
        "clientSecret": "your-client-secret",
        "type": "oidc",
        "discoveryUrl": "https://your-provider.tld/.well-known/openid-configuration",
        "scopes": ["openid", "profile", "email"]
      }
    ]

    For standard OAuth2 providers:

    json
    [
      {
        "providerId": "custom-oauth2",
        "providerName": "Custom OAuth2",
        "providerIconUrl": "https://api.iconify.design/tabler:login-2.svg",
        "clientId": "your-client-id",
        "clientSecret": "your-client-secret",
        "type": "oauth2",
        "authorizationUrl": "https://your-provider.tld/oauth2/authorize",
        "tokenUrl": "https://your-provider.tld/oauth2/token",
        "userInfoUrl": "https://your-provider.tld/oauth2/userinfo",
        "scopes": ["profile", "email"]
      }
    ]
  3. Test the Configuration

    • Restart your Papra instance to apply the changes
    • Go to the login page
    • You should see your custom OAuth2 providers as login options
    • Try logging in with a test account

Troubleshooting

Providers Not Showing Up

If your OAuth2 providers are not showing up on the login page:

  • Check that the JSON configuration in AUTH_PROVIDERS_CUSTOMS is valid
  • Ensure all required fields are provided
  • Verify that the provider IDs are unique

Authentication Fails

If authentication fails:

  • Verify that the redirect URI is correctly configured in your OAuth2 provider
  • Check that the client ID and client secret are correct
  • Ensure the required scopes are properly configured
  • Check the Papra logs for any error messages

OIDC Discovery Issues

If you're using OIDC and experiencing issues:

  • Verify that the discoveryUrl is accessible
  • Check that the provider supports OIDC discovery
  • Ensure the provider's configuration is properly exposed through the discovery endpoint

Security Considerations

Multiple Providers

You can configure multiple custom OAuth2 providers by adding them to the array:

json
[
  {
    "providerId": "custom-oauth2-1",
    "providerName": "Custom OAuth2 Provider 1",
    "type": "oidc",
    "discoveryUrl": "https://provider1.tld/.well-known/openid-configuration",
    "clientId": "client-id-1",
    "clientSecret": "client-secret-1",
    "scopes": ["openid", "profile", "email"]
  },
  {
    "providerId": "custom-oauth2-2",
    "providerName": "Custom OAuth2 Provider 2",
    "type": "oidc",
    "discoveryUrl": "https://provider2.tld/.well-known/openid-configuration",
    "clientId": "client-id-2",
    "clientSecret": "client-secret-2",
    "scopes": ["openid", "profile", "email"]
  }
]

File: apps/docs/src/content/docs/02-self-hosting/01-using-docker.mdx


title: Installing Papra using Docker slug: self-hosting/using-docker

import { Steps } from '@astrojs/starlight/components';
import { Tabs, TabItem } from '@astrojs/starlight/components';
import SecretKeyGenerator from '../../../components/random-hex-key-generator.astro';

Papra provides optimized Docker images for streamlined deployment. This method is recommended for users seeking a production-ready setup with minimal maintenance overhead.

  • Simplified management: Single container handles all components
  • Lightweight: Optimized image sizes across architectures
  • Cross-platform support: Compatible with arm64, arm/v7, and x86_64 systems
  • Security options: Supports both rootless (recommended) and rootful configurations

Prerequisites

Ensure Docker is installed on your host system. Official installation guides are available at:
docker.com/get-started

Verify Docker installation with:

bash
docker --version

Quick Deployment

Papra requires an AUTH_SECRET to run. Generate a secure one using the generator below or the OpenSSL command:

```bash openssl rand -hex 48 ```

Then launch Papra with default configuration using:

bash
docker run -d \
  --name papra \
  --restart unless-stopped \
  --env APP_BASE_URL=http://localhost:1221 \
  --env AUTH_SECRET=<your-auth-secret> \
  -p 1221:1221 \
  ghcr.io/papra-hq/papra:latest

This command will:

  1. Pull the latest rootless image from GitHub Container Registry
  2. Expose the web interface on http://localhost:1221
  3. Configure automatic restarts for service continuity

Image Variants

Choose between two security models based on your requirements:

  • Rootless: Tagged as latest, latest-rootless or <version>-rootless (like 0.2.1-rootless). Recommended for most users.
  • Root: Tagged as latest-root or <version>-root (like 0.2.1-root). Only use if you need to run Papra as the root user.

The :latest tag always references the latest rootless build.

Persistent Data Configuration

For production deployments, mount host directories to preserve application data between container updates.

  1. Create Storage Directories

    Create a directory for Papra data ./papra-data, with ./papra-data/db and ./papra-data/documents subdirectories:

    bash
    mkdir -p ./papra-data/{db,documents}
  2. Launch Container with Volume Binding

    Make sure you have generated an AUTH_SECRET (see the Quick Deployment section above), then run:

    bash
    docker run -d \
      --name papra \
      --restart unless-stopped \
      --env APP_BASE_URL=http://localhost:1221 \
      --env AUTH_SECRET=<your-auth-secret> \
      -p 1221:1221 \
      -v $(pwd)/papra-data:/app/app-data \
      --user $(id -u):$(id -g) \
      ghcr.io/papra-hq/papra:latest

    This configuration:

    • Maintains data integrity across container lifecycle events
    • Enforces proper file ownership without manual permission adjustments
    • Stores both database files and document assets persistently

Image Registries

Papra images are distributed through multiple channels:

Primary Source (GHCR):

bash
docker pull ghcr.io/papra-hq/papra:latest
docker pull ghcr.io/papra-hq/papra:latest-rootless
docker pull ghcr.io/papra-hq/papra:latest-root

Community Mirror (Docker Hub):

bash
docker pull corentinth/papra:latest
docker pull corentinth/papra:latest-rootless
docker pull corentinth/papra:latest-root

Updating Papra

Regularly pull updated images and recreate containers to receive security patches and feature updates.

bash
docker pull ghcr.io/papra-hq/papra:latest
# Or
docker pull corentinth/papra:latest

File: apps/docs/src/content/docs/02-self-hosting/02-using-docker-compose.mdx


title: Using Docker Compose slug: self-hosting/using-docker-compose

import { Steps } from '@astrojs/starlight/components';
import { Tabs, TabItem } from '@astrojs/starlight/components';
import SecretKeyGenerator from '../../../components/random-hex-key-generator.astro';

This guide covers how to deploy Papra using Docker Compose, ideal for users who prefer declarative configurations or plan to integrate Papra into a broader service stack.

Using Docker Compose provides:

  • A single, versioned configuration file
  • Easy integration with volumes, networks, and service dependencies
  • Simplified updates and re-deployments

This method supports both rootless and rootful Papra images, please refer to the Docker guide for more information about the difference between the two. The following example uses the recommended rootless setup.

Prerequisites

Ensure Docker and Docker Compose are installed on your host system. Official installation guides are available at: docker.com/get-started

Verify Docker installation with:

bash
docker --version
docker compose version
  1. Initialize Project Structure

    Create working directory and persistent storage subdirectories:

    bash
    mkdir -p papra/app-data/{db,documents} && cd papra
  2. Generate an Auth Secret

    Papra requires an AUTH_SECRET to run. Generate a secure one using the generator below or the OpenSSL command:

    ```bash openssl rand -hex 48 ```

    Keep the value handy, you will set it as an environment variable in the next step.

  3. Create Docker Compose file

    Create a file named docker-compose.yml with the following content (replacing <your-auth-secret> with the value generated in the previous step):

    yaml
    services:
      papra:
        container_name: papra
        image: ghcr.io/papra-hq/papra:latest
        restart: unless-stopped
        ports:
          - "1221:1221"
        environment:
          - AUTH_SECRET=<your-auth-secret>
        volumes:
          - ./app-data:/app/app-data
        user: "${UID}:${GID}"
  4. Start Papra

    From the directory containing your docker-compose.yml file, run:

    bash
    UID=$(id -u) GID=$(id -g) docker compose up -d

    This command downloads the latest Papra image, sets up the container, and starts the Papra service. The UID and GID variables are used to set the user and group for the container, ensuring proper file ownership. If you don't want to use the UID and GID variables, you can replace the image with the rootful variant.

  5. Access Papra

    Once your container is running, access Papra via your browser at:

    text
    http://localhost:1221

    Your Papra instance is now ready for use!

  6. To go further

    Check the configuration page for more information on how to configure your Papra instance.

Maintenance

Check logs

bash
docker compose logs -f

Stop the service

bash
docker compose down

Update Papra

bash
docker compose pull
docker compose up -d

You're all set! Enjoy managing your documents with Papra.