Files
portainer-core/NEW_CONTAINERS.md
T
jpmschweitzerandClaude Opus 4.5 765818b4e9 feat(stack): add Paperless-ngx document management system
- Add paperless.yml stack (port 8091, documents.schweitz.net)
- Uses shared postgres (DB: paperless) and redis (DB 8)
- ClamAV installed on host for virus scanning (port 3310)
- Add Paperless integration to library-desk stack
- Update CONTAINERS.md with Paperless and ClamAV profiles
- Add Portainer and NPM API documentation to setup-new-host.md
- Update redis-shared.yml and postgres-shared.yml with Paperless refs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 00:36:15 +01:00

7.2 KiB

Plan: Add Paperless-ngx and ClamAV

Overview

Add document management (Paperless-ngx) and host-level virus scanning (ClamAV) to the tower-of-joy infrastructure.

Decisions Made

Decision Choice
Database Use postgres-shared (new DB: paperless)
Redis Use redis-shared DB 8
ClamAV Host OS installation (full server protection)
External Access Yes, at documents.schweitz.net via NPM
Network docker-dataplane
Stack Separate paperless.yml stack

Services

Service Port Location Purpose
Paperless-ngx 8091 Container Document management, OCR
ClamAV 3310 Host OS Virus scanning + server protection

Implementation Steps

Phase 1: ClamAV Host Installation (requires sudo)

User must run these commands manually:

# Install ClamAV
sudo apt update
sudo apt install clamav clamav-daemon -y

# Stop freshclam to update definitions
sudo systemctl stop clamav-freshclam
sudo freshclam
sudo systemctl start clamav-freshclam

# Configure clamd to listen on network
sudo sed -i 's/^#TCPSocket.*/TCPSocket 3310/' /etc/clamav/clamd.conf
sudo sed -i 's/^#TCPAddr.*/TCPAddr 0.0.0.0/' /etc/clamav/clamd.conf

# If TCPSocket/TCPAddr not present, add them
grep -q "^TCPSocket" /etc/clamav/clamd.conf || echo "TCPSocket 3310" | sudo tee -a /etc/clamav/clamd.conf
grep -q "^TCPAddr" /etc/clamav/clamd.conf || echo "TCPAddr 0.0.0.0" | sudo tee -a /etc/clamav/clamd.conf

# Restart daemon
sudo systemctl restart clamav-daemon

# Enable at boot
sudo systemctl enable clamav-daemon clamav-freshclam

# Verify
clamdscan --ping 3

Phase 2: Create Storage Directories

# SSD - configs and working data
mkdir -p ~/docker-data/paperless/{data,consume,export}

# HDD - document storage
mkdir -p /mnt/media/paperless/media

Phase 3: Create PostgreSQL Database

docker exec -i postgres-shared psql -U postgres <<'EOF'
-- Paperless database
CREATE DATABASE paperless;
CREATE USER paperless_user WITH PASSWORD '<generate-secure-password>';
GRANT ALL PRIVILEGES ON DATABASE paperless TO paperless_user;
\c paperless
GRANT ALL ON SCHEMA public TO paperless_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO paperless_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO paperless_user;
EOF

Phase 4: Create Stack File

Create: stacks/paperless.yml

version: '3.8'

# Paperless-ngx - Document Management System
# Port: 8091 (HTTP)
# GPU: No
# External: documents.schweitz.net

