Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“  Scan to Paperless Bridge

Docker Build & Publish Platform License

Author: Dr. Henning Dickten (@hensing)

A lightweight, secure, and Dockerized bridge designed for Raspberry Pi and generic Linux servers. It provides a Samba (SMB) share for hardware document scanners.

Once a scan is saved to the share, this container detects the completed file, uploads it directly to Paperless-ngx via API, and optionally archives or cleans up the local file.

Supports both single-user (simple env var config) and multi-user mode (one share and API key per person).


πŸ”„ How it works

Single-user mode

graph LR
    A[πŸ–¨οΈ Hardware Scanner] -- SMB Port 445 --> B(πŸ“‚ /data/inbox)
    B --> C[🐳 Container Watcher]
    C -- API Token --> D[πŸ“„ Paperless-NGX]
    C -- Move/Delete --> E(πŸ“¦ /data/archive)
Loading

Multi-user mode

graph TD
    SCAN[πŸ–¨οΈ Hardware Scanner]

    subgraph alice [Alice]
        direction TB
        AI(πŸ“‚ alice_scans)
        AW[🐳 Watcher]
        AA(πŸ“¦ archive)
        AI --> AW --> AA
    end

    subgraph bob [Bob]
        direction TB
        BI(πŸ“‚ bob_docs)
        BW[🐳 Watcher]
        BA(πŸ“¦ archive)
        BI --> BW --> BA
    end

    PL[πŸ“„ Paperless-NGX]

    SCAN -- SMB: alice_scans --> AI
    SCAN -- SMB: bob_docs --> BI
    AW -- Alice's API Key --> PL
    BW -- Bob's API Key --> PL
Loading

✨ Features

  • πŸš€ Multi-Arch Support: Optimized for linux/amd64 and linux/arm64 (Raspberry Pi).
  • πŸ‘₯ Multi-User Support: Each user gets their own Samba share, SMB credentials, and Paperless-NGX API key.
  • πŸ”’ Rootless by Design: Never runs as root, not even briefly at startup β€” fixed UID/GID 65532:65532 (the "distroless nonroot" convention, deliberately not 1000, to avoid colliding with a real host login user) baked into the image. Port 445 is remapped from an unprivileged internal port via Docker's own port publishing, so the container needs zero Linux capabilities.
  • πŸ“‚ Samba Integration: Built-in SMB server compliant with modern scanners.
  • ⚑ Smart Detection: Uses inotify to detect close_write events (prevents processing incomplete files).
  • 🏷️ Auto-Tagging: Automatically apply tags to uploaded documents β€” configurable per user in multi-user mode.
  • 🧹 Auto-Cleanup: Options to archive or delete files after successful upload.
  • πŸ›‘οΈ SSL Support: Full support for HTTPS and self-signed certificates.

πŸ”— Recommended Workflow

This tool works best as part of a modern document management ecosystem. We highly recommend checking out:

  • Paperless-ngx Documentation: The official documentation for the backend system.
  • Paperless-GPT: An amazing tool to add AI-powered analysis, tagging, and renaming to your documents after they have been uploaded.

πŸš€ Quick Start

Single-User Mode

1. Configuration

Create your .env file based on the example:

cp .env.example .env

Minimal .env example:

PAPERLESS_URL=https://paperless.local:8000
PAPERLESS_API_KEY=your-super-secret-token
SMB_USER=scanner
SMB_PASSWORD=scan123

⚠️ scan123 is just an example value, not a real default you should keep. Change SMB_PASSWORD to a strong, unique password before starting the container β€” this share is reachable by anything on your network. The container will log a loud warning at startup for as long as the password is left at scan123.

1b. Host Permissions

This container always runs as a fixed UID/GID (65532:65532 by default) and never as root β€” not even transiently at startup. Before the first start, make sure ./data and ./config are owned by (or writable by) UID/GID 65532 on the host:

mkdir -p data config
chown -R 65532:65532 data config

The container can no longer fix bind-mount ownership for you (that would require a root step, which has been deliberately removed).

Why 65532 and not 1000? Docker doesn't remap container UIDs into a separate namespace by default β€” "UID 1000 in the container" is literally the same UID as a host account, and on single-user Debian/Raspberry Pi OS installs, UID 1000 is almost always the operator's own (often sudo-capable) login. Since this container runs a network-facing SMB service, a container escape running as that UID would inherit the operator's file access and could potentially piggyback on a still-valid sudo credential cache. 65532 is the widely-used "distroless nonroot" convention β€” deliberately outside both the Linux system-service range (0–999) and the human-user range (1000+), so it won't collide with a real login account. If you'd rather match your own host user than run the one-time chown (accepting the collision consideration above), rebuild with --build-arg APP_UID=<uid> --build-arg APP_GID=<gid> and update user: in your compose file to match.

2. Docker Compose

