# Manohar's Infrastructure Self-hosted stack on Hetzner CX32 (Helsinki), deployed via Dokploy + Traefik. ## Services | App | URL | Purpose | |---|---|---| | Forgejo | https://git.manohargupta.com | Self-hosted Git | | n8n | https://automate.manohargupta.com | Workflow automation | | Apprise | https://notify.manohargupta.com | Notification API (Tailscale only) | | Miniflux | https://feeds.manohargupta.com | RSS reader | | ChangeDetection | https://watch.manohargupta.com | Webpage change monitor | | Paperless-ngx | https://docs.manohargupta.com | Document OCR + search | | Tiger Agent | https://agent.manohargupta.com | AI orchestration | | Dokploy | https://dokploy.manohargupta.com | Docker orchestration | | Uptime Kuma | https://status.manohargupta.com | Monitoring | | Umami | 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: