# rbnk.uk Documentation > Generated: 2026-05-23T22:55:32.121395Z > Source: https://docs.rbnk.uk ## README ### Docker Infrastructure Documentation

Application error: a client-side exception has occurred while loading gymplan.rbnk.uk (see the browser console for more information).

Welcome to the comprehensive documentation for the self-hosted Docker infrastructure on `rbnk.uk`. > **Auto-rebuild enabled**: Documentation automatically rebuilds when changes are detected. ## 🆕 What's New ### December 2025 - **CodeScribe** - Code documentation generator with REST API, MCP server, and CLI for AI context generation (\1) - **Browser Use** - LLM-powered browser automation service with REST API, Gradio UI, and MCP integration (\1) - **Claude Code Safety Hooks** - Comprehensive safety infrastructure with pre/post tool hooks, audit logging, and dangerous command blocking (\1) - **Backup Infrastructure Overhaul** - Enhanced dual backup system with Prometheus metrics, Grafana dashboards, and improved R2 integration - **Supabase Enhancements** - Added Realtime, Edge Functions, Analytics, Vector (logging), and ImgProxy services ### November 2025 - **Zurg + Jellyfin** - Media streaming stack with Real-Debrid integration and hardware transcoding - **Docs Auto-Rebuild** - Automatic documentation rebuilding on git push with llms.txt generation ### August 2025 - **Iodine DNS Tunnel** - DNS tunneling service optimized for captive portal bypass (port 53 with AdGuard forwarding) - **Coolify-Traefik Bridge** - Automated routing of Coolify services through main Traefik with DNS record creation - **Authentik SSO** - Single sign-on with dual-route authentication strategy - **StackWiz MCP** - Model Context Protocol server enabling AI-powered Docker stack management - **Monitoring Overhaul** - Comprehensive Prometheus, Grafana, Loki, and AlertManager improvements ## 📚 Documentation Index ### Architecture - \1 - High-level architecture and component relationships - \1 - Docker networks, Traefik routing, SSL setup - \1 - Dual-disk configuration and data management - \1 - Architectural decisions and their rationale ### Services #### Core Infrastructure - \1 - Reverse proxy and SSL termination - \1 - Prometheus, Grafana, Loki, and AlertManager - \1 - Apprise, ntfy, Mailrise, and Uptime Kuma - \1 - Network-wide DNS filtering and ad blocking - \1 - DNS tunneling service for bypassing restrictive firewalls #### Data & Development - \1 - PostgreSQL-based backend-as-a-service - \1 - Lightweight SQLite alternative - \1 - Self-hosted Git service with GitHub mirroring #### AI & Automation - \1 - LLM-powered browser automation with REST API and Gradio UI - \1 - Code documentation generator for AI context with MCP server - \1 - Local LLM service with dual-disk setup - \1 - AI chat interface - \1 - Unified LLM API proxy - \1 - Virtual keys implementation for secure LLM access - \1 - Workflow automation platform with visual programming interface - \1 - Model Context Protocol server for Docker stack management #### Storage & Backup - \1 - Cloud storage backup and synchronization service with infrastructure backups to Cloudflare R2 - \1 - AI-powered document management with intelligent search #### Media & Streaming - \1 - Media streaming stack with Real-Debrid integration - \1 - Unified streaming addon management - \1 - Web-based YouTube downloader for video and audio content #### Utilities - \1 - Automated container updates with service exclusions ### Operations - \1 - Backup procedures and disaster recovery - \1 - Cloudflare DNS configuration and management - \1 - Health checks, logs, and metrics - \1 - Permissions, secrets management, hardening - \1 - Claude Code safety infrastructure and audit logging - \1 - Updates, disk management, troubleshooting - \1 - Port forwarding configuration and automation - \1 - VPN access configuration ### Development - \1 - Get started with development quickly - \1 - Using the stack creation wizard - \1 - How to add new services to the infrastructure - \1 - AI assistant skills, agents, commands, and hooks - \1 - Common issues and solutions ### Troubleshooting - \1 - Diagnosing and fixing SSL/TLS problems ### Security - \1 - Dual repository security model, secret filtering, and sync workflows ### Version Control - \1 - Development workflow with Gitea primary and GitHub mirror - \1 - Automated secure synchronization process ### Reference - \1 - Complete service port mapping - \1 - All configuration variables - \1 - Common commands cheatsheet ## 🚀 Quick Start 1. **Deploy a new service**: Use `stackwiz` to create a new service stack 2. **Access services**: All services available at `https://*.rbnk.uk` 3. **View logs**: `docker compose -f SERVICE/docker-compose.yml logs -f` 4. **Backup data**: Run `/srv/dockerdata/_scripts/SERVICE-backup.sh` 5. **Access Git**: Browse code at `https://gitea.rbnk.uk` or clone via SSH/HTTPS 6. **AI Automation**: Use StackWiz MCP with Claude to create and manage Docker stacks automatically ## 🏗️ Infrastructure Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Internet │ └────────────────────┬────────────────────────────────────────┘ │ HTTPS (*.rbnk.uk) ┌─────────┴──────────┐ │ Cloudflare DNS │ │ (SSL Certificates)│ └─────────┬──────────┘ │ ┌─────────┴──────────┐ │ Traefik │ ← Reverse Proxy │ (Docker Labels) │ Auto-SSL └─────────┬──────────┘ │ ┌────────────┴────────────┬─────────────┬──────────────┐ │ │ │ │ ┌───────┴────────┐ ┌────────┴───────┐ ┌──┴────────┐ ┌──┴────────┐ │ Supabase │ │ Open WebUI │ │ LiteLLM │ │ Gitea │ │ (PostgreSQL) │ │ (AI Chat UI) │ │ (LLM API) │ │ (Git) │ └────────────────┘ └────────────────┘ └───────────┘ └───────────┘ ┌────────────────┐ ┌─────────────────────┐ ┌────────────────┐ │ Rclone │ │ Monitoring Stack │ │ n8n + MCP │ │ (Cloud Backup) │ │ (Prometheus/Grafana)│ │ (Automation) │ │ & R2 Storage │ └─────────────────────┘ └────────────────┘ └────────────────┘ ┌─────────────────────────────────────────────────────────────────┐ │ Notification Stack │ │ (Apprise + ntfy + Mailrise + Uptime Kuma) │ └─────────────────────────────────────────────────────────────────┘ ``` ## 💾 Storage Layout - **System Disk** (`/dev/sda` - 100GB): OS and system files (29% used) - **Data Disk** (`/dev/sdb` - 500GB): `/srv/dockerdata` - All Docker data (39% used) ## 🔐 Version Control The infrastructure uses a **dual repository strategy** for secure code management: - **Private Repository** (Gitea): `https://gitea.rbnk.uk` - Contains all files including sensitive configuration - **Public Repository** (GitHub): Public mirror with sensitive files automatically filtered out - **Automated Sync**: Changes pushed to Gitea are automatically synced to GitHub with security filtering - **Access**: Gitea provides full Git functionality with web UI for code browsing and management ## 🔑 Key Principles 1. **Service Isolation**: Each service in its own directory with dedicated `docker-compose.yml` 2. **Automatic SSL**: Traefik + Cloudflare handles all SSL certificates 3. **Network Security**: Internal services (databases) not exposed publicly 4. **Standardized Structure**: Consistent patterns across all services 5. **Infrastructure as Code**: Everything configured through files, not UIs 6. **Secure Version Control**: Private Gitea instance with automated public GitHub sync ## ⚠️ Important Notes - **Disk Space**: System disk (100GB, 29% used), Data disk (500GB, 39% used) - healthy capacity - **Backups**: Automated dual backup system - cloud backups at 2 AM, infrastructure backups to R2 at 4 AM - **Secrets**: Stored in `.env` files - ensure proper permissions (640) - **Container Updates**: Watchtower handles automatic updates but critical services (databases, infrastructure) are protected - **Git Security**: Never commit sensitive files to public repos - use Gitea for private code ## 📝 Contributing to Docs When adding documentation: 1. Include the "why" not just the "how" 2. Add examples where helpful 3. Cross-reference related documentation 4. Update this index when adding new guides --- *Last Updated: December 2025 - Added CodeScribe code documentation service with MCP server* --- ## architecture > decisions ### Architecture Decision Records (ADR) Last Updated: 2025-12-02 ## Overview This document logs key architectural decisions made for the infrastructure, including context, rationale, trade-offs, and potential migration paths. --- ## ADR-001: Use Docker Compose for Service Orchestration **Date**: 2024-01-01 **Status**: Accepted **Context**: Need to deploy multiple services with complex interdependencies on a single VM. ### Decision Use Docker Compose instead of Kubernetes or Docker Swarm for service orchestration. ### Rationale - Single VM deployment doesn't justify Kubernetes complexity - Docker Compose provides sufficient orchestration for our scale - Easier to debug and maintain for small teams - Native support for environment variables and networking - Simple backup and restore procedures ### Trade-offs - **Pros**: Simple, maintainable, good documentation, fast deployment - **Cons**: No built-in HA, limited scaling options, manual updates ### Alternatives Considered 1. **Kubernetes**: Too complex for single-node deployment 2. **Docker Swarm**: Better for multi-node, adds unnecessary complexity 3. **Systemd units**: Less portable, harder dependency management ### Migration Path If scaling beyond single node: 1. Convert compose files to Kubernetes manifests using Kompose 2. Implement external configuration management (Helm/Kustomize) 3. Move to managed Kubernetes service ### Lessons Learned - Compose v2 syntax provides all needed features - Network isolation works well for security - Volume management requires careful planning --- ## ADR-002: Use Traefik as Reverse Proxy **Date**: 2024-01-15 **Status**: Accepted **Context**: Need reverse proxy with automatic SSL and service discovery. ### Decision Use Traefik v3 instead of Nginx, Caddy, or HAProxy. ### Rationale - Native Docker integration with label-based configuration - Automatic SSL with Let's Encrypt and Cloudflare DNS - Built-in service discovery - Good observability with metrics and access logs - Active development and community ### Trade-offs - **Pros**: Auto-configuration, great Docker support, modern features - **Cons**: Learning curve, more complex than Nginx for simple cases ### Alternatives Considered 1. **Nginx**: Requires manual configuration, no auto-discovery 2. **Caddy**: Simpler but less flexible for complex routing 3. **HAProxy**: Powerful but lacks modern convenience features ### Migration Path To migrate away from Traefik: 1. Export routing rules from Traefik API 2. Convert to target proxy configuration 3. Implement service discovery mechanism 4. Test thoroughly with canary deployments ### Lessons Learned - Label-based configuration prevents configuration drift - Middleware chains are powerful for auth/redirects - DNS challenge works better than HTTP for wildcard certs --- ## ADR-003: Self-Host Supabase **Date**: 2024-02-01 **Status**: Accepted **Context**: Need PostgreSQL-based backend with auth, storage, and realtime features. ### Decision Self-host Supabase instead of using managed service or building custom. ### Rationale - Full control over data and infrastructure - Cost-effective for our scale - All features available without restrictions - Can customize and extend as needed - Good architectural patterns to follow ### Trade-offs - **Pros**: No vendor lock-in, full control, cost savings, customizable - **Cons**: Maintenance burden, security responsibility, no SLA ### Alternatives Considered 1. **Managed Supabase**: Monthly costs, data sovereignty concerns 2. **Custom stack**: Too much development effort 3. **Firebase**: Vendor lock-in, not PostgreSQL 4. **Hasura + Auth0**: More complex, higher cost ### Migration Path To managed Supabase: 1. Use Supabase migration tools 2. Update connection strings 3. Verify auth flows work correctly To custom stack: 1. PostgREST can be replaced with custom API 2. GoTrue can be replaced with other auth 3. Database remains PostgreSQL ### Lessons Learned - Bootstrap SQL pattern works well for initialization - JWT secret management is critical - Storage API needs careful permission setup - Backup strategy essential from day one --- ## ADR-004: Use Cloudflare for DNS and SSL **Date**: 2024-02-15 **Status**: Accepted **Context**: Need reliable DNS with API access for automatic SSL certificates. ### Decision Use Cloudflare for DNS management and SSL certificate generation. ### Rationale - Free tier sufficient for our needs - Excellent API for DNS challenges - DDoS protection included - DNS analytics helpful for debugging - Good Traefik integration ### Trade-offs - **Pros**: Free, reliable, good API, security features - **Cons**: Dependency on external service, potential API limits ### Alternatives Considered 1. **Route53**: Costs money, AWS lock-in 2. **Self-hosted DNS**: Complexity, reliability concerns 3. **HTTP challenges**: Doesn't work for wildcard certs 4. **Manual certs**: Operational burden ### Migration Path To different DNS provider: 1. Update Traefik configuration for new provider 2. Migrate DNS records 3. Update API credentials 4. Test certificate renewal ### Lessons Learned - API token scoping important for security - Wildcard certificates simplify subdomain management - DNS challenge more reliable than HTTP --- ## ADR-005: Separate Networks for Security **Date**: 2024-03-01 **Status**: Accepted **Context**: Need network isolation between external-facing and internal services. ### Decision Use separate Docker networks: `traefik_proxy` for external, `supabase_internal` for database access, plus `cache_redis_network` and `session_redis_network` for Redis services. ### Rationale - Clear security boundary - Prevents accidental exposure - Easy to audit and understand - Native Docker feature - No performance penalty ### Trade-offs - **Pros**: Better security, clear boundaries, easy to implement - **Cons**: Services need multiple network attachments, some complexity ### Alternatives Considered 1. **Single network**: Security risk 2. **Network policies**: Not available in Docker 3. **Firewall rules**: More complex, less portable 4. **VLANs**: Overkill for single host ### Migration Path Already using best practice, but if needed: 1. Can add more networks for finer segmentation 2. Can implement service mesh if needed 3. Can add network policies with Kubernetes migration ### Lessons Learned - Explicit network membership prevents accidents - Internal services stay internal - Cross-network communication needs explicit configuration --- ## ADR-006: Systemd Timers for Scheduled Tasks **Date**: 2024-03-15 **Status**: Accepted **Context**: Need reliable scheduling for backups and maintenance tasks. ### Decision Use systemd timers instead of cron or container-based scheduling. ### Rationale - Better logging and monitoring - Dependency management - No additional containers needed - Handles system boot properly - Built into the OS ### Trade-offs - **Pros**: Reliable, good logs, dependency handling, no overhead - **Cons**: Linux-specific, requires systemd knowledge ### Alternatives Considered 1. **Cron**: Less visibility, basic logging 2. **Container scheduler**: Additional complexity 3. **n8n/Airflow**: Overkill for simple tasks 4. **Kubernetes CronJobs**: Not applicable ### Migration Path To container-based scheduling: 1. Convert timer units to CronJob specs 2. Implement job containers 3. Handle logging/monitoring 4. Test failure scenarios ### Lessons Learned - OnCalendar syntax intuitive for schedules - Persistent=true handles missed runs - Service units provide good isolation --- ## ADR-007: Git for Configuration Management **Date**: 2024-04-01 **Status**: Accepted **Context**: Need version control for infrastructure configuration. ### Decision Use Git repository for all configuration except secrets. ### Rationale - Version history for debugging - Easy rollback capabilities - Collaboration features - Can implement GitOps later - Industry standard practice ### Trade-offs - **Pros**: Version control, collaboration, audit trail, rollback - **Cons**: Need to carefully exclude secrets, requires discipline ### Alternatives Considered 1. **No version control**: Risky, no history 2. **Config management tool**: Overkill for our scale 3. **Database storage**: Less flexible, custom tooling ### Migration Path Already using best practice. For GitOps: 1. Implement CI/CD pipeline 2. Add automated testing 3. Implement automatic deployment 4. Add approval workflows ### Lessons Learned - .gitignore critical for security - .env.example files valuable for documentation - Commit messages should explain why - Regular commits prevent large disasters --- ## ADR-008: Bootstrap SQL for Database Initialization **Date**: 2024-04-15 **Status**: Accepted **Context**: Need reproducible database initialization for Supabase. ### Decision Use bootstrap SQL files that run on container initialization. ### Rationale - Declarative and reproducible - Version controlled with Git - No manual steps required - Can include custom functions/triggers - Survives container recreation ### Trade-offs - **Pros**: Reproducible, versioned, automated, documented - **Cons**: Only runs on first init, migrations need different approach ### Alternatives Considered 1. **Manual setup**: Error-prone, not reproducible 2. **Migration tool**: Complexity for initial setup 3. **Backup restore**: Includes data, less flexible ### Migration Path For schema changes after initialization: 1. Implement migration tool (Flyway, Liquibase) 2. Convert bootstrap SQL to migrations 3. Version all schema changes 4. Test rollback procedures ### Lessons Learned - Alphabetical ordering provides control - Idempotent scripts are better - Include test data cautiously - Document each script's purpose --- ## ADR-009: 14-Day Backup Retention **Date**: 2024-05-01 **Status**: Accepted **Context**: Balance between storage costs and recovery needs. ### Decision Implement 14-day retention for automated backups. ### Rationale - Two weeks covers most recovery scenarios - Storage costs remain reasonable - Allows recovery from weekend issues - Simple rotation logic - Can restore before issues discovered ### Trade-offs - **Pros**: Good balance, reasonable cost, simple implementation - **Cons**: No long-term archives, might miss slow issues ### Alternatives Considered 1. **7 days**: Too short for some scenarios 2. **30 days**: Higher storage costs 3. **Incremental forever**: Complex management 4. **No backups**: Unacceptable risk ### Migration Path To longer retention: 1. Implement tiered storage (daily/weekly/monthly) 2. Use object storage for archives 3. Implement backup testing automation 4. Consider incremental backups ### Lessons Learned - Test restores regularly - Monitor backup sizes - Alert on backup failures - Document restore procedures --- ## Decision Template ```markdown ## ADR-XXX: [Decision Title] **Date**: YYYY-MM-DD **Status**: Proposed/Accepted/Deprecated/Superseded **Context**: [Why this decision needed to be made] ### Decision [What was decided] ### Rationale [Why this decision was made] ### Trade-offs - **Pros**: [Positive outcomes] - **Cons**: [Negative outcomes] ### Alternatives Considered 1. **Option 1**: [Why not chosen] 2. **Option 2**: [Why not chosen] ### Migration Path [How to move away from this decision if needed] ### Lessons Learned [What we learned from implementing this] ``` ## Review Schedule Architectural decisions should be reviewed: - Quarterly for active development - Annually for stable systems - When major changes are proposed - When issues arise from decisions ## Change Process 1. Propose new ADR with status "Proposed" 2. Discuss with team 3. Update status to "Accepted" or "Rejected" 4. If replacing existing decision, mark old as "Superseded" 5. Document lessons learned after implementation --- ## architecture > network-topology ### Network Topology ## Physical Infrastructure The infrastructure runs inside a Proxmox virtualized environment with multiple network layers between the internet and Docker containers. ## Tailscale Network Layer Tailscale provides an overlay network that bypasses traditional port forwarding requirements and enables secure access to services. ### Tailscale Architecture ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ Tailscale Network │ │ │ │ ┌─────────────────┐ ┌──────────────────┐ │ │ │ Your Device │ │ Docker VM │ │ │ │ 100.x.x.x │ ←── WireGuard Encrypted Tunnel ──→ │ dockerhost │ │ │ │ │ │ 100.98.20.43 │ │ │ └─────────────────┘ └──────────────────┘ │ │ │ │ Direct P2P Connection (when possible) or via DERP Relay │ └─────────────────────────────────────────────────────────────────────────────────┘ ``` ### Key Benefits 1. **No Port Forwarding Required**: Access services without configuring Proxmox iptables 2. **End-to-End Encryption**: All traffic encrypted via WireGuard protocol 3. **Simplified Access**: Use Tailscale IPs or MagicDNS names instead of public IPs 4. **NAT Traversal**: Works behind firewalls and complex network setups 5. **Zero-Trust Security**: Only authorized devices can connect ### Access Patterns #### Via Tailscale (Recommended for Admin Access) ``` Your Device → Tailscale VPN → dockerhost (100.98.20.43) → Docker Services ↓ No Proxmox port forwarding needed! ``` #### Traditional Public Access (For End Users) ``` User Browser → Cloudflare → 157.180.63.15 → Proxmox NAT → 10.10.10.10 → Traefik ``` ### Service Access Examples 1. **SSH Access**: ```bash # Via Tailscale (no port forwarding needed) ssh user@100.98.20.43 # or ssh user@dockerhost.tail92c8e3.ts.net # Traditional (requires port 22 forwarding) ssh user@157.180.63.15 ``` 2. **Traefik Dashboard**: ```bash # Via Tailscale (direct access) http://100.98.20.43:8080 # Traditional (blocked without port forward) # Would need port 8080 forwarded, which is not recommended ``` 3. **Direct Database Access**: ```bash # Via Tailscale (secure, no public exposure) psql -h 100.98.20.43 -p 5432 -U postgres # Traditional (requires port 5432 forward - NOT recommended) ``` ### Tailscale Integration Points ``` ┌──────────────────────────────────────────────────────────────────────────────┐ │ Docker VM with Tailscale │ │ │ │ Network Interfaces: │ │ ├── eth0: 10.10.10.10/24 (Proxmox NAT) │ │ ├── tailscale0: 100.98.20.43 (Tailscale VPN) │ │ └── docker0: 172.17.0.1/16 (Docker Bridge) │ │ │ │ Access Methods: │ │ 1. Public Internet → Proxmox NAT → eth0 (requires port forwarding) │ │ 2. Tailscale VPN → tailscale0 (no port forwarding needed) │ │ │ │ Services accessible via both: │ │ - HTTP/HTTPS (via Traefik) │ │ - SSH │ │ - Any Docker exposed port │ └──────────────────────────────────────────────────────────────────────────────┘ ``` ### DNS Considerations When using Tailscale with AdGuard: 1. **MagicDNS**: Tailscale provides automatic DNS names (e.g., dockerhost.tail92c8e3.ts.net) 2. **Custom DNS**: You can configure Tailscale to use your AdGuard instance 3. **Split DNS**: Define custom domains that resolve only within your Tailnet ### Security Benefits 1. **No Public Port Exposure**: Admin interfaces remain completely private 2. **Device Authorization**: Only approved devices can join the Tailnet 3. **ACLs**: Fine-grained access control between Tailscale nodes 4. **Audit Logs**: Track all connections and access attempts 5. **Key Rotation**: Automatic key management and rotation ### Complete Network Flow ``` ┌─────────────────────────────────────────────────────────────────────────────────┐ │ Internet (Public) │ │ │ │ │ ↓ │ │ Hetzner Public IP: 157.180.63.15 │ └──────────────────────────────────┬─────────────────────────────────────────────┘ │ ┌──────────────────────────────────┴─────────────────────────────────────────────┐ │ Proxmox Host (Hetzner Server) │ │ │ │ Network Interfaces: │ │ ├── enp7s0: 157.180.63.15/26 (Public) │ │ └── vmbr0: 10.10.10.1/24 (Internal Bridge) │ │ │ │ NAT & Port Forwarding Rules: │ │ ├── 157.180.63.15:80 → 10.10.10.10:80 (HTTP) │ │ ├── 157.180.63.15:443 → 10.10.10.10:443 (HTTPS) │ │ ├── 157.180.63.15:22 → 10.10.10.10:22 (SSH) │ │ └── 157.180.63.15:51820 → 10.10.10.10:51820 (WireGuard/Optional) │ │ │ └──────────────────────────────────┬─────────────────────────────────────────────┘ │ NAT ↓ ┌──────────────────────────────────┴─────────────────────────────────────────────┐ │ Docker VM (10.10.10.10) - Ubuntu │ │ │ │ Network Configuration: │ │ ├── eth0: 10.10.10.10/24 (From Proxmox) │ │ ├── docker0: 172.17.0.1/16 (Docker Default Bridge) │ │ ├── traefik_proxy: 172.18.0.0/16 (External Services) │ │ └── supabase_internal: 172.19.0.0/16 (Database Network) │ │ │ │ Services Running: │ │ ├── Traefik (Reverse Proxy) - Ports 80, 443 │ │ ├── Docker Daemon │ │ └── SSH Server │ └────────────────────────────────────────────────────────────────────────────────┘ ``` ### Network Hierarchy ``` Internet Traffic Flow: ══════════════════════ 1. User → DNS (*.rbnk.uk) → 157.180.63.15 2. Hetzner Server → Proxmox iptables NAT → 10.10.10.10 3. Docker VM → Traefik (ports 80/443) → Docker Networks 4. Docker Networks → Containers (by hostname/labels) Example: https://supabase.rbnk.uk ──────────────────────────────── User Browser ↓ (DNS resolves to 157.180.63.15) Hetzner Public IP:443 ↓ (iptables DNAT) Proxmox NAT → 10.10.10.10:443 ↓ (VM receives traffic) Traefik Container ↓ (reads Host header) ↓ (routes based on rules) Supabase Studio Container ``` ## Port Forwarding Requirements ### Essential Ports (Always Required) | Service | External Port | Internal Port | Protocol | Purpose | |---------|--------------|---------------|----------|---------| | HTTP | 80 | 80 | TCP | HTTP traffic (redirects to HTTPS) | | HTTPS | 443 | 443 | TCP | All web services via Traefik | | SSH | 22 | 22 | TCP | Remote administration | ### Optional Ports (Service-Specific) | Service | External Port | Internal Port | Protocol | Purpose | |---------|--------------|---------------|----------|---------| | WireGuard | 51820 | 51820 | UDP | VPN access (if needed) | | PostgreSQL | 5432 | 5432 | TCP | Direct DB access (NOT recommended) | | Traefik Dashboard | 8080 | 8080 | TCP | Internal only (no forward) | ### Proxmox NAT Configuration Add to `/etc/network/interfaces` on Proxmox host: ```bash # NAT for Docker VM post-up echo 1 > /proc/sys/net/ipv4/ip_forward post-up iptables -t nat -A POSTROUTING -s '10.10.10.0/24' -o enp7s0 -j MASQUERADE # Port forwarding for Docker VM post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 80 -j DNAT --to 10.10.10.10:80 post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 443 -j DNAT --to 10.10.10.10:443 post-up iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 22 -j DNAT --to 10.10.10.10:22 # Cleanup rules on down post-down iptables -t nat -D POSTROUTING -s '10.10.10.0/24' -o enp7s0 -j MASQUERADE post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 80 -j DNAT --to 10.10.10.10:80 post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 443 -j DNAT --to 10.10.10.10:443 post-down iptables -t nat -D PREROUTING -i enp7s0 -p tcp --dport 22 -j DNAT --to 10.10.10.10:22 ``` ## Docker Networks Overview The infrastructure uses Docker's bridge networks to isolate and connect services appropriately. All Docker networks operate within the VM (10.10.10.10). ``` ┌─────────────────────────────────────────────────────────────────┐ │ Docker VM (10.10.10.10) - Network Context │ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Docker Host Network (172.17.0.1) │ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ │ │ traefik_proxy (172.18.0.0/16) - External │ │ │ │ │ │ Connected to: eth0 for outbound NAT │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │ │ │ │ │ │ │ Traefik │ │ LiteLLM │ │ n8n │ │ Open WebUI │ │ │ │ │ │ │ │ .18.2 │ │ .18.3 │ │ .18.4 │ │ .18.5 │ │ │ │ │ │ │ └─────────┘ └──────────┘ └─────────┘ └──────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ │ │ Gitea │ │ Rclone │ │Prometheus│ │ Grafana │ │ │ │ │ │ │ │ .18.6 │ │ .18.7 │ │ .18.8 │ │ .18.9 │ │ │ │ │ │ │ └─────────┘ └─────┬────┘ └──────────┘ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ └──→ Cloudflare R2 (External) │ │ │ │ │ │ ↑ ↑ ↑ ↑ │ │ │ │ │ └───────┼────────────┼───────────┼─────────────┼────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌───────┼────────────┼───────────┼─────────────┼────────┐ │ │ │ │ │ ↓ ↓ ↓ ↓ │ │ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │ │ │ │ │ │ │ Auth │ │ Rest │ │ Storage │ │ Studio │ │ │ │ │ │ │ │ .19.3 │ │ .19.4 │ │ .19.5 │ │ .19.6 │ │ │ │ │ │ │ └────┬────┘ └────┬─────┘ └────┬────┘ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───────────┴────────────┴──────────────┘ │ │ │ │ │ │ ↓ │ │ │ │ │ │ ┌─────────────┐ │ │ │ │ │ │ │ PostgreSQL │ │ │ │ │ │ │ │ .19.2 │ │ │ │ │ │ │ └─────────────┘ │ │ │ │ │ │ │ │ │ │ │ │ supabase_internal (172.19.0.0/16) - Internal │ │ │ │ │ │ Isolated: No direct internet access │ │ │ │ │ └───────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ │ │ cache_redis_network (172.20.0.0/16) - Redis │ │ │ │ │ │ Cache layer for ephemeral data │ │ │ │ │ │ │ │ │ │ │ │ ┌─────────────┐ ┌─────────┐ ┌─────────────┐ │ │ │ │ │ │ │ Cache Redis │───│ Gitea │───│ Stremthru │ │ │ │ │ │ │ │ .20.2 │ │ .20.10 │ │ .20.11 │ │ │ │ │ │ │ └─────────────┘ └─────────┘ └─────────────┘ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ ┌───────────────────────────────────────────────────┐ │ │ │ │ │ session_redis_network (172.21.0.0/16) - Redis │ │ │ │ │ │ Session storage for persistent user data │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌─────────┐ │ │ │ │ │ │ │Session Redis │───│ Gitea │ │ │ │ │ │ │ │ .21.2 │ │ .21.10 │ │ │ │ │ │ │ └──────────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ └───────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ VM Host Network Services: │ │ │ │ └── Ollama (0.0.0.0:11434) ← Via 172.17.0.1 │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │ External Access: All traffic enters via Traefik on ports │ │ 80/443, forwarded from Proxmox NAT (157.180.63.15) │ └─────────────────────────────────────────────────────────────────┘ ``` ## Network Definitions ### traefik_proxy (External Network) **Purpose**: Services that need public internet access **Configuration**: ```yaml networks: traefik_proxy: external: true ``` **IP Range**: 172.18.0.0/16 - Gateway: 172.18.0.1 - Traefik: 172.18.0.2 - Services: 172.18.0.3+ **Connected Services**: - Traefik (reverse proxy) - All public-facing services - Supabase services (dual-homed) - Rclone (web GUI, cloud sync, and R2 infrastructure backups) - Prometheus and Grafana (monitoring stack) ### supabase_internal (Internal Network) **Purpose**: Database isolation and internal communication **Configuration**: ```yaml networks: internal: driver: bridge internal: true # No external routing ``` **IP Range**: 172.19.0.0/16 - Gateway: 172.19.0.1 - PostgreSQL: 172.19.0.2 - Internal services: 172.19.0.3+ **Connected Services**: - PostgreSQL (database) - All Supabase components - No external access ### cache_redis_network (Cache Network) **Purpose**: Shared Redis cache for ephemeral data across services **Configuration**: ```yaml networks: cache_redis_network: name: cache_redis_network driver: bridge ``` **IP Range**: 172.20.0.0/16 - Gateway: 172.20.0.1 - Cache Redis: 172.20.0.2 - Connected services: 172.20.0.3+ **Connected Services**: - cache-redis (Redis 7.4 instance) - Gitea (Git object caching) - Stremthru (API response caching) - Any service needing temporary data storage **Cache Characteristics**: - **Persistence**: Disabled (no disk writes) - **Memory Policy**: `allkeys-lru` (evict least recently used) - **Max Memory**: 4GB (configurable) - **Use Cases**: API responses, computed results, temporary data ### session_redis_network (Session Network) **Purpose**: Shared Redis storage for persistent session data **Configuration**: ```yaml networks: session_redis_network: name: session_redis_network driver: bridge ``` **IP Range**: 172.21.0.0/16 - Gateway: 172.21.0.1 - Session Redis: 172.21.0.2 - Connected services: 172.21.0.3+ **Connected Services**: - session-redis (Redis 7.4 instance) - Gitea (web session storage) - Future web applications requiring session management **Session Characteristics**: - **Persistence**: Enabled (AOF + RDB snapshots) - **Memory Policy**: `volatile-lru` (evict expired keys first) - **Max Memory**: 1GB (configurable) - **Use Cases**: User sessions, authentication tokens, persistent state ## Service Communication Patterns ### 1. External User → Service ``` User Request → Cloudflare → 157.180.63.15:443 → Proxmox NAT → 10.10.10.10:443 → Traefik → Service ``` **Example**: Accessing Open WebUI ``` https://openwebui.rbnk.uk ↓ (Cloudflare DNS) 157.180.63.15:443 ↓ (Proxmox iptables DNAT) 10.10.10.10:443 ↓ (Docker VM receives) Traefik container reads Host header ↓ (Docker network routing) Routes to open-webui container on port 8080 ``` ### 2. Service → Database ``` Service → supabase_internal network → PostgreSQL:5432 ``` **Example**: Supabase Studio accessing database ``` Studio (172.19.0.6) → PostgreSQL (172.19.0.2:5432) ``` ### 3. Container → VM Host Service ``` Container → Docker Gateway (172.17.0.1) → VM Host Service ``` **Example**: Open WebUI accessing Ollama (running on VM, not in container) ``` Open WebUI container → http://172.17.0.1:11434 → Ollama API (VM process) ``` **Note**: This is within the VM (10.10.10.10), not the Proxmox host ### 4. Service → External API ``` Service → traefik_proxy network → Docker NAT → VM eth0 → Proxmox NAT → Internet ``` **Example**: LiteLLM calling OpenAI ``` LiteLLM container → Docker bridge → 10.10.10.10 eth0 → Proxmox vmbr0 → 157.180.63.15 → api.openai.com ``` ### 5. Service → Redis Cache ``` Service → cache_redis_network → Cache Redis:6379 ``` **Example**: Gitea caching Git objects ``` Gitea (172.20.0.10) → cache_redis_network → cache-redis (172.20.0.2:6379) ``` **Configuration**: ```yaml # In service docker-compose.yml environment: - REDIS_CACHE_URL=redis://:${REDIS_PASSWORD}@cache-redis:6379/0 networks: - cache_redis_network ``` ### 6. Service → Redis Sessions ``` Service → session_redis_network → Session Redis:6379 ``` **Example**: Gitea storing user sessions ``` Gitea (172.21.0.10) → session_redis_network → session-redis (172.21.0.2:6379) ``` **Configuration**: ```yaml # In service docker-compose.yml environment: - REDIS_SESSION_URL=redis://:${REDIS_PASSWORD}@session-redis:6379/1 networks: - session_redis_network ``` ### 7. Metrics Collection Flow ``` Services → Node Exporter → Prometheus → Grafana ``` **Example**: Rclone backup metrics ``` Rclone script → writes to /var/lib/node_exporter/textfile_collector/rclone_backup.prom Node Exporter → reads metrics files → exposes on :9100/metrics Prometheus → scrapes :9100/metrics every 15s Grafana → queries Prometheus → displays on dashboards ``` ## Traefik Routing Rules ### Host-Based Routing Each service registers itself with Traefik using Docker labels: ```yaml labels: - "traefik.enable=true" - "traefik.http.routers.SERVICE.rule=Host(`subdomain.rbnk.uk`)" - "traefik.http.services.SERVICE.loadbalancer.server.port=8080" ``` ### Path-Based Routing (Supabase) Supabase uses path prefixes to route to different services: ``` supabase.rbnk.uk/ ├── /auth/v1/* → Auth Service (port 9999) ├── /rest/v1/* → PostgREST API (port 3000) ├── /storage/v1/* → Storage API (port 5000) └── /* → Studio UI (port 3000) ``` **Implementation**: ```yaml # Auth service - "traefik.http.routers.supa-auth.rule=Host(`supabase.rbnk.uk`) && PathPrefix(`/auth/v1`)" - "traefik.http.middlewares.supa-auth-stripprefix.stripprefix.prefixes=/auth/v1" # Studio catch-all (must be last) - "traefik.http.routers.supa-studio.rule=Host(`supabase.rbnk.uk`)" - "traefik.http.routers.supa-studio.priority=1" ``` ## SSL/TLS Configuration ### Cloudflare DNS Challenge ``` Traefik → Cloudflare API → DNS TXT Record → Let's Encrypt → Certificate ``` **Configuration**: ```yaml # In Traefik config certificatesResolvers: cf: acme: dnsChallenge: provider: cloudflare ``` **Why DNS Challenge?** - No need to open port 80 - Supports wildcard certificates - Works behind NAT/firewall ### Certificate Storage ``` /srv/dockerdata/traefik/ └── letsencrypt/ └── acme.json # Contains all certificates ``` ## Port Forwarding by Service Type ### Web Services (via Traefik) Most services only need ports 80/443 forwarded to the VM. Traefik handles internal routing. **Required forwards**: 80, 443 **Services**: Supabase, LiteLLM, n8n, Open WebUI, any web app ### Direct Database Access **WARNING**: Not recommended for production. Use Supabase REST API instead. **Required forwards**: 5432 **Configuration**: ```bash # On Proxmox (NOT recommended) iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 5432 -j DNAT --to 10.10.10.10:5432 ``` ### VPN Services For secure remote access to internal networks. **Required forwards**: 51820 (UDP for WireGuard) **Configuration**: ```bash # On Proxmox iptables -t nat -A PREROUTING -i enp7s0 -p udp --dport 51820 -j DNAT --to 10.10.10.10:51820 ``` ### Custom TCP Services For services that can't use HTTP/HTTPS. **Example**: Custom game server on port 25565 ```bash # On Proxmox iptables -t nat -A PREROUTING -i enp7s0 -p tcp --dport 25565 -j DNAT --to 10.10.10.10:25565 ``` ### Port Forwarding Best Practices 1. **Minimize Exposure**: Only forward essential ports 2. **Use Traefik**: Prefer HTTP/HTTPS routing over direct port exposure 3. **Document Changes**: Keep track of all forwarded ports 4. **Firewall Rules**: Add iptables rules to Proxmox network config for persistence 5. **Security**: Never expose database or admin ports directly ## Network Security ### 1. Network Isolation ```yaml # Internal network - no internet access networks: internal: internal: true ``` ### 2. Port Exposure **Never expose ports directly**: ```yaml # BAD - Exposes to internet ports: - "5432:5432" # GOOD - Only Traefik exposed # No ports: section, use Traefik labels ``` ### 3. Service Discovery Services find each other by name within same network: ```yaml # In app config DATABASE_URL=postgresql://user:pass@supa-db:5432/dbname # ^^^^^^^ # Service name = hostname ``` ## Common Networking Issues ### 1. Container Can't Reach Internet **Symptom**: API calls fail from container **Check**: ```bash # Is container on external network? docker inspect CONTAINER | jq '.[0].NetworkSettings.Networks' ``` **Fix**: Add to traefik_proxy network ### 2. Service Can't Connect to Database **Symptom**: Connection refused to PostgreSQL **Check**: ```bash # Are both on same network? docker network inspect supabase_internal ``` **Fix**: Ensure service is on internal network ### 3. Traefik Can't Route to Service **Symptom**: 502 Bad Gateway **Check**: ```bash # Is service on traefik_proxy network? docker inspect SERVICE | grep traefik_proxy # Check Traefik can see service curl http://localhost:8080/api/http/services ``` **Fix**: Add traefik.docker.network label: ```yaml - "traefik.docker.network=traefik_proxy" ``` ## Network Debugging Commands ### VM Network Status ```bash # Check VM network interfaces ip addr show # Check routing table ip route # Test connectivity to Proxmox gateway ping 10.10.10.1 # Check NAT status sudo iptables -t nat -L -n -v ``` ### Docker Network Configuration ```bash # List all networks docker network ls # Inspect network details docker network inspect traefik_proxy # See connected containers docker network inspect traefik_proxy | jq '.[0].Containers' ``` ### Test Connectivity Chain ```bash # 1. From container to another container docker exec CONTAINER ping OTHER_CONTAINER # 2. From container to VM host docker exec CONTAINER ping 172.17.0.1 # 3. From container to internet (tests full NAT chain) docker exec CONTAINER ping 8.8.8.8 # 4. Check specific port accessibility docker exec CONTAINER nc -zv SERVICE PORT # 5. Test from VM to Proxmox ping 10.10.10.1 # 6. Test from VM to internet curl -I https://example.com ``` ### DNS Resolution Debugging ```bash # Check DNS inside container docker exec CONTAINER nslookup OTHER_SERVICE # Check external DNS from container docker exec CONTAINER nslookup google.com # Check DNS from VM nslookup google.com # Test specific DNS server docker exec CONTAINER nslookup example.com 8.8.8.8 ``` ### Proxmox-Specific Debugging ```bash # On Proxmox host (via SSH to 157.180.63.15) # Check VM network status qm list qm config VMID | grep net # Check bridge status brctl show # Monitor traffic to VM tcpdump -i vmbr0 host 10.10.10.10 ``` ## Best Practices ### 1. Network Naming - Use descriptive names - Suffix with purpose: `_proxy`, `_internal` - Keep consistent across services ### 2. IP Assignment - Let Docker assign IPs automatically - Use service names, not IPs - Document IP ranges for troubleshooting ### 3. Security - Minimize services on external network - Use internal networks for databases - Never expose management ports ### 4. Documentation - Document custom networks in compose files - Comment non-obvious routing rules - Keep network diagram updated ## Future Improvements 1. **Network Policies**: Implement iptables rules for finer control 2. **Service Mesh**: Consider Consul/Linkerd for complex routing 3. **Monitoring**: Add network metrics to Prometheus 4. **IPv6**: Enable dual-stack networking --- ## architecture > overview ### 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 ```yaml # 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**: ```yaml # 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**: ```bash # 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: ```yaml # 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. --- ## architecture > service-integrations ### Service Integration Patterns This document describes how new services integrate with the existing infrastructure components, showcasing patterns for authentication, monitoring, notifications, and data flow. ## Table of Contents 1. \1 2. \1 3. \1 4. \1 5. \1 6. \1 7. \1 ## Integration Overview The infrastructure follows a layered integration approach where services connect through: - **Docker Networks**: Container communication via bridge networks - **Traefik Proxy**: Centralized routing and SSL termination - **Notification Stack**: Unified alerting via Apprise/ntfy - **Monitoring Stack**: Metrics collection through Prometheus - **Backup System**: Automated data protection via Rclone ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Integration Layer Architecture │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Services │ ←→ │ Traefik │ ←→ │ Cloudflare │ │ │ │ │ │ (Proxy) │ │ (CDN) │ │ │ └──────┬───────┘ └──────────────┘ └──────────────┘ │ │ │ │ │ ↓ │ │ ┌──────┴───────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Notification │ │ Monitoring │ │ Backup │ │ │ │ Stack │ │ Stack │ │ System │ │ │ │ │ │ │ │ │ │ │ │ • Apprise │ │ • Prometheus │ │ • Rclone │ │ │ │ • ntfy │ │ • Grafana │ │ • R2 Storage │ │ │ │ • Mailrise │ │ • Loki │ │ • Cloud Sync │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## Core Integration Patterns ### 1. Docker Network Connectivity All services use a dual-network approach for secure isolation: ```yaml networks: default: name: traefik_proxy external: true # Optional internal network for database access internal: name: service_internal driver: bridge internal: true ``` **Pattern Benefits**: - External services accessible via Traefik - Database access isolated to internal networks - Clear separation of concerns ### 2. Traefik Integration Pattern Standard Traefik labels for HTTP services: ```yaml labels: - "traefik.enable=true" - "traefik.docker.network=traefik_proxy" # HTTP redirect - "traefik.http.routers.service-http.rule=Host(`service.rbnk.uk`)" - "traefik.http.routers.service-http.entrypoints=web" - "traefik.http.routers.service-http.middlewares=redirect-to-https@file" # HTTPS - "traefik.http.routers.service.rule=Host(`service.rbnk.uk`)" - "traefik.http.routers.service.entrypoints=websecure" - "traefik.http.routers.service.tls.certresolver=cf" - "traefik.http.services.service.loadbalancer.server.port=8080" ``` ### 3. Monitoring Integration Pattern Services expose metrics for Prometheus collection: ```yaml labels: # Enable Prometheus scraping - "prometheus.io/scrape=true" - "prometheus.io/port=9090" - "prometheus.io/path=/metrics" # Custom job label for organization - "prometheus_job=service-metrics" ``` ### 4. Redis Integration Pattern Services integrate with consolidated Redis instances for caching and session management: #### A. Cache Redis Integration (Ephemeral Data) ```yaml # Service connecting to cache Redis environment: - REDIS_CACHE_URL=redis://:${REDIS_PASSWORD}@cache-redis:6379/0 - CACHE_BACKEND=redis networks: - cache_redis_network # Connect to cache Redis network ``` **Cache Redis Configuration**: - **Purpose**: Temporary data, API responses, computed results - **Persistence**: Disabled (no disk writes) - **Memory Policy**: `allkeys-lru` (evict least recently used) - **Max Memory**: 4GB (configurable) - **Port**: 6380 (host) → 6379 (container) #### B. Session Redis Integration (Persistent Data) ```yaml # Service connecting to session Redis environment: - REDIS_SESSION_URL=redis://:${REDIS_PASSWORD}@session-redis:6379/0 - SESSION_STORE=redis networks: - session_redis_network # Connect to session Redis network ``` **Session Redis Configuration**: - **Purpose**: User sessions, authentication tokens, persistent state - **Persistence**: Enabled (AOF + RDB snapshots) - **Memory Policy**: `volatile-lru` (evict expired keys first) - **Max Memory**: 1GB (configurable) - **Port**: 6381 (host) → 6379 (container) #### C. Multi-Redis Service Pattern ```yaml # Service using both Redis instances environment: - REDIS_CACHE_URL=redis://:${REDIS_PASSWORD}@cache-redis:6379/0 - REDIS_SESSION_URL=redis://:${REDIS_PASSWORD}@session-redis:6379/0 networks: - cache_redis_network - session_redis_network ``` **Use Cases**: - **Gitea**: Cache for Git operations, sessions for web UI - **Web Applications**: API response caching + user session storage - **Background Jobs**: Task queues (cache) + job status (session) ### 5. Notification Integration Pattern Services integrate with notification stack via multiple methods: #### A. Direct API Integration ```bash # Send notification via Apprise curl -X POST https://apprise.rbnk.uk/notify/apprise \ -H "Content-Type: application/json" \ -d '{"title": "Service Alert", "body": "Status update"}' ``` #### B. ntfy Topic Publishing ```bash # Publish to ntfy topic with authentication curl -X POST https://ntfy.rbnk.uk/service-warning-disk \ -H "Authorization: Bearer $NTFY_TOKEN" \ -d "Disk usage at 85%" ``` #### C. SMTP Gateway via Mailrise ```bash # Send email that converts to push notification echo "Service notification" | mail -s "Alert" service@localhost:8025 ``` ## Service-Specific Integrations ### Gitea + Redis Integration **Purpose**: Performance optimization through caching and session management ```yaml # Gitea with dual Redis connections services: gitea: environment: # Cache Redis for Git operations - GITEA__cache__ENABLED=true - GITEA__cache__ADAPTER=redis - GITEA__cache__HOST=redis://:${REDIS_PASSWORD}@cache-redis:6379/0 # Session Redis for web sessions - GITEA__session__PROVIDER=redis - GITEA__session__PROVIDER_CONFIG=redis://:${REDIS_PASSWORD}@session-redis:6379/1 networks: - traefik_proxy - cache_redis_network # For cache operations - session_redis_network # For session storage ``` **Performance Benefits**: ```bash # Cache Redis handles: # - Git object caching # - Repository metadata # - API response caching # Session Redis handles: # - User login sessions # - CSRF tokens # - Web UI state ``` **Monitoring Integration**: ```bash # Redis metrics available via Prometheus redis_connected_clients{instance="cache-redis"} redis_memory_used_bytes{instance="session-redis"} redis_keyspace_hits_total{instance="cache-redis"} ``` ### Metube + Rclone Integration **Purpose**: Automated backup of downloaded media files ```yaml # Metube volumes shared with Rclone volumes: - ./downloads:/downloads - ./config:/config # Rclone access to Metube downloads volumes: - /srv/dockerdata/metube/downloads:/backup/metube:ro ``` **Backup Automation**: ```bash #!/bin/bash # Automated sync of Metube downloads to cloud storage docker exec rclone rclone sync /backup/metube gdrive:media/metube \ --include "*.mp4" \ --include "*.mkv" \ --include "*.avi" \ --transfers 2 \ --bwlimit 10M ``` ### Watchtower + Notification Integration **Configuration**: Watchtower sends update notifications via notification stack ```yaml environment: # Email notifications via Mailrise SMTP gateway - WATCHTOWER_NOTIFICATIONS=email - WATCHTOWER_NOTIFICATION_EMAIL_SERVER=mailrise:8025 - WATCHTOWER_NOTIFICATION_EMAIL_FROM=watchtower@rbnk.uk - WATCHTOWER_NOTIFICATION_EMAIL_TO=admin@localhost ``` **Enhanced Notification Flow**: ``` Watchtower Update → SMTP (Mailrise:8025) → Apprise API → ntfy Topic → Push Notification ``` ### Paperless-AI + n8n Workflow Integration **Document Processing Automation**: 1. **Webhook Trigger**: Paperless-AI sends webhook on document upload 2. **n8n Processing**: Workflow extracts metadata and processes content 3. **Notification**: Results sent via notification stack ```yaml # Paperless-AI webhook configuration environment: - WEBHOOK_URL=https://n8n.rbnk.uk/webhook/paperless-process - POST_PROCESSING_ENABLED=true ``` **n8n Workflow Pattern**: ```json { "nodes": [ { "name": "Webhook", "type": "n8n-nodes-base.webhook", "parameters": { "path": "paperless-process" } }, { "name": "Process Document", "type": "n8n-nodes-base.function", "parameters": { "functionCode": "// Extract document metadata\nreturn items.map(item => ({\n json: {\n documentId: item.json.id,\n title: item.json.title,\n processed: true\n }\n}));" } }, { "name": "Send Notification", "type": "n8n-nodes-base.httpRequest", "parameters": { "url": "https://apprise.rbnk.uk/notify/apprise", "method": "POST", "jsonParameters": true, "options": { "bodyParametersUi": { "parameter": [ { "name": "title", "value": "Document Processed" }, { "name": "body", "value": "Document {{$node.Process Document.json.title}} has been processed" } ] } } } } ] } ``` ## Network Architecture ### Docker Network Topology ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ Docker Host (10.10.10.10) │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ traefik_proxy (172.18.0.0/16) │ │ │ │ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────────┐ │ │ │ │ │ Traefik │ │ Metube │ │ Rclone │ │ Paperless-AI │ │ │ │ │ │ .18.2 │ │ .18.10 │ │ .18.11 │ │ .18.12 │ │ │ │ │ └─────────┘ └──────────┘ └─────────┘ └──────────────────┘ │ │ │ │ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────────┐ │ │ │ │ │Watchtower│ │ n8n │ │Apprise │ │ Monitoring │ │ │ │ │ │ .18.13 │ │ .18.4 │ │ .18.14 │ │ Stack │ │ │ │ │ └──────────┘ └──────────┘ └─────────┘ └──────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ supabase_internal (172.19.0.0/16) │ │ │ │ (No Internet Access) │ │ │ │ │ │ │ │ ┌─────────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ PostgreSQL │ │ Auth │ │ REST │ │ │ │ │ │ .19.2 │ │ .19.3 │ │ .19.4 │ │ │ │ │ └─────────────┘ └──────────┘ └──────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ Host Services: │ │ ├── Docker Daemon (unix:///var/run/docker.sock) │ │ ├── Ollama (127.0.0.1:11434) │ │ └── Node Exporter (127.0.0.1:9100) │ └─────────────────────────────────────────────────────────────────────────┘ ``` ### Service Discovery Patterns #### 1. Container-to-Container Communication ```yaml # Service A connecting to Service B environment: - SERVICE_B_URL=http://service-b:8080 # Uses Docker's built-in DNS resolution ``` #### 2. Container-to-Host Service ```yaml # Container connecting to host service (Ollama) environment: - OLLAMA_URL=http://172.17.0.1:11434 # 172.17.0.1 is Docker's default gateway to host ``` #### 3. Container-to-Database ```yaml # Connection via internal network environment: - DATABASE_URL=postgresql://user:pass@supa-db:5432/dbname networks: - supabase_internal # Required for database access ``` ## API Integration Examples ### 1. Rclone Remote Control API **Purpose**: Enable other services to trigger backup operations ```bash # Start backup via Rclone RC API curl -X POST http://rclone:5572/sync/sync \ -H "Content-Type: application/json" \ -d '{ "srcFs": "/data/metube", "dstFs": "gdrive:backups/metube", "_async": true }' ``` **Integration in n8n**: ```json { "name": "Trigger Backup", "type": "n8n-nodes-base.httpRequest", "parameters": { "url": "http://rclone:5572/sync/sync", "method": "POST", "jsonParameters": true, "options": { "bodyParametersUi": { "parameter": [ { "name": "srcFs", "value": "/data/{{$node.Previous.json.folder}}" }, { "name": "dstFs", "value": "cloudflare:backups/{{$node.Previous.json.folder}}" }, { "name": "_async", "value": true } ] } } } } ``` ### 2. Supabase API Integration **Authentication Flow**: ```javascript // Service authentication with Supabase const response = await fetch('https://supabase.rbnk.uk/auth/v1/token', { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': process.env.SUPABASE_ANON_KEY }, body: JSON.stringify({ email: 'service@example.com', password: process.env.SERVICE_PASSWORD }) }); ``` **Data Operations**: ```javascript // Insert service data const data = await fetch('https://supabase.rbnk.uk/rest/v1/service_logs', { method: 'POST', headers: { 'Content-Type': 'application/json', 'apikey': process.env.SUPABASE_ANON_KEY, 'Authorization': `Bearer ${accessToken}`, 'Prefer': 'return=representation' }, body: JSON.stringify({ service: 'metube', action: 'download_complete', metadata: { filename: 'video.mp4', size: 1024000 } }) }); ``` ### 3. Notification API Integration **Unified Notification Function**: ```bash #!/bin/bash # notification.sh - Unified notification script notify() { local title="$1" local message="$2" local priority="${3:-normal}" local service="${4:-general}" # Send to Apprise curl -s -X POST https://apprise.rbnk.uk/notify/apprise \ -H "Content-Type: application/json" \ -d "{\"title\": \"$title\", \"body\": \"$message\"}" # Send to ntfy topic local topic="${service}-${priority}-alert" curl -s -X POST "https://ntfy.rbnk.uk/$topic" \ -H "Authorization: Bearer $NTFY_TOKEN" \ -d "$message" } # Usage examples notify "Backup Complete" "Metube backup finished successfully" "info" "backup" notify "Disk Warning" "Disk usage at 85%" "warning" "monitoring" notify "Service Error" "Paperless-AI processing failed" "error" "paperless" ``` ## Monitoring & Observability ### Metrics Collection Flow ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ ┌──────────────┐ │ Service │───▶│ Metrics │───▶│ Prometheus │───▶│ Grafana │ │ (Container) │ │ Endpoint │ │ (Scraper) │ │ (Dashboard) │ └─────────────┘ └──────────────┘ └─────────────┘ └──────────────┘ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Log Output │ │ AlertManager│ │ (Docker) │ │ (Alerts) │ └─────────────┘ └─────────────┘ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Loki │ │Notification │ │ (Log Store) │ │ Stack │ └─────────────┘ └─────────────┘ ``` ### Service Metrics Examples #### Metube Metrics ```yaml # Custom metrics via textfile collector - name: metube_downloads_total type: counter help: Total number of downloads - name: metube_download_size_bytes type: histogram help: Size of downloaded files - name: metube_queue_length type: gauge help: Current download queue length ``` #### Rclone Backup Metrics ```yaml # Generated by backup scripts - name: rclone_backup_duration_seconds type: histogram help: Time taken for backup operations - name: rclone_backup_bytes_transferred type: counter help: Total bytes transferred - name: rclone_backup_success type: gauge help: Last backup success status (1=success, 0=failure) ``` ### Log Aggregation Patterns **Structured Logging Configuration**: ```yaml # Docker logging driver logging: driver: "json-file" options: max-size: "10m" max-file: "3" labels: "service,environment" ``` **Loki Labels**: ```yaml # Promtail config for service discovery - job_name: containers docker_sd_configs: - host: unix:///var/run/docker.sock relabel_configs: - source_labels: ['__meta_docker_container_name'] target_label: 'container' - source_labels: ['__meta_docker_container_label_com_docker_compose_service'] target_label: 'service' ``` ## Configuration Examples ### Complete Service Integration Template ```yaml # docker-compose.yml for new service services: my-service: image: myservice:latest container_name: my-service restart: unless-stopped # Environment configuration environment: - SERVICE_PORT=8080 - DATABASE_URL=postgresql://user:pass@supa-db:5432/mydb - NOTIFICATION_URL=https://apprise.rbnk.uk/notify/apprise - BACKUP_ENABLED=true # Redis integration (optional) - REDIS_CACHE_URL=redis://:${REDIS_PASSWORD}@cache-redis:6379/0 - REDIS_SESSION_URL=redis://:${REDIS_PASSWORD}@session-redis:6379/0 # Network connectivity networks: - traefik_proxy # External access - supabase_internal # Database access - cache_redis_network # Cache Redis (optional) - session_redis_network # Session Redis (optional) # Volume mounts volumes: - ./data:/app/data - ./config:/app/config # Traefik integration labels: - "traefik.enable=true" - "traefik.docker.network=traefik_proxy" - "traefik.http.routers.myservice-http.rule=Host(`myservice.rbnk.uk`)" - "traefik.http.routers.myservice-http.entrypoints=web" - "traefik.http.routers.myservice-http.middlewares=redirect-to-https@file" - "traefik.http.routers.myservice.rule=Host(`myservice.rbnk.uk`)" - "traefik.http.routers.myservice.entrypoints=websecure" - "traefik.http.routers.myservice.tls.certresolver=cf" - "traefik.http.services.myservice.loadbalancer.server.port=8080" # Monitoring integration - "prometheus.io/scrape=true" - "prometheus.io/port=9090" - "prometheus.io/path=/metrics" - "prometheus_job=myservice-metrics" # Backup exclusion (if needed) - "backup.exclude=true" networks: traefik_proxy: external: true supabase_internal: external: true cache_redis_network: external: true session_redis_network: external: true ``` ### Backup Integration Script ```bash #!/bin/bash # backup-myservice.sh set -euo pipefail SERVICE_NAME="my-service" BACKUP_DATE=$(date +%Y%m%d-%H%M%S) REMOTE="cloudflare" log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" } # Backup service data log "Backing up $SERVICE_NAME data..." docker exec rclone rclone sync "/backup/dockerdata/$SERVICE_NAME/data" \ "$REMOTE:service-backups/$SERVICE_NAME/$BACKUP_DATE/" \ --exclude "*/tmp/**" \ --exclude "*/cache/**" \ --transfers 4 \ --checkers 8 # Send success notification curl -s -X POST https://apprise.rbnk.uk/notify/apprise \ -H "Content-Type: application/json" \ -d "{\"title\": \"$SERVICE_NAME Backup Complete\", \"body\": \"Backup completed successfully at $BACKUP_DATE\"}" log "$SERVICE_NAME backup completed" ``` ### Health Check Integration ```yaml # Health check configuration healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s # Custom health check script # healthcheck.sh #!/bin/bash # Check service health and send alerts if ! curl -sf http://localhost:8080/health > /dev/null; then curl -X POST https://ntfy.rbnk.uk/myservice-error-health \ -H "Authorization: Bearer $NTFY_TOKEN" \ -d "Service health check failed" exit 1 fi ``` ### Alerting Rules Integration ```yaml # prometheus-alerts.yml groups: - name: myservice rules: - alert: MyServiceDown expr: up{job="myservice-metrics"} == 0 for: 2m labels: severity: critical service: myservice annotations: summary: "MyService is down" description: "MyService has been down for more than 2 minutes" - alert: MyServiceHighErrorRate expr: rate(myservice_errors_total[5m]) > 0.1 for: 5m labels: severity: warning service: myservice annotations: summary: "High error rate in MyService" description: "Error rate is {{ $value }} errors per second" ``` This comprehensive integration guide ensures new services follow established patterns while maintaining security, observability, and reliability standards across the infrastructure. --- ## architecture > storage-strategy ### Storage Strategy - Dual Disk Architecture ## Current Storage Layout ``` ┌─────────────────────────────────────────────────────────────┐ │ Proxmox VM: dockerhost │ │ │ │ ┌─────────────────────┐ ┌─────────────────────────┐ │ │ │ /dev/sda (100GB) │ │ /dev/sdb (500GB) │ │ │ │ System Disk │ │ Data Disk │ │ │ ├─────────────────────┤ ├─────────────────────────┤ │ │ │ sda1: /boot/efi 1GB │ │ sdb1: /srv/dockerdata │ │ │ │ sda2: /boot 2GB │ │ 492GB formatted │ │ │ │ sda3: LVM 97GB │ │ 288GB available │ │ │ │ └── / (96GB) │ │ │ │ │ │ 29% used │ │ 39% used │ │ │ └─────────────────────┘ └─────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Why This Dual-Disk Design? ### Original Setup Intent Based on the Proxmox configuration: ``` Disks: • scsi0 (OS): 60GB on vmdata • scsi1 (Data): 200GB on vmdata (thin provisioned) ``` **Design Rationale**: 1. **Separation of Concerns**: OS vs Application Data 2. **Independent Growth**: Data disk can expand without OS reinstall 3. **Performance**: Separate I/O paths for system and data 4. **Backup Strategy**: Different backup policies for OS vs data ### Current Reality **System Disk (100GB)**: - **96GB root partition**: 29% used (~66GB free) - **Status**: Healthy with ample headroom - **Contains**: OS, system packages, logs, Docker engine **Data Disk (500GB)**: - **492GB usable**: ~39% used (288GB available) - **Mounted at**: `/srv/dockerdata` - **Contains**: All container data, configs, backups ## Storage Organization ### Directory Structure ``` /srv/dockerdata/ ├── SERVICE_NAME/ # Each service isolated │ ├── docker-compose.yml # Service definition │ ├── .env # Configuration (640 perms) │ ├── data/ # Persistent data │ │ ├── database/ # Database files │ │ ├── uploads/ # User uploads │ │ └── cache/ # Temporary data │ └── config/ # Configuration files ├── _scripts/ # Operational scripts ├── _templates/ # Service templates ├── _backup/ # Backup storage └── ollama/ # Special case - models └── models/ # Large LLM files ``` ### Storage Patterns by Service Type **Database Services** (PostgreSQL): ``` supabase/data/ ├── postgres/ # ~500MB-5GB typical ├── wal_archive/ # Transaction logs └── backups/ # Local dumps ``` **Application Services**: ``` open-webui/data/ ├── backend/ # SQLite DB, session data └── uploads/ # User uploaded files ``` **Large File Services**: ``` ollama/models/ ├── llama2:7b/ # 3.8GB ├── mistral:latest/ # 4.1GB └── llama2:70b/ # 40GB (careful!) ``` ## Storage Management ### Regular Maintenance Keep storage healthy with periodic cleanup: ```bash # Check what's using space sudo du -h --max-depth=1 / | sort -h du -sh /srv/dockerdata/* | sort -h # Clean Docker artifacts (unused images, containers, volumes) docker system prune -a --volumes # Clean package cache sudo apt clean sudo apt autoremove # Rotate/compress logs sudo journalctl --vacuum-time=14d ``` ### Optional: Move Docker Root to Data Disk If system disk becomes constrained again, move Docker data: ```bash # Stop Docker sudo systemctl stop docker # Move Docker data sudo mv /var/lib/docker /srv/dockerdata/docker-root # Configure Docker to use new location sudo tee /etc/docker/daemon.json < 85%: Critical, act immediately - Data disk > 70%: Plan expansion - Any partition > 90%: Emergency ### How to Expand Data Disk ```bash # On Proxmox host qm resize 100 scsi1 +100G # In VM sudo growpart /dev/sdb 1 sudo resize2fs /dev/sdb1 ``` ### Migration Path When disks can't grow: 1. **Add third disk**: Dedicated for specific service 2. **External storage**: NFS/S3 for large files 3. **New VM**: Distribute services across VMs ## Best Practices ### 1. **Separation Rules** - OS disk: System only - Data disk: All application data - External: Backups and archives ### 2. **Monitoring** - Daily disk usage checks - Weekly growth analysis - Monthly capacity planning ### 3. **Cleanup Routine** ```bash # Weekly cleanup script #!/bin/bash docker system prune -f sudo journalctl --vacuum-time=14d find /srv/dockerdata -name "*.log" -mtime +30 -delete ``` ### 4. **Documentation** - Track which services use most space - Document growth patterns - Plan before hitting limits ## Recommendations ### Ongoing Maintenance 1. Monitor disk usage weekly via Grafana dashboards 2. Run cleanup scripts monthly to remove unused Docker artifacts 3. Review storage growth trends quarterly ### Best Practices 1. Implement automated cleanup scripts 2. Configure log rotation for all services 3. Maintain external backup destination (Cloudflare R2) ### Future Considerations 1. Consider moving Docker root to data disk if system disk usage increases 2. Continue monitoring storage metrics in Grafana 3. Maintain disaster recovery plan with regular backup testing ## Storage Decision Matrix | What to Store | System Disk | Data Disk | External | |---------------|-------------|-----------|----------| | OS Files | ✓ | | | | Docker Engine | ✓ | | | | Container Images | | ✓ | | | Application Data | | ✓ | | | Database Files | | ✓ | | | Backups | | ✓ | ✓ (R2) | | Large Models (LLM) | | ✓ | | | Logs (archived) | | | ✓ | The dual-disk strategy provides a solid foundation with ample capacity for growth. --- ## coolify-traefik-bridge ### Coolify-Traefik Bridge Automated solution for routing Coolify services through the main Traefik proxy with automatic DNS record creation. ## Overview The Coolify-Traefik Bridge script automatically: 1. Detects all Coolify-managed containers 2. Connects them to the main Traefik network 3. Generates Traefik routing configurations 4. Creates DNS records in Cloudflare ## How It Works 1. **Service Detection**: Finds containers with `coolify.managed=true` label 2. **Network Connection**: Ensures containers are on `traefik_proxy` network 3. **Config Generation**: Creates Traefik dynamic configs in `/srv/dockerdata/traefik/dynamic/` 4. **DNS Creation**: Automatically creates A records pointing to server IP ## Usage ### Manual Run (for testing) ```bash /srv/dockerdata/_scripts/coolify-traefik-bridge.sh true ``` ### Enable as Service ```bash sudo systemctl daemon-reload sudo systemctl enable coolify-traefik-bridge sudo systemctl start coolify-traefik-bridge ``` ### Check Service Status ```bash sudo systemctl status coolify-traefik-bridge journalctl -u coolify-traefik-bridge -f ``` ## Configuration - **Watch Interval**: 30 seconds (configurable in script) - **Config Directory**: `/srv/dockerdata/traefik/dynamic/` - **DNS Script**: `/srv/dockerdata/_scripts/cloudflare-dns-create.sh` ## Generated Files For each Coolify service, creates: - `/srv/dockerdata/traefik/dynamic/coolify-.yml` ## Troubleshooting ### Service Not Accessible 1. Check DNS propagation: `dig +short .rbnk.uk` 2. Verify Traefik config: `docker exec traefik cat /etc/traefik/dynamic/coolify-.yml` 3. Check container is on network: `docker inspect | jq '.[0].NetworkSettings.Networks'` ### Long Domain Names Some Coolify services generate long UUIDs in domains. The script handles these automatically but they may look unusual (e.g., `audiobookshelf-xosc0wsscooo40cgwco44ggw.rbnk.uk`). ## Benefits - **Zero Manual Configuration**: Just deploy in Coolify, bridge handles the rest - **Automatic DNS**: No need to manually create DNS records - **SSL Certificates**: Automatically handled by main Traefik - **Unified Routing**: All services go through main Traefik proxy ## Security Notes - Services are automatically connected to `traefik_proxy` network - SSL termination handled by main Traefik with Cloudflare certificates - No ports exposed directly from containers --- ## development > adding-services ### Adding Services Guide This guide covers the process of adding new services to the infrastructure, from initial setup to production deployment. ## Table of Contents - \1 - \1 - \1 - \1 - \1 - \1 - \1 - \1 - \1 ## When to Use stackwiz vs Manual Setup ### Use stackwiz when: - Service follows standard patterns (web app with database) - You need Traefik integration out of the box - Creating Pocketbase-based applications - You want automatic DNS record creation - You want consistent directory structure ```bash # Example: Adding a new web application stackwiz mywebapp # Or with Pocketbase backend stackwiz --pocketbase mywebapp # Legacy simple template (basic cases only) ./_templates/mkstack.sh mywebapp ``` ### Manual setup when: - Complex multi-service stacks (like Supabase) - Special networking requirements - Custom initialization scripts needed - Non-standard port configurations ## Docker Compose Best Practices ### 1. Service Naming Conventions Use clear, descriptive names that indicate the service purpose: ```yaml services: myapp-web: # Main application myapp-db: # Database myapp-redis: # Cache layer myapp-worker: # Background jobs ``` ### 2. Version and Structure Always use Compose file version 3.8+ for full feature support: ```yaml version: '3.8' services: # Services defined here networks: # Network configuration volumes: # Named volumes ``` ### 3. Health Checks Implement health checks for critical services: ```yaml services: myapp-web: image: myapp:latest healthcheck: test: ["CMD", "curl", "-f", "http://localhost:3000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s ``` ### 4. Resource Limits Set appropriate resource constraints: ```yaml services: myapp-web: deploy: resources: limits: cpus: '2.0' memory: 2G reservations: cpus: '0.5' memory: 512M ``` ## Traefik Integration Patterns ### Basic Web Service Standard HTTP service with automatic SSL: ```yaml services: myapp: image: myapp:latest networks: - traefik_proxy labels: - "traefik.enable=true" - "traefik.docker.network=traefik_proxy" - "traefik.http.routers.myapp.rule=Host(`myapp.rbnk.uk`)" - "traefik.http.routers.myapp.entrypoints=websecure" - "traefik.http.routers.myapp.tls=true" - "traefik.http.routers.myapp.tls.certresolver=cloudflare" - "traefik.http.services.myapp.loadbalancer.server.port=3000" ``` ### Service with Multiple Routes Example from Supabase with path-based routing: ```yaml labels: # Main studio interface - "traefik.http.routers.supabase-studio.rule=Host(`supabase.rbnk.uk`)" # API endpoints - "traefik.http.routers.supabase-rest.rule=Host(`supabase.rbnk.uk`) && PathPrefix(`/rest/v1`)" - "traefik.http.routers.supabase-auth.rule=Host(`supabase.rbnk.uk`) && PathPrefix(`/auth/v1`)" # Different backend services - "traefik.http.services.supabase-rest.loadbalancer.server.port=3000" - "traefik.http.services.supabase-auth.loadbalancer.server.port=9999" ``` ### WebSocket Support For services requiring WebSocket connections: ```yaml labels: - "traefik.http.routers.myapp-ws.rule=Host(`myapp.rbnk.uk`) && PathPrefix(`/ws`)" - "traefik.http.routers.myapp-ws.service=myapp-ws" - "traefik.http.services.myapp-ws.loadbalancer.server.port=3000" # Enable sticky sessions for WebSockets - "traefik.http.services.myapp-ws.loadbalancer.sticky=true" - "traefik.http.services.myapp-ws.loadbalancer.sticky.cookie.name=myapp_ws" ``` ## Network Configuration Decisions ### 1. External vs Internal Networks **External (traefik_proxy)**: For services that need public access ```yaml networks: traefik_proxy: external: true ``` **Internal**: For service-to-service communication ```yaml networks: internal: driver: bridge internal: true ``` ### 2. Multi-Network Services Services can connect to multiple networks: ```yaml services: myapp-api: networks: - traefik_proxy # Public access - supabase_default # Database access - internal # Internal services ``` ### 3. Network Aliases Use aliases for service discovery: ```yaml services: myapp-db: networks: internal: aliases: - database - postgres ``` ## Volume and Data Management ### 1. Named Volumes vs Bind Mounts **Named Volumes** (Preferred for data): ```yaml volumes: myapp-data: driver: local services: myapp: volumes: - myapp-data:/var/lib/myapp ``` **Bind Mounts** (For config files): ```yaml services: myapp: volumes: - ./config/myapp.conf:/etc/myapp/myapp.conf:ro - ./data:/data ``` ### 2. Backup Considerations Structure volumes for easy backup: ```yaml services: myapp-db: volumes: # All data in one parent directory - ./data/postgres:/var/lib/postgresql/data - ./data/backups:/backups ``` ### 3. Init Scripts Pattern From Supabase bootstrap example: ```yaml volumes: - ./bootstrap-sql:/docker-entrypoint-initdb.d:ro ``` Files execute alphabetically: ``` bootstrap-sql/ ├── 00-initial-schema.sql ├── 01-core-tables.sql ├── 02-auth-setup.sql └── 03-app-tables.sql ``` ## Environment Variable Management ### 1. Environment File Structure ```bash # Stack-specific .env file touch myapp/.env chmod 640 myapp/.env ``` Example .env: ```env # Database POSTGRES_USER=myapp POSTGRES_PASSWORD=secure_password_here POSTGRES_DB=myapp # Application APP_SECRET_KEY=generate_with_openssl_rand API_KEY=another_secure_key # External Services REDIS_URL=redis://myapp-redis:6379 DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@myapp-db:5432/${POSTGRES_DB} ``` ### 2. Sharing Secrets Between Services Use common environment variables: ```yaml services: myapp-web: env_file: .env environment: - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@myapp-db:5432/${POSTGRES_DB} myapp-worker: env_file: .env environment: - DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@myapp-db:5432/${POSTGRES_DB} ``` ### 3. Secret Generation ```bash # Generate secure passwords openssl rand -base64 32 # Generate JWT secrets openssl rand -hex 64 # Generate API keys uuidgen | tr -d '-' # For LiteLLM services, use virtual keys instead of master key # See: LiteLLM Virtual Keys documentation ``` ### 4. LiteLLM Integration If your service needs LLM access, use virtual keys instead of the master key: ```bash # Create virtual key via API (see LiteLLM Virtual Keys guide) curl -X POST "https://llm.rbnk.uk/key/generate" \ -H "Authorization: Bearer $LITELLM_MASTER_KEY" \ -H "Content-Type: application/json" \ -d '{ "budget_id": "service-budget-id", "metadata": {"service": "myapp"} }' ``` For complete virtual key implementation details, see: \1 ## Testing New Services ### 1. Local Testing First ```bash # Test without Traefik docker compose -f myapp/docker-compose.yml up # Check service is accessible curl http://localhost:3000/health # Check logs docker compose -f myapp/docker-compose.yml logs -f ``` ### 2. Network Testing ```bash # Verify network connectivity docker compose -f myapp/docker-compose.yml exec myapp-web ping myapp-db # Test database connection docker compose -f myapp/docker-compose.yml exec myapp-web psql $DATABASE_URL -c "SELECT 1" ``` ### 3. Traefik Integration Testing ```bash # Add to docker-compose.yml and restart docker compose -f myapp/docker-compose.yml up -d # Check Traefik picked up the service curl http://localhost:8080/api/http/routers | jq '.[] | select(.rule | contains("myapp"))' # Test SSL endpoint curl -v https://myapp.rbnk.uk/health ``` ### 4. Load Testing ```bash # Simple load test ab -n 1000 -c 10 https://myapp.rbnk.uk/ # Monitor resources during test docker stats myapp-myapp-web-1 ``` ## Documentation Requirements ### 1. Service README Create `myapp/README.md`: ```markdown # MyApp Service ## Overview Brief description of what this service does ## Architecture - Main application: Node.js/Python/etc - Database: PostgreSQL - Cache: Redis (if applicable) ## Configuration Key environment variables: - `DATABASE_URL`: PostgreSQL connection string - `REDIS_URL`: Redis connection string - `API_KEY`: External API authentication ## Endpoints - `https://myapp.rbnk.uk` - Main web interface - `https://myapp.rbnk.uk/api` - REST API ## Maintenance ### Backup Automated via `_scripts/backup-myapp.sh` ### Updates ```bash docker compose -f myapp/docker-compose.yml pull docker compose -f myapp/docker-compose.yml up -d ``` ## Troubleshooting Common issues and solutions ``` ### 2. Update Main Documentation Add to `/srv/dockerdata/README.md`: - Service entry in services table - URL in endpoints section - Any special considerations ## Git Workflow for Infrastructure Changes ### 1. Initial Repository Setup ```bash # Configure dual remotes (one-time setup) cd /srv/dockerdata /srv/dockerdata/_scripts/setup-git-remotes.sh # This creates: # - origin → Gitea (primary) # - gitea → Gitea (explicit) # - github → GitHub (optional mirror) ``` ### 2. Feature Branch Workflow ```bash # Create feature branch git checkout -b add-myapp-service # Make changes vim myapp/docker-compose.yml vim myapp/.env.example # Never commit real .env # Test thoroughly docker compose -f myapp/docker-compose.yml up -d # Commit with clear message git add myapp/ git commit -m "Add MyApp service with PostgreSQL and Redis - Web interface at myapp.rbnk.uk - Includes automated backups - Integrated with Traefik for SSL" # Push to Gitea (primary) git push -u origin add-myapp-service # Or push to all remotes git push-all ``` ### 3. Environment File Handling ```bash # Create example file cp myapp/.env myapp/.env.example # Edit to remove sensitive values vim myapp/.env.example # Add to .gitignore echo "myapp/.env" >> .gitignore ``` ### 4. Pre-deployment Checklist - [ ] Service starts successfully - [ ] Health checks pass - [ ] Traefik routing works - [ ] SSL certificate obtained - [ ] Backups configured - [ ] Monitoring/alerts set up - [ ] Documentation complete - [ ] .env.example created - [ ] No secrets in commits ### 5. Deployment Process ```bash # On production server git pull origin main # Copy environment file cp myapp/.env.example myapp/.env vim myapp/.env # Add real values # Deploy docker compose -f myapp/docker-compose.yml up -d # Verify docker compose -f myapp/docker-compose.yml ps curl https://myapp.rbnk.uk/health ``` ## Common Patterns and Examples ### Database-Backed Web App See `_templates/stack-template/` for the standard pattern ### Microservices Stack See `supabase/` for complex multi-service example ### Simple Static Site ```yaml version: '3.8' services: static-site: image: nginx:alpine volumes: - ./public:/usr/share/nginx/html:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro networks: - traefik_proxy labels: - "traefik.enable=true" - "traefik.docker.network=traefik_proxy" - "traefik.http.routers.static.rule=Host(`static.rbnk.uk`)" - "traefik.http.routers.static.entrypoints=websecure" - "traefik.http.routers.static.tls=true" - "traefik.http.routers.static.tls.certresolver=cloudflare" ``` ### API with Rate Limiting ```yaml labels: # Standard routing - "traefik.http.routers.api.rule=Host(`api.rbnk.uk`)" # Rate limiting middleware - "traefik.http.middlewares.api-ratelimit.ratelimit.average=100" - "traefik.http.middlewares.api-ratelimit.ratelimit.burst=200" - "traefik.http.routers.api.middlewares=api-ratelimit" ``` Remember: Always test thoroughly in development before deploying to production! --- ## development > claude-code ### Claude Code Configuration This infrastructure includes comprehensive Claude Code enhancements for AI-assisted operations. ## Overview Claude Code is configured with custom skills, agents, commands, and safety hooks specific to this infrastructure. These enhancements enable: - **Context-aware assistance** - Claude understands this specific infrastructure - **Safety guardrails** - Dangerous commands are blocked before execution - **Specialized agents** - Domain-specific experts for different tasks - **Quick commands** - Slash commands for common operations ## Components Summary | Type | Count | Location | |------|-------|----------| | Skills | 12 | `.claude/skills/` | | Custom Agents | 4 | `.claude/agents/` | | Quick Commands | 4 | `.claude/commands/infra/` | | Safety Hooks | 3 | `.claude/hooks/` | ## Skills Skills are model-invoked capabilities that activate automatically based on context. ### Infrastructure-Specific Skills | Skill | Activates When | Purpose | |-------|---------------|---------| | `monitoring-operations` | Discussing Prometheus, Grafana, Loki | PromQL queries, LogQL, dashboards | | `notification-management` | Working with ntfy, Apprise | Push notifications, SMTP gateway | | `security-auditing` | Security reviews, SSL checks | Certificate verification, secret detection | | `git-operations` | Gitea, GitHub sync | Repository management, BFG filtering | | `redis-management` | Redis operations | cache-redis, session-redis management | | `media-services` | Jellyfin, Metube | Media streaming and downloads | ### Core Skills (Pre-existing) | Skill | Purpose | |-------|---------| | `backup-restore` | Backup procedures and disaster recovery | | `database-operations` | PostgreSQL and Supabase database ops | | `docker-stack-manager` | Docker Compose stack management | | `infrastructure-diagnostics` | Troubleshooting and health checks | | `n8n-workflow-builder` | n8n automation workflows | | `service-deployment` | New service deployment with Traefik | ## Custom Agents Specialized agents with infrastructure-specific knowledge. ### infrastructure-specialist **Model**: Sonnet | **Tools**: Read, Grep, Glob, Bash, WebFetch Context-aware expert on this specific infrastructure. Knows: - Service topology and dependencies - Network architecture (traefik_proxy, supabase_default) - Configuration file locations - Critical service paths ### media-manager **Model**: Haiku | **Tools**: Read, Bash, Glob Fast operations for media services: - Jellyfin library management - Metube download troubleshooting - Stremthru and zurg configuration ### security-reviewer **Model**: Sonnet | **Tools**: Read, Grep, Glob (read-only) Security auditing without modification capability: - Configuration review - Secret detection - SSL/TLS verification - Container security analysis ### log-analyzer **Model**: Haiku | **Tools**: Read, Grep, Glob, Bash Fast log analysis and correlation: - Error pattern detection - Cross-service correlation - Incident timeline building ## Quick Commands Slash commands for common infrastructure operations. ``` /infra/quick-status - Fast infrastructure health overview /infra/service-health - Detailed service diagnostics /infra/emergency-stop - Safe shutdown procedures /infra/backup-now - Trigger immediate backup ``` ### Using Commands Commands are invoked with the slash prefix: ``` /infra/quick-status ``` Some commands accept arguments: ``` /infra/service-health grafana /infra/emergency-stop non-critical /infra/backup-now infrastructure ``` ## Safety Hooks Hooks intercept tool execution to enforce safety policies. ### PreToolUse Hooks **dangerous-command-blocker.py** Blocks catastrophic commands before execution: - `rm -rf /` and similar patterns - `docker system prune -a` - `DROP DATABASE`, `TRUNCATE TABLE` - Writing to system directories (`/etc`, `/usr`) **production-service-guard.py** Requires confirmation for critical service operations: - Protected services: supa-db, traefik, prometheus, grafana, alertmanager, loki, authentik, adguard, gitea, cache-redis, session-redis - Protected operations: stop, remove, kill, restart, down ### PostToolUse Hook **audit-logger.py** Logs all tool executions for audit trail: - Location: `.claude/logs/audit.log` - Includes: timestamp, session ID, tool name, input summary, result status ## Configuration Files | File | Purpose | |------|---------| | `.claude/settings.local.json` | Hook registration, permissions, MCP servers | | `.claude/skills/*/SKILL.md` | Individual skill definitions | | `.claude/agents/*.md` | Agent configurations | | `.claude/commands/infra/*.md` | Command definitions | ## Viewing Audit Logs ```bash # View recent audit entries tail -100 /srv/dockerdata/.claude/logs/audit.log # Search for specific tool usage grep "Bash" /srv/dockerdata/.claude/logs/audit.log | tail -20 # View today's activity grep "$(date +%Y-%m-%d)" /srv/dockerdata/.claude/logs/audit.log ``` ## Extending the Configuration ### Adding a New Skill 1. Create directory: `.claude/skills/my-skill/` 2. Create `SKILL.md` with YAML frontmatter: ```yaml --- name: my-skill description: Description of when this skill should activate --- # Skill Name ## Purpose [What this skill enables] ## Operations [Commands and procedures] ``` ### Adding a New Agent Create `.claude/agents/my-agent.md`: ```yaml --- name: my-agent description: What this agent specializes in tools: Read, Grep, Glob model: haiku --- # Agent Name ## Your Responsibilities [What this agent should focus on] ## Key Knowledge [Infrastructure-specific context] ``` ### Adding a New Command Create `.claude/commands/category/my-command.md`: ```yaml --- description: What this command does arguments: - name: arg1 description: Optional argument required: false --- # Command Name [Instructions for Claude to follow when command is invoked] ``` ## Related Documentation - \1 - Detailed hook documentation - \1 - Automation integration - \1 - AI-powered stack management - \1 - Code documentation generator with MCP integration --- ## development > development-guide ### Development Guide This guide covers development workflows, best practices, and procedures for working with the rbnk.uk infrastructure. ## Overview The development documentation provides guidelines for adding new services, modifying existing configurations, and following best practices for infrastructure as code. ## Development Workflow ### 1. \1 Get up and running quickly with: - Environment setup - Basic commands - Common tasks ### 2. \1 Step-by-step guide for deploying new services: - Using StackWiz for quick deployment - Manual service configuration - Traefik integration - DNS setup ### 3. \1 Using the stack creation wizard: - Creating generic service stacks - Deploying Pocketbase applications - Automatic DNS configuration - Template customization ### 4. \1 Version control best practices: - Branching strategy - Commit guidelines - Secret management - Sync procedures ### 5. \1 Setting up local development environment: - Docker Desktop configuration - Local testing procedures - Debugging techniques ### 6. \1 Integrating with infrastructure APIs: - Supabase REST API - LiteLLM endpoints - n8n webhooks - Monitoring APIs ## Development Tools ### StackWiz Quick stack creation tool: ```bash # Create generic service stackwiz myservice # Create Pocketbase app stackwiz --pocketbase myapp # With DNS creation stackwiz --create-dns myservice ``` ### Docker Commands Essential Docker operations: ```bash # Build and start services docker compose up -d --build # View logs docker compose logs -f SERVICE_NAME # Execute commands in container docker exec -it CONTAINER_NAME /bin/sh # Clean up docker system prune -a ``` ### Git Operations Version control commands: ```bash # Standard workflow git add . git commit -m "feat: add new service" git push origin main # Check for secrets git diff --staged | grep -iE 'password|secret|token|key' ``` ## Best Practices ### 1. Service Configuration - Use environment variables for configuration - Store secrets in `.env` files (never commit) - Follow existing naming conventions - Document all configuration options ### 2. Network Architecture - Add services to appropriate Docker networks - Use internal DNS names for service communication - Configure Traefik labels for external access - Document port mappings ### 3. Security - Never hardcode credentials - Use strong, unique passwords - Enable authentication on all services - Regular security updates ### 4. Documentation - Update documentation with changes - Include configuration examples - Document troubleshooting steps - Keep README files current ### 5. Testing - Test locally when possible - Verify Traefik routing - Check DNS resolution - Monitor logs during deployment ## Common Tasks ### Adding a New Service 1. **Create stack**: `stackwiz servicename` 2. **Configure service**: Edit `docker-compose.yml` 3. **Add Traefik labels**: ```yaml labels: - "traefik.enable=true" - "traefik.http.routers.servicename.rule=Host(`servicename.rbnk.uk`)" - "traefik.http.routers.servicename.tls.certresolver=cf" ``` 4. **Create DNS record**: `./_scripts/cloudflare-dns-create.sh servicename` 5. **Start service**: `docker compose up -d` 6. **Verify**: Access at `https://servicename.rbnk.uk` ### Updating Services 1. **Pull latest images**: ```bash docker compose pull docker compose up -d ``` 2. **Force recreate**: ```bash docker compose up -d --force-recreate ``` 3. **With build**: ```bash docker compose up -d --build ``` ### Debugging Issues 1. **Check logs**: `docker compose logs -f` 2. **Inspect container**: `docker inspect CONTAINER_NAME` 3. **Network debugging**: `docker exec CONTAINER ping TARGET` 4. **Traefik routing**: Check Traefik dashboard 5. **DNS issues**: Verify with `dig servicename.rbnk.uk` ## Integration Examples ### Supabase Integration ```javascript const { createClient } = require('@supabase/supabase-js'); const supabase = createClient( 'https://supabase.rbnk.uk', process.env.ANON_KEY ); ``` ### n8n Webhook ```bash curl -X POST https://n8n.rbnk.uk/webhook/WEBHOOK_ID \ -H "Content-Type: application/json" \ -d '{"event": "deployment", "service": "myapp"}' ``` ### Notification Integration ```bash # Send notification via helper script notify "Deployment Complete" "Service deployed successfully" normal system # Direct Apprise API curl -X POST https://apprise.rbnk.uk/notify/apprise \ -F "title=Deployment" \ -F "body=Service deployed" \ -F "tag=development" ``` ## Troubleshooting For common issues and solutions, see: - \1 - \1 - \1 - \1 ## Resources - **Templates**: `/srv/dockerdata/_templates/` - **Scripts**: `/srv/dockerdata/_scripts/` - **Documentation**: `/srv/dockerdata/docs/` - **Examples**: Check existing service configurations --- ## development > git-workflow ### Git Workflow for Dual Remote Setup ## Table of Contents 1. \1 2. \1 3. \1 4. \1 5. \1 6. \1 7. \1 8. \1 ## Overview This guide explains how to work effectively with our dual-remote Git setup, where Gitea serves as the primary repository and GitHub acts as an optional public mirror. ### Architecture Overview ``` ┌─────────────┐ Push/Pull ┌──────────────┐ │ Developer │ ←────────────────→ │ Gitea │ │ Machine │ │ (Primary) │ └─────────────┘ └──────┬───────┘ │ Auto-sync │ (hourly + │ on push) ↓ ┌──────────────┐ │ GitHub │ │ (Mirror) │ └──────────────┘ ``` ### Benefits of Dual Remote Setup 1. **Data Sovereignty**: Primary code stays on self-hosted infrastructure 2. **Public Collaboration**: Optional GitHub mirror for open-source projects 3. **Redundancy**: Multiple locations for code backup 4. **Flexibility**: Choose where to push based on project needs 5. **Security**: Sensitive projects stay private on Gitea ## Initial Setup ### Step 1: SSH Key Configuration First, ensure you have SSH keys set up for both Gitea and GitHub: ```bash # Generate SSH key if needed ssh-keygen -t ed25519 -C "your-email@example.com" # Add to SSH agent eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519 # Test Gitea connection ssh -T git@gitea.rbnk.uk # Test GitHub connection ssh -T git@github.com ``` ### Step 2: Configure SSH for Multiple Hosts Create or edit `~/.ssh/config`: ``` # Gitea Host gitea.rbnk.uk HostName gitea.rbnk.uk User git IdentityFile ~/.ssh/id_ed25519 Port 22 # GitHub Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519 Port 22 ``` ### Step 3: Clone and Configure Repository #### Option A: Using Setup Script (Recommended) ```bash # Clone from Gitea git clone git@gitea.rbnk.uk:username/project.git cd project # Run setup script curl -sSL https://gitea.rbnk.uk/admin/scripts/raw/branch/main/setup-git-remotes.sh | bash ``` #### Option B: Manual Configuration ```bash # Clone from Gitea git clone git@gitea.rbnk.uk:username/project.git cd project # GitHub remote should already be configured # If not, add it manually: git remote add github git@github.com:username/project.git # Verify remotes (standard naming as of 2025-07-27) git remote -v # origin = Gitea (primary) # github = GitHub (auto-synced mirror) ``` ### Step 4: Configure Git Aliases Add these helpful aliases to your `.gitconfig`: ```bash git config --global alias.push-all '!git push origin && git push github' git config --global alias.sync-github '!git fetch github && git merge github/$(git branch --show-current) && git push origin' git config --global alias.sync-gitea '!git fetch origin && git merge origin/$(git branch --show-current) && git push github' git config --global alias.remotes 'remote -v' git config --global alias.branches 'branch -avv' ``` ## Daily Development Workflow ### Standard Development Flow 1. **Start with a fresh branch**: ```bash # Ensure you're up to date git fetch origin git checkout main git pull origin main # Create feature branch git checkout -b feature/awesome-feature ``` 2. **Make your changes**: ```bash # Edit files vim src/main.py # Stage changes git add src/main.py # Commit with descriptive message git commit -m "feat: add user authentication module - Implement JWT-based auth - Add login/logout endpoints - Include rate limiting Closes #42" ``` 3. **Push to Gitea (primary)**: ```bash git push -u origin feature/awesome-feature ``` 4. **Create Pull Request**: - Navigate to https://gitea.rbnk.uk/username/project - Click "New Pull Request" - Select your feature branch - Add reviewers and description 5. **Automatic GitHub Sync**: - When PR is merged to main: GitHub sync triggers automatically - Push notification appears showing sync status - Manual sync if needed: `sudo systemctl start gitea-sync.service` ### Commit Message Format Follow conventional commits for consistency: ``` ():