Create a docker-compose.yml (or use the one provided):

services:
  scan-to-paperless:
    image: ghcr.io/hensing/scan-to-paperless:latest
    container_name: scan-to-paperless
    restart: unless-stopped
    user: "65532:65532"
    security_opt:
      - "no-new-privileges:true"
    cap_drop:
      - "ALL"
    ports:
      - "445:8445"
    env_file:
      - .env
    volumes:
      - ./data:/data
      - ./config:/config:ro

Start the container:

docker compose up -d

3. Scanner Setup

Configure your physical scanner (Brother, Canon, HP, etc.) with these settings:

  • Protocol: SMB / CIFS
  • Server: IP of your Docker host
  • Port: 445
  • Share Name: scanner (default)
  • Username: scanner (default)
  • Password: scan123 (default β€” ⚠️ example only, change it in .env before going live)

πŸ‘₯ Multi-User Mode

Multi-user mode activates automatically when ./config/users.conf exists. Each user gets an isolated Samba share and uploads to Paperless with their own API key.

1. Create the user config

mkdir -p config
cp config/users.conf.example config/users.conf

Edit config/users.conf β€” one user per line, colon-separated:

# smb_user:smb_password:smb_share:paperless_api_key:paperless_tags(optional)
alice:secretpassword1:alice_scans:paperless-api-token-alice:scanned,alice
bob:secretpassword2:bob_docs:paperless-api-token-bob:scanned,bob

2. Set global settings in .env

Only PAPERLESS_URL and optional processing settings are needed. The SMB_* and PAPERLESS_API_KEY variables are ignored in multi-user mode.

PAPERLESS_URL=https://paperless.local:8000

Make sure you've also completed the Host Permissions step (chown -R 65532:65532 data config) β€” it applies to multi-user mode too.

The image ships with a fixed pool of 32 internal Samba accounts (see config/users.conf.example); rebuild with --build-arg SMB_POOL_SIZE=<N> if you need more than 32 concurrent users.

3. Start the container

docker compose up -d

Each user's files land in /data/<smb_user>/inbox and are archived to /data/<smb_user>/archive.

Upgrading from single-user: No changes required. Single-user mode is auto-detected when users.conf is absent.


πŸ” Automating Ownership & Sharing via Paperless Workflows

Want John's scans to automatically get shared with the "family" group, or have John's scanner account hand ownership of every document straight to Jane? This container doesn't set Paperless document owner/permissions itself β€” Paperless-NGX's own Workflows feature already does this well, and reuses code that's actually tested, so let it do the work.

The trick is giving a Workflow something reliable to match on: Paperless's upload "Source" filter can't tell one API key from another (API and Web-UI uploads are one bucket), but a tag unique to a user can. That's exactly what paperless_tags in users.conf is for.

  1. In Paperless, create a tag that's unique to one user, e.g. alice_upload.
  2. In users.conf, add that tag name to the user's paperless_tags column (comma-separated with any other tags they already get), e.g.:
    alice:secretpassword1:alice_scans:paperless-api-token-alice:scanned,alice_upload
    
    Restart the container after adding a brand-new tag β€” tag names are resolved to IDs once when each user's watcher starts.
  3. In Paperless, go to Settings β†’ Workflows β†’ add a new workflow:
    • Trigger: Document Added
    • Condition: has tag(s) alice_upload
    • Action(s): Assignment β†’ set the fields you want, e.g. assign owner, or grant view/change permission to specific users or groups.

Some concrete mappings for the scenarios above:

Goal Workflow action
Auto-share John's uploads (R/W) with the "family" group Assign view + change permission β†’ group family
John scans, ownership goes to Jane Assign owner β†’ jane
A shared scanner account transfers rights on upload Same as above, keyed off that account's own unique tag

Since the mapping lives in Paperless's Workflow UI rather than in users.conf, it can be changed anytime without touching or restarting this container β€” only the tag itself needs to exist before the watcher starts.


βš™οΈ Configuration Reference

Global Settings (.env)

