Initial commit: all 6 self-hosted app compose files + README
This commit is contained in:
commit
fa2a7e2282
7 changed files with 465 additions and 0 deletions
30
README.md
Normal file
30
README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Manohar's Infrastructure
|
||||||
|
|
||||||
|
Self-hosted stack on Hetzner CX32 (Helsinki), deployed via Dokploy + Traefik.
|
||||||
|
|
||||||
|
## Services
|
||||||
|
|
||||||
|
AppURLPurposeForgejo<https://git.manohargupta.com>Self-hosted Gitn8n<https://automate.manohargupta.com>Workflow automationApprise<https://notify.manohargupta.com>Notification API (Tailscale only)Miniflux<https://feeds.manohargupta.com>RSS readerChangeDetection<https://watch.manohargupta.com>Webpage change monitorPaperless-ngx<https://docs.manohargupta.com>Document OCR + searchTiger Agent<https://agent.manohargupta.com>AI orchestrationDokploy<https://dokploy.manohargupta.com>Docker orchestrationUptime Kuma<https://status.manohargupta.com>MonitoringUmami<https://analytics.manohargupta.com>Web analytics
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
- **Server**: Hetzner CX32, Ubuntu 24.04, Helsinki (hel1)
|
||||||
|
- **Orchestration**: Dokploy (Docker Swarm)
|
||||||
|
- **Reverse proxy**: Traefik with Let's Encrypt TLS
|
||||||
|
- **Access**: Tailscale SSH only (`ssh root@manohar-ubuntu`)
|
||||||
|
- **DNS**: Namecheap → [manohargupta.com](http://manohargupta.com)
|
||||||
|
|
||||||
|
## Deployment notes
|
||||||
|
|
||||||
|
- All compose files are in `deployments/`
|
||||||
|
- Every compose declares `dokploy-network` as `external: true`
|
||||||
|
- Labels duplicated under both `labels:` and `deploy.labels:` — required because Dokploy uses swarm stack deploy for multi-service composes (swarm provider reads deploy.labels; docker provider reads labels)
|
||||||
|
- Secrets set in Dokploy Env tab, never hardcoded (except bcrypt hashes in labels which Dokploy cannot substitute)
|
||||||
|
- Apprise and ChangeDetection restricted to Tailscale CGNAT range (100.64.0.0/10)
|
||||||
|
|
||||||
|
## Key learnings
|
||||||
|
|
||||||
|
- Forgejo Docker image uses /data as root, not /var/lib/gitea
|
||||||
|
- n8n WEBHOOK_URL must match public domain exactly or webhook URLs are wrong
|
||||||
|
- htpasswd hashes in Traefik labels need $$ escaping (Compose interpolates single $)
|
||||||
|
- Dokploy env var substitution works in environment: blocks but NOT in labels:
|
||||||
52
apprise.compose.yml
Normal file
52
apprise.compose.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Apprise — notify.manohargupta.com
|
||||||
|
# Unified notification API. POST one message, fans out to Telegram, email, ~80 services.
|
||||||
|
# Stateless except for YAML config files stored in the config volume.
|
||||||
|
# No built-in auth -- protected by Traefik IP whitelist (Tailscale range only).
|
||||||
|
#
|
||||||
|
# Honest framing: n8n's built-in Telegram/email nodes cover most workflow notifications.
|
||||||
|
# Apprise's value is for non-n8n scripts (Python crons, server healthchecks) that also
|
||||||
|
# need to notify without importing the full n8n stack.
|
||||||
|
|
||||||
|
services:
|
||||||
|
apprise:
|
||||||
|
image: caronc/apprise:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
# Persist notification configs (tagged YAML files) across restarts
|
||||||
|
APPRISE_STATEFUL_MODE: simple
|
||||||
|
# Allow API to update configs -- lock this down after initial setup
|
||||||
|
APPRISE_CONFIG_LOCK: "no"
|
||||||
|
volumes:
|
||||||
|
- apprise_config:/config # stores per-tag YAML notification configs
|
||||||
|
- apprise_attach:/attach # stores file attachments for notifications
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.apprise.rule=Host(`notify.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.apprise.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.apprise.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.apprise.loadbalancer.server.port=8000"
|
||||||
|
# IP whitelist -- Tailscale CGNAT range only. Only your tailnet can call this API.
|
||||||
|
# Without this, anyone who finds the URL can send notifications on your behalf.
|
||||||
|
- "traefik.http.middlewares.apprise-ipallow.ipwhitelist.sourcerange=100.64.0.0/10"
|
||||||
|
- "traefik.http.routers.apprise.middlewares=apprise-ipallow@docker"
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.apprise.rule=Host(`notify.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.apprise.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.apprise.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.apprise.loadbalancer.server.port=8000"
|
||||||
|
- "traefik.http.middlewares.apprise-ipallow.ipwhitelist.sourcerange=100.64.0.0/10"
|
||||||
|
- "traefik.http.routers.apprise.middlewares=apprise-ipallow@docker"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
apprise_config:
|
||||||
|
apprise_attach:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
61
changedetection.compose.yml
Normal file
61
changedetection.compose.yml
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
# ChangeDetection.io — watch.manohargupta.com
|
||||||
|
# Watches pages with no RSS: CERC orders, MNRE notifications, SECI tenders.
|
||||||
|
# Browserless sidecar handles JS-rendered government portals (Angular/React).
|
||||||
|
# Auth: bcrypt basic auth via Traefik (hash hardcoded -- env vars don't work in labels).
|
||||||
|
# Memory budget: ~700 MB total (CD ~150 MB + browser sidecar ~550 MB).
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
browser:
|
||||||
|
image: dgtlmoon/sockpuppetbrowser:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
networks:
|
||||||
|
- cd_internal
|
||||||
|
environment:
|
||||||
|
- SCREEN_WIDTH=1920
|
||||||
|
- SCREEN_HEIGHT=1080
|
||||||
|
|
||||||
|
changedetection:
|
||||||
|
image: ghcr.io/dgtlmoon/changedetection.io:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- browser
|
||||||
|
environment:
|
||||||
|
PLAYWRIGHT_DRIVER_URL: ws://browser:3000
|
||||||
|
BASE_URL: https://watch.manohargupta.com
|
||||||
|
TZ: Asia/Kolkata
|
||||||
|
volumes:
|
||||||
|
- cd_data:/datastore
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
- cd_internal
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.changedetection.rule=Host(`watch.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.changedetection.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.changedetection.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.changedetection.loadbalancer.server.port=5000"
|
||||||
|
- "traefik.http.middlewares.cd-auth.basicauth.users=manohar:$$2y$$05$$KMo07kusNXiLmQ8orl6HseY5KyVof74gw6Z.7MSFtqYhuNX0otSJm"
|
||||||
|
- "traefik.http.routers.changedetection.middlewares=cd-auth@docker"
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.changedetection.rule=Host(`watch.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.changedetection.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.changedetection.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.changedetection.loadbalancer.server.port=5000"
|
||||||
|
- "traefik.http.middlewares.cd-auth.basicauth.users=manohar:$$2y$$05$$KMo07kusNXiLmQ8orl6HseY5KyVof74gw6Z.7MSFtqYhuNX0otSJm"
|
||||||
|
- "traefik.http.routers.changedetection.middlewares=cd-auth@docker"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
cd_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
cd_internal:
|
||||||
|
driver: bridge
|
||||||
52
forgejo.compose.yml
Normal file
52
forgejo.compose.yml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
# Forgejo — git.manohargupta.com
|
||||||
|
# Self-hosted Git for scripts, Power Query files, compose files, n8n exports.
|
||||||
|
# Storage: SQLite (single-user, zero ops overhead vs Postgres).
|
||||||
|
# SSH clone port: 2222 (host) -> 22 (container). Never conflicts with system SSH.
|
||||||
|
# Fix v2: volume mounted to /data (Forgejo Docker standard), not /var/lib/gitea.
|
||||||
|
|
||||||
|
services:
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:10
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
USER_UID: "1000"
|
||||||
|
USER_GID: "1000"
|
||||||
|
FORGEJO__server__DOMAIN: git.manohargupta.com
|
||||||
|
FORGEJO__server__ROOT_URL: https://git.manohargupta.com/
|
||||||
|
FORGEJO__server__SSH_DOMAIN: git.manohargupta.com
|
||||||
|
FORGEJO__server__SSH_PORT: "2222"
|
||||||
|
FORGEJO__server__SSH_LISTEN_PORT: "22"
|
||||||
|
FORGEJO__service__DISABLE_REGISTRATION: "true"
|
||||||
|
FORGEJO__service__REQUIRE_SIGNIN_VIEW: "false"
|
||||||
|
# /data is Forgejo Docker's standard root -- /var/lib/gitea was wrong
|
||||||
|
FORGEJO__database__DB_TYPE: sqlite3
|
||||||
|
FORGEJO__database__PATH: /data/gitea/forgejo.db
|
||||||
|
volumes:
|
||||||
|
- forgejo_data:/data # standard Forgejo Docker data root
|
||||||
|
- forgejo_config:/etc/gitea
|
||||||
|
- /etc/timezone:/etc/timezone:ro
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
ports:
|
||||||
|
- "2222:22"
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.forgejo.rule=Host(`git.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.forgejo.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.forgejo.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.forgejo.loadbalancer.server.port=3000"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:3000"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
forgejo_data:
|
||||||
|
forgejo_config:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
68
miniflux.compose.yml
Normal file
68
miniflux.compose.yml
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Miniflux — feeds.manohargupta.com
|
||||||
|
# Minimal RSS reader. Postgres-only (no SQLite option in Miniflux).
|
||||||
|
# Intended sources: MercomIndia, IEEFA, MNRE press releases, CERC, SECI, etc.
|
||||||
|
# n8n will poll the Miniflux API to digest unread entries and push to Telegram.
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
miniflux-db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: miniflux
|
||||||
|
POSTGRES_USER: miniflux
|
||||||
|
POSTGRES_PASSWORD: ${MINIFLUX_DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- miniflux_db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- miniflux_internal # DB never touches the public network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U miniflux -d miniflux"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
miniflux:
|
||||||
|
image: miniflux/miniflux:2.2.7 # Pinned -- upgrade deliberately
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
miniflux-db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
# Full Postgres DSN -- sslmode=disable is fine on the internal bridge network
|
||||||
|
DATABASE_URL: postgres://miniflux:${MINIFLUX_DB_PASSWORD}@miniflux-db/miniflux?sslmode=disable
|
||||||
|
# Run DB migrations automatically on boot
|
||||||
|
RUN_MIGRATIONS: "1"
|
||||||
|
# Auto-create admin account on first boot -- these env vars are ignored after that
|
||||||
|
CREATE_ADMIN: "1"
|
||||||
|
ADMIN_USERNAME: ${MINIFLUX_ADMIN_USERNAME}
|
||||||
|
ADMIN_PASSWORD: ${MINIFLUX_ADMIN_PASSWORD}
|
||||||
|
# IST so relative timestamps ("2 hours ago") match your timezone
|
||||||
|
TZ: Asia/Kolkata
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
- miniflux_internal
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.miniflux.rule=Host(`feeds.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.miniflux.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.miniflux.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.miniflux.loadbalancer.server.port=8080"
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.miniflux.rule=Host(`feeds.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.miniflux.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.miniflux.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.miniflux.loadbalancer.server.port=8080"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
miniflux_db_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
miniflux_internal:
|
||||||
|
driver: bridge
|
||||||
79
n8n.compose.yml
Normal file
79
n8n.compose.yml
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
# n8n — automate.manohargupta.com
|
||||||
|
# Visual workflow automation. Postgres-backed (SQLite locks under concurrent runs).
|
||||||
|
# v2: Added deploy.labels for Traefik swarm provider (Dokploy uses swarm stack deploy).
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
n8n-db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: n8n
|
||||||
|
POSTGRES_USER: n8n
|
||||||
|
POSTGRES_PASSWORD: ${N8N_DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- n8n_db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- n8n_internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
n8n:
|
||||||
|
image: docker.n8n.io/n8nio/n8n:1.83.0
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
n8n-db:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
N8N_HOST: automate.manohargupta.com
|
||||||
|
N8N_PORT: "5678"
|
||||||
|
N8N_PROTOCOL: https
|
||||||
|
WEBHOOK_URL: https://automate.manohargupta.com/
|
||||||
|
N8N_EDITOR_BASE_URL: https://automate.manohargupta.com/
|
||||||
|
DB_TYPE: postgresdb
|
||||||
|
DB_POSTGRESDB_HOST: n8n-db
|
||||||
|
DB_POSTGRESDB_PORT: "5432"
|
||||||
|
DB_POSTGRESDB_DATABASE: n8n
|
||||||
|
DB_POSTGRESDB_USER: n8n
|
||||||
|
DB_POSTGRESDB_PASSWORD: ${N8N_DB_PASSWORD}
|
||||||
|
# NEVER lose this key -- all stored credentials are encrypted with it
|
||||||
|
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
|
||||||
|
GENERIC_TIMEZONE: Asia/Kolkata
|
||||||
|
TZ: Asia/Kolkata
|
||||||
|
N8N_DIAGNOSTICS_ENABLED: "false"
|
||||||
|
N8N_VERSION_NOTIFICATIONS_ENABLED: "false"
|
||||||
|
volumes:
|
||||||
|
- n8n_data:/home/node/.n8n
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
- n8n_internal
|
||||||
|
# Container-level labels (docker provider)
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.n8n.rule=Host(`automate.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||||
|
# Service-level labels (swarm provider) -- Dokploy deploys as swarm stack
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.n8n.rule=Host(`automate.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.n8n.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
n8n_data:
|
||||||
|
n8n_db_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
n8n_internal:
|
||||||
|
driver: bridge
|
||||||
123
paperless.compose.yml
Normal file
123
paperless.compose.yml
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
# Paperless-ngx — docs.manohargupta.com
|
||||||
|
# OCR + full-text search for PDFs and Office docs (lender drafts, tariff schedules, etc.)
|
||||||
|
# 5 containers: webserver, redis broker, postgres, tika (Office), gotenberg (PDF render).
|
||||||
|
# Tika + Gotenberg add ~400 MB RAM but are essential for .docx/.xlsx indexing.
|
||||||
|
# First boot is slow (~90s) -- DB migrations run before the web UI becomes available.
|
||||||
|
|
||||||
|
services:
|
||||||
|
|
||||||
|
# Redis: job queue between the web UI and the OCR/consumer worker
|
||||||
|
paperless-broker:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- paperless_redis:/data
|
||||||
|
networks:
|
||||||
|
- paperless_internal
|
||||||
|
|
||||||
|
# Postgres: document metadata, tags, correspondents, search index
|
||||||
|
paperless-db:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: paperless
|
||||||
|
POSTGRES_USER: paperless
|
||||||
|
POSTGRES_PASSWORD: ${PAPERLESS_DB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- paperless_db_data:/var/lib/postgresql/data
|
||||||
|
networks:
|
||||||
|
- paperless_internal
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U paperless -d paperless"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
# Gotenberg: renders Office files (docx, xlsx) to PDF before OCR
|
||||||
|
paperless-gotenberg:
|
||||||
|
image: docker.io/gotenberg/gotenberg:8
|
||||||
|
restart: unless-stopped
|
||||||
|
command:
|
||||||
|
- "gotenberg"
|
||||||
|
- "--chromium-disable-javascript=true" # Security: no JS execution
|
||||||
|
- "--chromium-allow-list=file:///tmp/.*" # Only allow local file access
|
||||||
|
networks:
|
||||||
|
- paperless_internal
|
||||||
|
|
||||||
|
# Tika: extracts text from Office formats that Gotenberg can't handle alone
|
||||||
|
paperless-tika:
|
||||||
|
image: docker.io/apache/tika:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- paperless_internal
|
||||||
|
|
||||||
|
# Main app: web UI + OCR worker + consumer (watches the consume volume)
|
||||||
|
paperless:
|
||||||
|
image: ghcr.io/paperless-ngx/paperless-ngx:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
paperless-db:
|
||||||
|
condition: service_healthy
|
||||||
|
paperless-broker:
|
||||||
|
condition: service_started
|
||||||
|
paperless-gotenberg:
|
||||||
|
condition: service_started
|
||||||
|
paperless-tika:
|
||||||
|
condition: service_started
|
||||||
|
environment:
|
||||||
|
PAPERLESS_REDIS: redis://paperless-broker:6379
|
||||||
|
PAPERLESS_DBHOST: paperless-db
|
||||||
|
PAPERLESS_DBNAME: paperless
|
||||||
|
PAPERLESS_DBUSER: paperless
|
||||||
|
PAPERLESS_DBPASS: ${PAPERLESS_DB_PASSWORD}
|
||||||
|
# Secret key for Django session signing -- must be stable across restarts
|
||||||
|
PAPERLESS_SECRET_KEY: ${PAPERLESS_SECRET_KEY}
|
||||||
|
PAPERLESS_URL: https://docs.manohargupta.com
|
||||||
|
# Office doc support via Tika + Gotenberg
|
||||||
|
PAPERLESS_TIKA_ENABLED: "1"
|
||||||
|
PAPERLESS_TIKA_GOTENBERG_ENDPOINT: http://paperless-gotenberg:3000
|
||||||
|
PAPERLESS_TIKA_ENDPOINT: http://paperless-tika:9998
|
||||||
|
# OCR: 'skip' means don't re-OCR docs that already have a text layer (faster)
|
||||||
|
# Add '+hin' to language if you have Hindi documents: eng+hin (adds ~200 MB)
|
||||||
|
PAPERLESS_OCR_LANGUAGE: eng
|
||||||
|
PAPERLESS_OCR_MODE: skip
|
||||||
|
PAPERLESS_TIME_ZONE: Asia/Kolkata
|
||||||
|
USERMAP_UID: "1000"
|
||||||
|
USERMAP_GID: "1000"
|
||||||
|
volumes:
|
||||||
|
- paperless_data:/usr/src/paperless/data # search index, models
|
||||||
|
- paperless_media:/usr/src/paperless/media # original files + thumbnails
|
||||||
|
- paperless_export:/usr/src/paperless/export # manual export target
|
||||||
|
- paperless_consume:/usr/src/paperless/consume # drop files here to auto-ingest
|
||||||
|
networks:
|
||||||
|
- dokploy-network
|
||||||
|
- paperless_internal
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.paperless.rule=Host(`docs.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.paperless.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.paperless.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
|
||||||
|
deploy:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.docker.network=dokploy-network"
|
||||||
|
- "traefik.http.routers.paperless.rule=Host(`docs.manohargupta.com`)"
|
||||||
|
- "traefik.http.routers.paperless.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.paperless.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.paperless.loadbalancer.server.port=8000"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
paperless_redis:
|
||||||
|
paperless_db_data:
|
||||||
|
paperless_data:
|
||||||
|
paperless_media:
|
||||||
|
paperless_export:
|
||||||
|
paperless_consume:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
dokploy-network:
|
||||||
|
external: true
|
||||||
|
paperless_internal:
|
||||||
|
driver: bridge
|
||||||
Loading…
Add table
Reference in a new issue