services:
  paperless:
    image: ghcr.io/paperless-ngx/paperless-ngx:latest
    container_name: paperless
    restart: unless-stopped
    ports:
      - "8091:8000"
    volumes:
      # SSD - config and working data
      - /home/jpmschweitzer/docker-data/paperless/data:/usr/src/paperless/data
      - /home/jpmschweitzer/docker-data/paperless/consume:/usr/src/paperless/consume
      - /home/jpmschweitzer/docker-data/paperless/export:/usr/src/paperless/export
      # HDD - document storage
      - /mnt/media/paperless/media:/usr/src/paperless/media
    environment:
      # Database (shared PostgreSQL)
      PAPERLESS_DBENGINE: postgresql
      PAPERLESS_DBHOST: postgres-shared
      PAPERLESS_DBPORT: 5432
      PAPERLESS_DBNAME: paperless
      PAPERLESS_DBUSER: paperless_user
      PAPERLESS_DBPASS: ${PAPERLESS_DB_PASSWORD}

      # Redis (shared, DB 8)
      PAPERLESS_REDIS: redis://redis-shared:6379/8

      # Security
      PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}

      # URLs
      PAPERLESS_URL: https://documents.schweitz.net
      PAPERLESS_ALLOWED_HOSTS: "*"
      PAPERLESS_CORS_ALLOWED_HOSTS: "http://localhost:8091,https://documents.schweitz.net"

      # OCR Settings
      PAPERLESS_OCR_LANGUAGE: eng+nld
      PAPERLESS_OCR_MODE: skip
      PAPERLESS_OCR_OUTPUT_TYPE: pdfa

      # Webhooks (for Library Desk integration)
      PAPERLESS_WEBHOOKS_ALLOW_INTERNAL_REQUESTS: "true"

      # Admin user (created on first run)
      PAPERLESS_ADMIN_USER: admin
      PAPERLESS_ADMIN_PASSWORD: ${PAPERLESS_ADMIN_PASSWORD}

      # Timezone
      TZ: Europe/Amsterdam
    labels:
      - "com.centurylinklabs.watchtower.enable=true"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 512M
    networks:
      - docker-dataplane

networks:
  docker-dataplane:
    external: true
    name: docker-dataplane

Phase 5: Environment Variables

Add to Portainer stack environment or .env:

# Generate these:
PAPERLESS_DB_PASSWORD=<openssl rand -hex 32>
PAPERLESS_SECRET_KEY=<openssl rand -base64 32>
PAPERLESS_ADMIN_PASSWORD=<your-admin-password>

Phase 6: Deploy Stack

Via Portainer or:

cd /mnt/media/Projects/portainer-core/stacks
docker-compose -f paperless.yml up -d

Phase 7: Configure NPM Proxy

Create proxy host in NPM:

  • Domain: documents.schweitz.net
  • Forward: paperless:8000 (or 192.168.86.149:8091)
  • SSL: Request Let's Encrypt certificate
  • Websockets: Enable
  • Access List: As needed (Authentik SSO optional)

Phase 8: Post-Deployment Configuration

  1. Access Paperless UI: https://documents.schweitz.net

  2. Login with admin credentials

  3. Create API Token: My Profile → API Token → Create

  4. Create Custom Fields:

    • source_url (URL)
    • library_indexed (Boolean)
    • library_doc_id (Text)
    • collection (Text)
  5. Configure Webhook Workflow:

    • Trigger: Document Added
    • Action: Webhook to http://library-desk:8089/documents/webhook

Phase 9: Update Library Desk Configuration

Add to Library Desk environment:

PAPERLESS_URL=http://paperless:8000
PAPERLESS_TOKEN=<api-token>
CLAMAV_HOST=192.168.86.149  # Host IP (ClamAV on host)
CLAMAV_PORT=3310
CLAMAV_ENABLED=true

Files to Create/Modify

Action File Description
Create stacks/paperless.yml Paperless-ngx stack
Update CONTAINERS.md Add Paperless profile, update tables
Update stacks/postgres-shared.yml Add paperless DB setup in comments
Update stacks/redis-shared.yml Update DB allocation (DB 8: Paperless)

CONTAINERS.md Updates

Add to Quick Reference table:

| **Paperless-ngx** | 8091 | https://documents.schweitz.net | Internet | No | 8 | ✅ Running |
| **ClamAV** | 3310 | N/A (host service) | No | No | - | ✅ Running |

Add to External Domains:

- **documents.schweitz.net** → Paperless-ngx

Update Redis DB allocation:

DB 8: Paperless (task queue, cache)

Add to PostgreSQL databases:

paperless (document storage)

Verification Checklist

  • ClamAV responds: clamdscan --ping 3
  • PostgreSQL database exists: docker exec postgres-shared psql -U postgres -c '\l' | grep paperless
  • Paperless container running: docker ps | grep paperless
  • Paperless health check passes
  • External access works: https://documents.schweitz.net
  • Library Desk can reach Paperless webhook endpoint
  • ClamAV accessible from containers: nc -zv 192.168.86.149 3310