Variable Description Default Required
PAPERLESS_URL Full URL to Paperless-NGX (e.g., http://192.168.1.5:8000) - βœ…
PAPERLESS_VERIFY_SSL Verify SSL certificates (false for self-signed) true ❌
WHITELIST Allowed file extensions pdf,jpg,png,bmp ❌
ARCHIVE true = Move to archive folder, false = Delete after upload true ❌
UPLOAD_TIMEOUT Max time (seconds) for API upload 30 ❌
SCAN_SETTLE_TIME Seconds to wait after detection before upload 5 ❌

There is no PUID/PGID setting β€” the container always runs as fixed UID/GID 65532:65532. See Host Permissions.

Single-User Settings (.env)

Ignored when config/users.conf is present.

Variable Description Default
PAPERLESS_API_KEY API Token from Paperless Settings β†’ API Tokens -
PAPERLESS_TAGS Comma-separated tag names (or numeric IDs) to apply β€” see Automating Ownership & Sharing ""
SMB_USER Username for the scanner to login scanner
SMB_PASSWORD Password for the scanner scan123
SMB_SHARE Name of the SMB share scanner

Multi-User Settings (config/users.conf)

Field Description Required
smb_user Linux/SMB username β€” unique, no spaces βœ…
smb_password Password for the SMB share βœ…
smb_share Share name visible to the scanner βœ…
paperless_api_key API Token from Paperless Settings β†’ API Tokens βœ…
paperless_tags Comma-separated tag names or numeric IDs (optional, can be empty) β€” see Automating Ownership & Sharing ❌

smb_user and smb_share must match ^[A-Za-z][A-Za-z0-9_-]*$; a fixed pool of 32 internal Samba accounts is available by default (SMB_POOL_SIZE build arg) β€” entries beyond that are skipped with a warning until the image is rebuilt with a larger pool.

Note on Permissions: This container never runs as root, not even transiently at startup. It runs as a fixed UID/GID (65532:65532 by default, overridable at build time via --build-arg APP_UID/APP_GID) baked into the image at build time β€” there is no runtime PUID/PGID remapping. Because of this, you are responsible for ensuring ./data and ./config are owned by (or writable by) that UID/GID before starting the container β€” the container itself can no longer fix bind-mount ownership, since doing so would require a root step that has been deliberately removed. See Host Permissions.

⬆️ Migrating from a previous version

This version removes the container's root startup phase entirely. If you're upgrading from an older version:

  1. PUID/PGID are gone. Remove them from your .env. The container now always runs as UID/GID 65532:65532.
  2. Rebuild the image (docker compose build / re-pull), not just restart β€” the new image ships a fixed pool of Samba accounts and a non-root USER directive.
  3. chown -R 65532:65532 ./data ./config on the host once, before starting β€” the container no longer fixes bind-mount ownership for you.
  4. Scanner-facing behavior is unchanged (still port 445, same users.conf format); no scanner reconfiguration is needed.
  5. Multi-user setups are capped at 32 concurrent users by default; rebuild with --build-arg SMB_POOL_SIZE=<N> if you need more.

πŸ“‚ Directory Structure

Single-user mode

/data
β”œβ”€β”€ inbox/      <-- Scanner saves files here (monitored)
└── archive/    <-- Processed files are moved here (if ARCHIVE=true)

Multi-user mode

/data
β”œβ”€β”€ alice/
β”‚   β”œβ”€β”€ inbox/    <-- alice's Samba share (monitored)
β”‚   └── archive/  <-- alice's processed files
└── bob/
    β”œβ”€β”€ inbox/    <-- bob's Samba share (monitored)
    └── archive/  <-- bob's processed files

πŸ› οΈ Troubleshooting

πŸ›‘ "Upload failed" in logs

  • Check if PAPERLESS_URL is reachable from inside the container.
  • Verify the API key (PAPERLESS_API_KEY or the key in users.conf).
  • If using a self-signed cert, try setting PAPERLESS_VERIFY_SSL=false.
  • Increase SCAN_SETTLE_TIME. Some network scanners report "finished" before the file is fully flushed to disk.

🚫 Scanner cannot connect (Network Error)

  • Ensure port 445 is not blocked by a firewall on the host.
  • Windows/Mac hosts might use port 445 for their own sharing service. Ensure port 445 is free or use a different external port (note: many scanners hardcode 445).

πŸ“„ File is ignored

  • Check the WHITELIST in .env.
  • The container waits for the close_write event. Ensure the scanner finishes writing the file completely.

πŸ‘₯ Multi-user: user cannot connect

  • Verify the share name in users.conf matches what the scanner is configured with.
  • Check the container logs for [INIT] Configured N user(s). β€” if N is 0, the config file has a parsing issue.
  • Ensure all four required fields (smb_user:smb_password:smb_share:paperless_api_key) are present.

🀝 How to Contribute

Contributions, improvements, and bug fixes are welcome!

  1. Fork the project.
  2. Create your feature branch (git checkout -b feature/AmazingFeature).
  3. Commit your changes (git commit -m 'Add some AmazingFeature').
  4. Push to the branch (git push origin feature/AmazingFeature).
  5. Open a Pull Request.

Note to Forks: Please ensure that the original author credit remains intact in the license and documentation when forking or redistributing this project.

πŸ‘¨β€πŸ’» Development

Build the image locally:

docker build -t scan-to-paperless .

πŸ“œ License

Distributed under the MIT License. See LICENSE for more information.

About

A secure SMB bridge for hardware scanners to automatically upload documents to Paperless-ngx. Features multi-arch support (x86/ARM), non-root execution, and auto-archiving.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages