> ## Documentation Index
> Fetch the complete documentation index at: https://www.towbar.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Paperless-ngx

> Paperless-ngx organizes scanned documents and PDFs into a searchable digital archive.

export const UseCaseNavigation = () => {
  useLayoutEffect(() => {
    const storageKey = "towbar-use-case-open-groups";
    const groupSelector = "#sidebar li[data-title] > button[aria-expanded]";
    let restoring = false;
    let frame = 0;
    function storedGroups() {
      try {
        return new Set(JSON.parse(sessionStorage.getItem(storageKey) || "[]"));
      } catch {
        return new Set();
      }
    }
    function save(groups) {
      try {
        sessionStorage.setItem(storageKey, JSON.stringify([...groups]));
      } catch {}
    }
    function groupName(button) {
      return button.parentElement?.dataset.title;
    }
    function rememberOpenGroups() {
      const groups = new Set();
      for (const button of document.querySelectorAll(groupSelector)) {
        if (button.getAttribute("aria-expanded") === "true") {
          const name = groupName(button);
          if (name) groups.add(name);
        }
      }
      save(groups);
    }
    function restoreOpenGroups() {
      frame = 0;
      const groups = storedGroups();
      if (!groups.size) return;
      restoring = true;
      for (const button of document.querySelectorAll(groupSelector)) {
        if (groups.has(groupName(button)) && button.getAttribute("aria-expanded") === "false") {
          button.click();
        }
      }
      restoring = false;
    }
    function scheduleRestore() {
      if (!frame) frame = requestAnimationFrame(restoreOpenGroups);
    }
    function onClick(event) {
      if (restoring || !(event.target instanceof Element)) return;
      const button = event.target.closest(groupSelector);
      if (button) {
        const name = groupName(button);
        if (!name) return;
        const groups = storedGroups();
        if (button.getAttribute("aria-expanded") === "true") groups.delete(name); else groups.add(name);
        save(groups);
        return;
      }
      if (event.target.closest('a[href^="/docs/use-cases/"]')) {
        rememberOpenGroups();
      }
    }
    const observer = new MutationObserver(scheduleRestore);
    document.addEventListener("click", onClick, true);
    observer.observe(document.getElementById("sidebar") || document.body, {
      childList: true,
      subtree: true
    });
    scheduleRestore();
    return () => {
      document.removeEventListener("click", onClick, true);
      observer.disconnect();
      cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<UseCaseNavigation />

<img className="towbar-doc-brand-logo" src="https://mintcdn.com/avgeek/EGkEWLTQnOwecqNx/assets/use-case-logos/paperlessngx.svg?fit=max&auto=format&n=EGkEWLTQnOwecqNx&q=85&s=8f58c65802e3c9792db537010ff58cec" alt="Paperless-ngx logo" aria-hidden="true" width="24" height="24" data-path="assets/use-case-logos/paperlessngx.svg" />

**Upstream source:** [Current installation guide](https://docs.paperless-ngx.com/setup/).

Paperless needs a Redis-compatible broker for background work. PostgreSQL stores document metadata; the Service volumes keep its index and document files. OCR of Office documents is an optional later Tika/Gotenberg addition.

## Towbar manifests

<CodeGroup>
  ```yaml title="postgres" theme={"system"}
  # .towbar/datastores/paperless-ngx-postgres.datastore.yml
  id: paperless-ngx-postgres
  name: Paperless-ngx PostgreSQL
  type: postgres
  container:
    network: application
    networkAlias: paperless-ngx-postgres
    resources:
      cpus: 1
      memory: 1g
  secrets:
    runtime:
      - POSTGRES_USER
      - POSTGRES_DB
      - POSTGRES_PASSWORD
  environments:
    production:
      server: 192.0.2.10
  ```

  ```yaml title="redis" theme={"system"}
  # .towbar/datastores/paperless-ngx-redis.datastore.yml
  id: paperless-ngx-redis
  name: Paperless-ngx Redis
  type: redis
  container:
    network: application
    networkAlias: paperless-ngx-redis
    resources:
      cpus: 0.5
      memory: 512m
  secrets:
    runtime:
      - REDIS_PASSWORD
  environments:
    production:
      server: 192.0.2.10
  ```

  ```yaml title="service" theme={"system"}
  # .towbar/services/paperless-ngx.service.yml
  id: paperless-ngx
  name: Paperless-ngx
  buildServer: null
  deployment:
    type: image
    image: ghcr.io/paperless-ngx/paperless-ngx:2.18.4
  container:
    port: 8000
    network: application
    volumes:
      - name: data
        mountPath: /usr/src/paperless/data
        initialData: image
      - name: media
        mountPath: /usr/src/paperless/media
        initialData: image
      - name: consume
        mountPath: /usr/src/paperless/consume
        initialData: image
  environments:
    production:
      server: 192.0.2.10
  rollout:
    type: recreate
    maintenanceMode: true
    reason: Persistent application data uses a single writer
  secrets:
    runtime:
      - PAPERLESS_REDIS
      - PAPERLESS_DBENGINE
      - PAPERLESS_DBHOST
      - PAPERLESS_DBNAME
      - PAPERLESS_DBUSER
      - PAPERLESS_DBPASS
      - PAPERLESS_SECRET_KEY
      - PAPERLESS_URL
  domains:
    primary: paperless-ngx.example.com
  tls:
    mode: direct
  ```
</CodeGroup>

## Configure

1. Prepare the example server, connect the repository, and map `production` to the branch containing these manifests. Replace the example server IP and domain. Commit the manifests, then sync the repository and inspect the resolved configuration.
2. Save the initialization values in the table below for the postgres Datastore and the redis Datastore under **Datastore → Settings → Secrets**. Deploy them first and wait for readiness. These values create the database and user only on an empty volume.
3. Set the Service's declared runtime values under **Service → Settings → Secrets** using the table below. Replace descriptions and placeholders with actual values; do not commit passwords or keys.
4. Keep the Service's named volumes attached across deployments. Towbar's Datastore backup policy does not back up Service volumes, so include them in your own recovery plan.
5. Deploy the Service, then perform the checks below before enabling auto-deploy.

## Datastore values

Save these in each Datastore's **Settings → Secrets** before deploying it. Use the suggested names or choose your own, then use the same names and passwords in the Service connection values below.

| Datastore              | Key                 | Suggested value                                                                           |
| ---------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
| Paperless-ngx postgres | `POSTGRES_DB`       | paperless\_ngx                                                                            |
| Paperless-ngx postgres | `POSTGRES_USER`     | paperless\_ngx                                                                            |
| Paperless-ngx postgres | `POSTGRES_PASSWORD` | Run `openssl rand -hex 32`; save its output and reuse it in the Service connection value. |
| Paperless-ngx redis    | `REDIS_PASSWORD`    | Run `openssl rand -hex 32`; save its output and use it in the Service broker URL.         |

## Runtime values

Save these values on the Service after the repository sync. The manifest declares required keys, not their values.

| Key                    | Value to save                                                                             |
| ---------------------- | ----------------------------------------------------------------------------------------- |
| `PAPERLESS_REDIS`      | `redis://:PASSWORD@paperless-ngx-redis:6379/0` (replace `PASSWORD` with `REDIS_PASSWORD`) |
| `PAPERLESS_DBENGINE`   | postgresql                                                                                |
| `PAPERLESS_DBHOST`     | paperless-ngx-postgres                                                                    |
| `PAPERLESS_DBNAME`     | Same value as POSTGRES\_DB.                                                               |
| `PAPERLESS_DBUSER`     | Same value as POSTGRES\_USER.                                                             |
| `PAPERLESS_DBPASS`     | Same value as POSTGRES\_PASSWORD.                                                         |
| `PAPERLESS_SECRET_KEY` | Generate a long random key.                                                               |
| `PAPERLESS_URL`        | [https://paperless-ngx.example.com](https://paperless-ngx.example.com)                    |

## Verify

Upload a PDF and wait for processing. Confirm the document and its searchable text remain after redeployment. Back up both PostgreSQL and the Service volumes together.

For field constraints, see [Service manifest](/docs/services/manifest) and [Datastore manifest](/docs/datastores/manifest).
