Skip to content

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

  1. Single Host: All services on one VM
  2. Vertical Scaling Only: Add more RAM/CPU to VM
  3. Storage: Fixed disk sizes (need to plan ahead)
  4. No Redundancy: Single point of failure

Future Growth Path

  1. Add More VMs: Distribute services across multiple hosts
  2. External Database: Move PostgreSQL to dedicated VM
  3. Object Storage: Use S3 for large files
  4. 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:

# Files never synced to GitHub
.env
*.key
*.pem
*-secret*
/private/
/credentials/

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

  • stackwiz for 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

  1. Expand Content Management: Add more AI-powered content processing services
  2. Enhance Monitoring: Integrate Watchtower metrics into Grafana dashboards
  3. Improve Automation: Add more intelligent update policies
  4. 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.