System Architecture Overview¶
High-Level Architecture¶
This infrastructure runs on a single Proxmox VM with a dual-disk setup, hosting multiple containerized services behind a Traefik reverse proxy with automatic SSL via Cloudflare.
┌─────────────────────────────────────────────────────────────────────┐
│ INTERNET │
│ │
│ Users → https://*.rbnk.uk → Cloudflare DNS → Your Server (Hetzner) │
└──────────────────────────────────┬──────────────────────────────────┘
│
┌──────────────┴──────────────┐
│ Proxmox Host │
│ (EX130-R: 24c/48t, 256GB) │
│ Helsinki, Finland │
└──────────────┬──────────────┘
│
┌──────────────┴──────────────┐
│ Ubuntu 24.04 LTS VM │
│ (8 vCPU, 16GB RAM) │
│ │
│ ┌───────────┬───────────┐ │
│ │ /dev/sda │ /dev/sdb │ │
│ │ 60GB OS │ 200GB Data│ │
│ └───────────┴───────────┘ │
└──────────────┬──────────────┘
│
Container Architecture¶
Docker Host
┌────────────────────────────────────────────────────────────────┐
│ │
│ ┌─────────────┐ traefik_proxy Network (172.18.0.0/16) │
│ │ Traefik │ ←─────────────────────────────────────────┐ │
│ │ (Router) │ │ │
│ └──────┬──────┘ │ │
│ │ │ │
│ ├──────→ [Kong Gateway] ←────┐ │ │
│ │ ↓ │ │ │
│ │ ┌─────┴─────┐ │ supabase_internal │ │
│ │ ├→ [Studio] │ │ Network │ │
│ │ ├→ [Auth] │ │ (172.19.0.0/16) │ │
│ │ ├→ [Rest] │ │ │ │
│ │ ├→ [Storage]│ │ │ │
│ │ ├→ [Meta] ├←────────┤ ↑ │ │
│ │ ├→ [Realtime] │ │ │ │
│ │ ├→ [Functions] │ [PostgreSQL] │ │
│ │ ├→ [Analytics] │ (Isolated) │ │
│ │ ├→ [Imgproxy] │ │ │
│ │ └→ [Vector] ─→ logs │ │ │
│ │ │ │
│ ├──────→ [Open WebUI] ←────────────────────────────┘ │
│ ├──────→ [LiteLLM] │
│ ├──────→ [n8n] │
│ ├──────→ [Gitea] ←─────── Git repositories & web UI │
│ │ ↓ │
│ │ ┌─────┴─────┐ Redis Networks │
│ │ │ Cache │ cache_redis_network (172.20.0.0) │
│ │ │ Sessions │ session_redis_network (172.21.0.0)│
│ │ └───────────┘ │
│ │ ↑ │
│ │ [Cache Redis] ←─── Ephemeral data caching │
│ │ [Session Redis] ←─ Persistent session storage │
│ │ │
│ ├──────→ [Rclone] ←────── Cloud sync & R2 backups │
│ ├──────→ [Prometheus/Grafana] ← Monitoring stack │
│ ├──────→ [StackWiz MCP] ←───── AI-powered stack mgmt │
│ ├──────→ [Apprise/ntfy] ←──── Notification stack │
│ ├──────→ [Metube] ←────────── YouTube downloader │
│ ├──────→ [Paperless-AI] ←──── AI document management │
│ ├──────→ [Watchtower] ←────── Automated updates │
│ ├──────→ [Authentik] ←─────── Identity/SSO management │
│ ├──────→ [Coolify] ←───────── Application deployment │
│ ├──────→ [Jellyfin] ←──────── Media streaming │
│ ├──────→ [Zurg/Rclone] ←───── Media mount/streaming │
│ ├──────→ [Zipline] ←───────── File sharing │
│ └──────→ [Other Services] │
│ │
│ Host Services: │
│ └── Ollama (Port 11434) ← Accessed by containers via gateway │
└────────────────────────────────────────────────────────────────┘
Why This Architecture?¶
1. Traefik as Reverse Proxy¶
Decision: Use Traefik instead of Nginx/Apache
Reasons: - Automatic SSL: Integrates with Cloudflare DNS for wildcard certificates - Docker Native: Reads container labels for dynamic configuration - No Manual Config: Services self-register via Docker labels - Path-Based Routing: Single domain can serve multiple services
Alternative Considered: Nginx Proxy Manager - Rejected because: Requires manual configuration for each service
2. Multi-Network Architecture¶
Decision: Separate networks for different service types: traefik_proxy, supabase_internal, cache_redis_network, session_redis_network
Reasons: - Security: Database never exposed to public network - Performance: Dedicated Redis networks for caching/sessions - Flexibility: Services can selectively connect to needed networks - Debugging: Clear network boundaries for troubleshooting
# Example: Service using multiple networks
networks:
- traefik_proxy # For public access
- supabase_internal # For database access
- cache_redis_network # For ephemeral caching
- session_redis_network # For persistent sessions
3. Redis Consolidation Strategy¶
Decision: Two shared Redis instances instead of per-service Redis
Reasons: - Resource Efficiency: Shared memory pool reduces overall RAM usage - Operational Simplicity: Two instances vs many individual Redis containers - Performance: Purpose-built configurations (cache vs sessions) - Consistency: Unified Redis management and monitoring
Implementation:
# Cache Redis - Ephemeral data
cache-redis:
command: redis-server --save "" --appendonly no --maxmemory 4gb --maxmemory-policy allkeys-lru
# Session Redis - Persistent data
session-redis:
command: redis-server --save 300 100 --appendonly yes --maxmemory 1gb --maxmemory-policy volatile-lru
4. Cloudflare DNS Challenge¶
Decision: Use DNS-01 challenge for SSL certificates
Reasons:
- Wildcard Support: One certificate for *.rbnk.uk
- No Port Exposure: Works without opening port 80/443
- Behind NAT: Works even if server is behind firewall
- Automation: Traefik handles renewal automatically
Trade-off: Requires Cloudflare API credentials
5. Storage Architecture¶
Decision: Separate OS and Data disks
/dev/sda (60GB) - OS Disk /dev/sdb (200GB) - Data Disk
├── / (System files) └── /srv/dockerdata
├── /boot ├── SERVICE_NAME/
├── /usr │ ├── docker-compose.yml
└── /var │ ├── .env
│ └── data/
└── _scripts/
Reasons:
- Isolation: OS disk filling won't affect services
- Performance: Data I/O doesn't compete with system
- Backup: Easy to backup entire /srv/dockerdata
- Growth: Data disk can be expanded independently
6. Service Isolation Pattern¶
Decision: Each service in its own directory with dedicated compose file
Structure:
/srv/dockerdata/SERVICE/
├── docker-compose.yml # Service definition
├── .env # Environment variables (640 permissions)
├── data/ # Persistent data
└── config/ # Configuration files
Reasons: - Independence: Services can be managed separately - Portability: Easy to move services between hosts - Clarity: Clear boundaries between services - Backup: Simple per-service backup strategy
Network Flow¶
External Request Flow¶
1. User → https://app.rbnk.uk
2. Cloudflare DNS → Server IP
3. Traefik (Port 443) → Checks routing rules
4. Traefik → Routes to correct container
5. Container → Processes request
6. Response → Traefik → User
Internal Service Communication¶
1. Service A → needs database
2. Service A → connects via supabase_internal network
3. PostgreSQL → only accessible on internal network
4. Response → back through internal network
Security Layers¶
1. Network Level¶
- Internal services not exposed to public
- Docker's built-in firewall rules
- Host firewall (though not fully configured)
2. Application Level¶
- JWT tokens for API authentication
- Row-level security in PostgreSQL
- Service-specific authentication
3. Infrastructure Level¶
- SSL/TLS for all public endpoints
- Environment variables for secrets
- File permissions (though needs improvement)
Scaling Considerations¶
Current Limitations¶
- Single Host: All services on one VM
- Vertical Scaling Only: Add more RAM/CPU to VM
- Storage: Fixed disk sizes (need to plan ahead)
- No Redundancy: Single point of failure
Future Growth Path¶
- Add More VMs: Distribute services across multiple hosts
- External Database: Move PostgreSQL to dedicated VM
- Object Storage: Use S3 for large files
- Load Balancing: Multiple instances behind Traefik
Git Repository Architecture¶
Gitea Integration¶
Decision: Deploy Gitea as the primary Git service
Reasons: - Self-hosted: Complete control over repositories and data - Lightweight: Low resource requirements (< 512MB RAM) - Feature-rich: Issues, pull requests, CI/CD integration - API-driven: Enables automation and integrations
Architecture:
┌─────────────────────────────────────────────────────────────┐
│ Git Repository Flow │
│ │
│ Developer ──push──→ Gitea (Private) │
│ │ │
│ ├── Webhook triggers │
│ │ │
│ ├── Backup System (2 AM daily) │
│ │ │
│ └── Sync to GitHub (automated) │
│ │ │
│ ↓ │
│ GitHub (Public Mirror) │
│ │
│ Security: .giteaignore filters sensitive files │
└─────────────────────────────────────────────────────────────┘
Repository Sync Strategy¶
Decision: Automated one-way sync from Gitea to GitHub
Implementation: - Systemd timer runs every 15 minutes - Security filter prevents sensitive file exposure - Maintains commit history and authorship - Handles force pushes and branch deletions
Security Filtering:
Key Architectural Patterns¶
1. Configuration as Code¶
Everything defined in files:
- docker-compose.yml for services
- .env for configuration
- No clicking in UIs
2. Consistent Structure¶
Every service follows same pattern: - Same directory layout - Same Docker label format - Same backup approach
3. Automation First¶
stackwizfor service creation- Backup scripts for data protection
- Systemd timers for scheduling (needs implementation)
4. Observability¶
Fully implemented monitoring stack: - Prometheus: Collects metrics from all services including Rclone backup metrics - Grafana: Visualization dashboards including dedicated Rclone Backup Monitoring dashboard - Loki: Log aggregation for centralized logging - AlertManager: Notifications for backup failures and system issues - Node Exporter: System metrics collection with custom Rclone textfile metrics - Watchtower: Monitors container updates with smart exclusions for databases and infrastructure
5. Unified Notifications¶
Comprehensive notification system: - Apprise: Universal notification hub supporting 100+ services - ntfy: Push notification server with authentication and topics - Mailrise: SMTP gateway converting emails to push notifications - Uptime Kuma: Service monitoring with integrated alerting - Integration: All services can send notifications through multiple channels
6. Content Management & Updates¶
Modern content and update management: - Metube: Web-based YouTube downloader with queue management and format selection - Paperless-AI: AI-powered document management with intelligent search and organization - Watchtower: Automated container updates with intelligent exclusions for critical services
Architecture Decisions Summary¶
| Decision | Rationale | Trade-off |
|---|---|---|
| Traefik | Auto-SSL, Docker native | More complex than Nginx |
| Multi Networks | Security isolation + performance | More complex networking |
| Redis Consolidation | Resource efficiency, unified management | Shared dependency risk |
| Cloudflare DNS | Wildcard SSL | Vendor lock-in |
| Dual Disk | Separation of concerns | Fixed partition sizes |
| Service Isolation | Independence | Some resource duplication |
| Gitea | Self-hosted Git control | Additional service to maintain |
| Dual Repository | Security + visibility | Sync complexity |
| Unified Notifications | Central alert management | Multiple services to maintain |
| Metube | Local media downloading | Storage space requirements |
| Paperless-AI | AI document organization | AI processing overhead |
| Watchtower | Automated updates | Requires careful exclusion management |
Watchtower Update Strategy¶
Automated Updates with Protection: Watchtower handles daily container updates while protecting critical services through exclusion labels:
# Critical services excluded from auto-updates
labels:
- "com.centurylinklabs.watchtower.enable=false"
Protected Service Categories: - Database Services: PostgreSQL, Redis instances require manual migration planning - Infrastructure: Traefik, monitoring stack need coordinated updates - Security Services: Authentication and DNS services require validation - Self-Protection: Watchtower excludes itself to prevent update conflicts
Benefits: - Keeps non-critical services updated automatically - Prevents data corruption from unplanned database updates - Maintains system stability through infrastructure protection - Provides email notifications for all update activities
Next Steps¶
- Expand Content Management: Add more AI-powered content processing services
- Enhance Monitoring: Integrate Watchtower metrics into Grafana dashboards
- Improve Automation: Add more intelligent update policies
- Security Hardening: Implement secrets management
This architecture provides a solid foundation for a self-hosted infrastructure while maintaining flexibility for future growth and automated management.