aller au contenu
FRFlo
Table des matières

Self-hosting modern infrastructure often turns into an uncomfortable compromise between autonomy, convenience, and security. In conventional setups, scaling up services means punching dozens of ports through public firewalls, running exposed reverse proxies subject to vulnerability scans, and hand-crafting security rules across heterogeneous services.

My production infrastructure takes a radically different route: a unified stack running over 20 Docker containers (development toolchains, project management, AI agents, game servers, databases) governed by a strict Zero Trust architecture.

The core principle? Zero application sockets listen on any public interface. Inbound requests travel over outbound QUIC tunnels, edge access requires federated multi-IdP authentication, and direct administrative access to internal resources (databases, MCP servers) runs over a private mesh network via the MASQUE protocol.

Here is the comprehensive technical breakdown of this VPS stack and its multi-layer security posture.


Infrastructure Architecture Overview

The entire stack is deployed on a dedicated Linux machine (Debian 12) hosted at OVH.

flowchart TB
    subgraph Clients["Clients & Administration Workstations"]
        Browser["Web Browser / Client App"]
        AdminDev["Admin Workstation (WARP Client)"]
        DiscordClient["Discord (Bots & Autonomous Agents)"]
    end

    subgraph Edge["Cloudflare Edge & Zero Trust"]
        Access["Cloudflare Access (IAP / IdP)"]
        Gateway["Cloudflare Gateway (L4/L7/DNS Inspection)"]
        VNet["Virtual Network (Teamnet Mesh)"]
        CFMail["smtp.mx.cloudflare.net:465"]
    end

    subgraph Host["Dedicated Server (Debian 12)"]
        subgraph Daemons["Host System Daemons"]
            CFTunnel["cloudflared (QUIC Ingress Tunnel)"]
            WARPConn["WARP Connector (MASQUE Mesh Tunnel)"]
        end

        subgraph DockerEngine["Docker Engine (vps monorepo)"]
            subgraph NetApp["Bridge: applications"]
                AppWeb["Public Web Frontends<br>Dockhand, Pocket ID, Gitea<br>Kaneo, AI Agents, Beszel Hub"]
            end

            subgraph NetRes["Bridge: resources"]
                DBs["Internal Databases<br>PostgreSQL, Redis, MariaDB, MongoDB"]
                InternalServ["Internal Daemons<br>Postfix SMTP Relay, MCP Servers"]
            end

            subgraph NetWings["Bridge: wings"]
                WingsDaemon["Pelican Wings Daemon"]
                GameContainers["Isolated Game Servers"]
            end
        end
    end

    Browser -->|HTTPS and HTTP3| Access
    Access -->|Outbound QUIC Tunnel UDP 7844| CFTunnel
    CFTunnel -->|Loopback 127.0.0.1| NetApp

    AdminDev -->|MASQUE Tunnel| Gateway
    Gateway --> VNet
    VNet -->|Private Route| WARPConn
    WARPConn -->|Netfilter conntrack| NetRes
    WARPConn -->|Netfilter conntrack| NetApp

    InternalServ -->|TLS Wrappermode port 465| CFMail

1. Stack Orchestration & Management: Dockhand

Managing two dozen containers alongside independent volumes, bind mounts, and encrypted environment files quickly turns unwieldy through raw shell commands.

To centralize management without ever exposing the Docker daemon socket (/var/run/docker.sock) to the internet, the stack is orchestrated using Dockhand, a fast and modern Docker management platform.

flowchart LR
    subgraph HostEnv["Server Host"]
        Socket["/var/run/docker.sock"]
        Compose["docker-compose.yml (vps monorepo)"]
        EnvFiles[".env (Encrypted variables)"]
    end

    subgraph DockhandUI["Dockhand Engine"]
        UI["Web Management UI"]
        Metrics["Telemetry & Event Log"]
        AutoPrune["Image Pruning & Updates"]
    end

    UI --> Socket
    UI --> Compose
    UI --> EnvFiles

Core Orchestration Highlights:

  • Unified Declarative Stack (vps): Every service, virtual network, and storage volume is declared within a structured, version-controlled Docker Compose file.
  • GitOps Continuous Deployment via Dockhand Webhook: A GitHub Actions workflow automatically triggers Dockhand’s deployment webhook on every validated commit to the infrastructure repository, ensuring hands-off alignment without requiring direct SSH exposure.
  • Systematic Healthchecks: Every critical dependency (PostgreSQL, MariaDB, MongoDB, Redis, Gitea, Kaneo, Pelican, ownCloud, ocis-qdrant-indexer, etc.) implements explicit probes (CMD-SHELL, pg_isready, mongosh ping, HTTP /health probe verifying Qdrant connectivity), ensuring deterministic boot sequences using depends_on: service_healthy constraints.
  • Automated Lifecycle & Hygiene: Automated image digest polling (latest, lts) paired with periodic automated orphan image pruning.
  • Isolated Control Plane: Dockhand itself runs in an isolated container backed by a dedicated volume.

2. Application Catalog: Service Overview

The stack serves everyday needs across multiple domains: development toolchains, identity management, productivity, AI workflows, gaming, and analytics.

A. Identity, Authentication & Security

  • Pocket ID: Modern, lightweight OIDC identity provider written in Go and Svelte. Natively supports Passkeys (FIDO2 / WebAuthn) for passwordless, hardware-backed biometric sign-ins.
  • phpMyAdmin: Web-based administration interface for internal MariaDB databases.

B. Development, GitOps & CI/CD

  • Gitea: Self-hosted Git forge backed by PostgreSQL for state storage and Postfix for transactional mail notifications.
  • Gitea Mirror: Automated service continuously mirroring remote GitHub repositories onto the local Gitea instance to maintain sovereign, offline backups.
  • act-runner: Native CI/CD runner executing GitHub Actions workflows directly on the server without external dependencies.

C. Productivity & Project Management

  • Kaneo: Modern project and issue tracker (a fluid alternative to Linear or Jira), integrated with Pocket ID via OAuth/OIDC using PKCE flows.
  • ownCloud Infinite Scale (oCIS): Modern self-hosted collaborative storage and file-syncing platform. Integrated with Pocket ID via native OIDC SSO (custom claim and role mapping), it supports application tokens via the ocis-app-tokens extension, embeds web extensions (mdpresentation-viewer for Markdown slide presentations and unzip for archive decompression), and leverages an Apache Tika extractor for document content analysis. Its Content Security Policy (csp.yaml) is hardened to explicitly allow Cloudflare Access assertion origins and Pocket ID IdP endpoints (frame-src, connect-src), ensuring seamless OIDC token renewal and iframe integrations without CSP violations.
  • BentoPDF: Full-featured web utility suite for processing and editing PDF documents.

D. AI Ecosystem, Agents & MCP

  • Executor: Secure sandboxed code runtime and Model Context Protocol (MCP) server, offering controlled execution primitives for AI agents.
  • Hermes Agent: Autonomous AI agent (powered by NousResearch Hermes) acting as a Discord administration bot, connected through the CLI Proxy API gateway.
  • Pi Bridge: Communication and remote execution gateway for the Pi Coding Agent, synchronized in real time with Discord.
  • CLI Proxy API: Gateway proxy standardizing LLM requests with Service Token authorization, remote management support, and active usage statistics collection.
  • Qdrant & Apache Tika: High-performance vector database and document text extraction engine dedicated to Retrieval-Augmented Generation (RAG) workflows.
  • ocis-qdrant-indexer: Real-time multi-user RAG sidecar attached to the internal ownCloud Infinite Scale NATS event bus (nats://owncloud:9233). It enforces strict per-user access isolation (dedicated Qdrant vector collections partitioned by tenant hash and filtered by authorized spaces), encrypted at-rest storage of Personal Access Tokens (PATs) in a local SQLite store (users.sqlite backed by a dedicated persistent volume), multimodal Tika/PaddleOCR extraction, and exposes a Streamable HTTP MCP endpoint with native HTTP Basic authentication.
  • ocis-mcp-server: Official ownCloud Infinite Scale MCP server (ghcr.io/frflo/ocis-mcp-server:latest) deployed in the Docker stack. It exposes 80+ tools (file navigation, share management, metadata, spaces) to AI agents and Executor with cleartext HTTP enabled internally (OCIS_MCP_INSECURE=true).
  • PaddleOCR CPU: High-performance optical character recognition engine (ghcr.io/frflo/ocis-paddleocr:latest) running on the internal resources network (internal port 8868, client timeout extended to 15 minutes). Operates alongside Apache Tika for text extraction from scanned PDFs and images.
  • Persistent Google Chrome: Containerized Chrome browser (chrome) offering an interactive noVNC graphical session secured by Cloudflare Access along with a Chrome DevTools Protocol (CDP on internal port 9222) proxy for automated agent browser tasks.
  • GTFS MCP: Private MCP server offering transit query and spatial data analysis tools (Ilévia, SNCF, IDFM).
  • Open-Meteo MCP: Internal MCP server exposing weather forecasts and historical data to Executor.
  • Google Maps MCP: Internal MCP server providing geocoding, places, routes, traffic, and timezone data to Executor.
  • Moodle MCP: Internal MCP server providing integration with a learning platform.

E. Gaming & Game Server Virtualization

  • Pelican Panel: Next-generation game server management interface (the modern successor to Pterodactyl), connected to PostgreSQL and Postfix.
  • Pelican Wings: Host daemon governing the lifecycle of game server containers (Minecraft Bedrock, SteamCMD dedicated servers) on an isolated bridge network.

F. Media & Utilities

  • Cobalt: Private, privacy-preserving media download and extraction instance.
  • VERT: High-performance CPU-accelerated video conversion engine.
  • Your Spotify: Personal analytics dashboard tracking Spotify listening history and habits over a MongoDB datastore.

G. Observability, Backups & Monitoring

  • Beszel: Central monitoring hub providing real-time telemetry on CPU, RAM, disk I/O, and container health (password authentication disabled, security fully delegated to Cloudflare Access).
  • Beszel Agent: Lightweight monitoring agent deployed with a read-only Docker socket mount (/var/run/docker.sock:ro) feeding system telemetry continuously.
  • Arkeep: Continuous automated backup engine for Docker volumes and system configurations (arkeep-server and arkeep-agent), backed by a dedicated MariaDB database.
  • CPA Manager Plus: Dedicated administration console and observability dashboard for the cli-proxy-api AI gateway. Deployed locally on port 8018 with persistent SQLite storage (/data/usage.sqlite), it tracks token consumption, response latency, and per-model quotas without interfering with live production inference.
  • Containerized Healthchecks: Service health probes (notably phpMyAdmin) favor direct, non-blocking TCP network socket checks (fsockopen) over high-level HTTP wrappers, preventing false-positive instability flags during local redirects.

3. Layer 7 Ingress: Cloudflare Tunnel & QUIC

To make these applications reachable from the web without opening a single incoming HTTP or HTTPS port on the server’s firewall, Ingress relies on Cloudflare Tunnel (cloudflared).

sequenceDiagram
    participant Edge as Cloudflare Edge (PoPs)
    participant Cloudflared as cloudflared Daemon (Host)
    participant LocalApp as Docker Container (127.0.0.1:local_port)

    Note over Cloudflared,Edge: Boot: 4 outbound QUIC tunnels to the Edge
    Cloudflared->>Edge: Outbound persistent connection (UDP/7844)
    Edge-->>Cloudflared: Tunnels established & active

    Note over Edge: Inbound user request for a service
    Edge->>Cloudflared: Request multiplexed inside QUIC stream
    Cloudflared->>LocalApp: Local HTTP reverse proxy to 127.0.0.1
    LocalApp-->>Cloudflared: HTTP 200 OK Response
    Cloudflared-->>Edge: Packet returned via QUIC stream

Secured Ingress Mechanics

Every web application container binds strictly to loopback 127.0.0.1 on an unexposed port:

# cloudflared Ingress rules example
ingress:
- hostname: ssh
service: ssh://ssh
- hostname: docker
service: http://docker
- hostname: pocket-id
service: http://pocket-id
- hostname: beszel
service: http://beszel
- hostname: pelican
service: http://pelican
- hostname: vert
service: http://vert
- hostname: pdf
service: http://pdf
- hostname: media
service: http://media
- hostname: gitea
service: http://gitea
- hostname: gitea-mirror
service: http://gitea-mirror
- hostname: phpmyadmin
service: http://phpmyadmin
- hostname: cli-proxy-api
service: http://cli-proxy-api
- hostname: owncloud
service: http://owncloud
- hostname: kaneo
service: http://kaneo
- hostname: wings
service: http://wings
- hostname: executor
service: http://executor
- hostname: spotify
service: http://spotify
- hostname: spotify-api
service: http://spotify-api
- hostname: hermes
service: http://hermes
- hostname: pi-bridge
service: http://pi-bridge
- hostname: cpa
service: http://cpa-manager-plus
- hostname: arkeep
service: http://arkeep
- hostname: chrome
service: http://chrome
- service: http_status:404

With this architecture:

  • Zero Public Listening Ports: Running netstat or ss reveals no listeners on 0.0.0.0:80 or 0.0.0.0:443.
  • Edge-Level DDoS Protection: All inbound traffic is scrubbed across Cloudflare’s Anycast network before tunneling over outbound UDP.
  • Strict Infrastructure Service Isolation: Internal building blocks (PostgreSQL, Redis, MongoDB, MariaDB, Qdrant vector engine, Apache Tika extractor, oCIS NATS bus, or Postfix relay) carry zero public ingress rules. They remain strictly reachable over internal Docker bridges or through the secure MASQUE mesh network.
  • Strict AI Observability Segregation: The production inference endpoint, relied upon by autonomous agents, remains strictly focused on core model execution. The management and observability UI is entirely decoupled onto its own dedicated subdomain routed to local port 8018, preventing contention or disruptions to mission-critical inference pipelines.

4. Authentication & IAP: Multi-IdP Identity Broker

Application endpoints are guarded behind Cloudflare Access (Identity Aware Proxy).

sequenceDiagram
    participant Client as Web Browser
    participant Access as Cloudflare Access (IAP)
    participant OIDCProvider as OIDC Provider
    participant Service as Web Application

    Client->>Access: Navigates to application URL
    Access->>Client: Redirect to Identity Broker
    Client->>OIDCProvider: Authenticates (Passkeys / WebAuthn)
    OIDCProvider-->>Access: Signed OIDC assertion
    Note over Access: Evaluates policies + signs JWT RS256
    Access->>Client: Encrypted session cookie
    Access->>Service: Injects Cf-Access-Jwt-Assertion header
    Service-->>Client: Renders application

Multi-Provider Federation (IdPs):

  1. Pocket ID: Primary self-hosted IdP enforcing FIDO2 / WebAuthn Passkeys.
  2. Discord OIDC: Identity broker for community bots and companion agent apps.
  3. GitHub & Google OAuth: Fallback identities for emergencies and recovery.

Granular Policies & Service Tokens:

  • Admin Policy (allow): Unrestricted access for the verified administrator.
  • WARP Device Posture (non_identity): Transparent single-click pass-through for trusted machines enrolled with a verified client certificate.
  • Machine-to-Machine Service Tokens: Automated script access via CF-Access-Client-Id and CF-Access-Client-Secret headers.
  • Targeted Bypass Rules: Selective public access for external endpoints (e.g. MCP OAuth metadata discovery).
  • Public Path Bypass Pattern: For services requiring partial public exposure (such as public file sharing on ownCloud Infinite Scale), a dedicated secondary Access application is assigned to public share paths (/s/*, /dav/public-files/*) with a bypass policy (everyone: {}). External guests can download shared assets without prior authentication, while root and private management paths remain strictly guarded by Zero Trust.
  • Multi-layered Policy for Observability Consoles: For sensitive management tools such as CPA Manager Plus, Cloudflare Access enforces a layered sequence combining an Admin group allow rule, WARP device posture verification (non_identity), and trusted IP override entries for comprehensive defense in depth.
  • Zero-Port SSH Access: SSH access is encapsulated over HTTPS using the ssh service hostname, completely removing port 22 from the public internet.

5. Layer 4 Mesh Network: WARP Connector & the MASQUE Protocol

To interact directly with backend datastores (PostgreSQL, MariaDB, Redis, MongoDB) or private MCP servers using local desktop tools (DBeaver, TablePlus, VS Code), the host runs a WARP Connector joined to a Cloudflare Virtual Network (Teamnet).

flowchart LR
    subgraph DevMachine["Developer Workstation (WARP Client)"]
        DBeaver["DBeaver / TablePlus (SQL)"]
        MongoCompass["MongoDB Compass"]
        MCPClient["MCP Client / VS Code"]
    end

    subgraph CFMesh["Cloudflare Virtual Network"]
        VNet["Private Virtual Network"]
        CGNAT["CGNAT Range: 100.64.0.0/10"]
    end

    subgraph VPS["Host Server (WARP Connector)"]
        WARPInt["CloudflareWARP Interface (MASQUE)"]
        Netfilter["Netfilter / conntrack"]

        subgraph Subnets["Announced Docker Subnets"]
            SubRes["resources network (Databases)"]
            SubApp["applications network (Tools & MCP)"]
            SubWings["wings network (Game servers)"]
        end
    end

    DBeaver -->|TCP SQL| WARPInt
    MongoCompass -->|TCP NoSQL| WARPInt
    MCPClient -->|HTTP MCP| WARPInt
    WARPInt --> Netfilter
    Netfilter --> SubRes
    Netfilter --> SubApp

The MASQUE Protocol (RFC 9298 / RFC 9484)

The mesh tunnel operates via MASQUE (Multiplexed Application Substrate over QUIC Encryption), tunneling raw IP packets (TCP and UDP) inside HTTP/3 over QUIC streams.

Compared to legacy VPN protocols (like standard WireGuard):

  • Traffic seamlessly negotiates corporate firewalls and restrictive proxies, masquerading as regular outbound HTTPS on port 443.
  • QUIC multiplexing prevents head-of-line blocking across concurrent database queries and admin sessions.

Client-Side Split Tunneling (Include Mode):

Only targeted destination IP blocks enter the tunnel:

  • Private Docker subnets on the host
  • Cloudflare WARP CGNAT range: 100.64.0.0/10 (RFC 6598)
  • Specified internal domain zones

Exposing Databases and Internal Services on the WARP Mesh

To reconcile direct administrative access with zero public exposure, internal services and databases bind exclusively to the IP address of the Cloudflare WARP interface (${CLOUDFLARE_MESH_IP}), preventing any socket listening on 0.0.0.0 or exposure through the L7 Cloudflare Tunnel ingress.

This segmentation follows two distinct patterns:

  • Databases on their standard ports: PostgreSQL, MariaDB, Redis, and MongoDB, allowing native connections from local desktop clients (DBeaver, TablePlus, Compass) without port collisions or custom mapping.
  • Internal services and MCP tooling on the 90xx port range:
    • Chrome DevTools / noVNC
    • Arkeep Server (gRPC)
    • Gitea (SSH)
    • Pocket ID (internal interface)
    • Wings (Pelican daemon)
    • Specialized MCP servers (google-maps-mcp, gtfs-mcp, moodle-mcp, open-meteo-mcp)
    • ocis-qdrant-indexer (Streamable HTTP)
    • paddleocr (OCR engine)
    • Postfix (SMTP relay)
    • Apache Tika (document extraction)
    • Qdrant (REST and gRPC endpoints)

6. Docker Micro-Segmentation: 3 Isolated Bridge Networks

To strictly limit the blast radius if any single application container is compromised, the environment is segmented into three independent bridge networks:

flowchart TD
    subgraph NetApp["Bridge: applications"]
        A1["Dockhand"]
        A2["Pocket ID"]
        A3["Gitea"]
        A4["Kaneo"]
        A5["Executor MCP"]
    end

    subgraph NetRes["Bridge: resources"]
        R1[("PostgreSQL")]
        R2[("Redis")]
        R3[("MariaDB")]
        R4[("MongoDB")]
        R5["Postfix SMTP Relay"]
    end

    subgraph NetWings["Bridge: wings"]
        W1["Pelican Wings Daemon"]
        W2["Dedicated Game Servers"]
    end

    A3 -->|SQL Queries| R1
    A4 -->|SQL Queries| R1
    A2 -->|SQL Queries| R1
    A3 -->|Outbound SMTP| R5
    A2 -->|Outbound SMTP| R5
    W1 -->|SQL Queries| R3
  1. applications: Hosts public web services. Containers on this bridge cannot communicate with the resources network unless explicitly multi-homed.
  2. resources: Houses internal databases and backend tools. No ports are mapped to the host; connections originate exclusively from authorized application containers or through the WARP mesh tunnel.
  3. wings: Dedicated entirely to gaming containers created by Pelican Wings, completely walled off from production databases and developer tooling.

7. Secure SMTP Egress via Cloudflare Email Routing

Transactional emails (account verification, Git notifications, system health alerts) bypass unencrypted or risky direct SMTP relays:

sequenceDiagram
    participant App as App Container (Gitea / Pocket ID)
    participant Postfix as Postfix Relay Container (Internal)
    participant CFMail as Cloudflare Email (smtp.mx.cloudflare.net:465)
    participant Recipient as Final Recipient

    App->>Postfix: Internal plaintext SMTP (port 25)
    Note over Postfix: TLS Wrappermode + SASL Auth (API Token)
    Postfix->>CFMail: TLS Connection to port 465
    CFMail-->>Recipient: Delivered with SPF / DKIM / DMARC signatures

The internal postfix container operates inside the resources bridge and securely forwards all outbound traffic to [smtp.mx.cloudflare.net]:465 using TLS wrappermode and a scoped Cloudflare API Token.

# Postfix service definition in docker-compose.yml
postfix:
container_name: postfix
image: mwader/postfix-relay:latest
networks:
- resources
environment:
- POSTFIX_mynetworks=10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 100.64.0.0/10
- POSTFIX_relayhost=[smtp.mx.cloudflare.net]:465
- POSTFIX_smtp_tls_security_level=encrypt
- POSTFIX_smtp_tls_wrappermode=yes
- POSTFIX_smtp_sasl_auth_enable=yes
- POSTFIX_smtp_sasl_password_maps=hash:/etc/postfix/sasl_passwd
- POSTMAP_sasl_passwd=[smtp.mx.cloudflare.net]:465 api_token:${CF_SMTP_API_TOKEN}

8. Linux Kernel Tuning: Netfilter, Conntrack & Routing

Bridging the WARP Connector virtual interface (CloudflareWARP) with internal Docker bridges requires the Linux kernel’s connection tracking subsystem (nf_conntrack) combined with IPv4 packet forwarding:

Terminal window
# Enable kernel-level packet forwarding
sysctl -w net.ipv4.ip_forward=1
# Permit ingress forwarding from WARP to internal Docker bridges
iptables -I FORWARD 1 -i CloudflareWARP -d 172.18.0.0/16 -j ACCEPT
# Allow return traffic via stateful conntrack inspection
iptables -I FORWARD 2 -o CloudflareWARP -s 172.18.0.0/16 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

When an administrator connects to an internal database via DBeaver across the WARP tunnel:

  1. The TCP SYN enters through CloudflareWARP.
  2. The nf_conntrack engine registers the new flow in its state table.
  3. The packet is routed directly to the appropriate Docker bridge.
  4. The service’s TCP SYN-ACK reply matches the RELATED,ESTABLISHED state rule and is returned automatically without public port exposure.

9. Gateway Filtering: DNS, HTTP & L4 Network Security

Upstream of the server, every connection is continuously screened by Cloudflare Gateway policies:

Layer Policy Rule Action Result
DNS Anti-Malware Block Prevents resolution of known malicious domains
DNS Anti-Phishing Block Mitigates credential harvesting attempts
DNS Trusted Allowlist Allow Prioritizes DNSSEC-validated trusted endpoints
HTTP Threat Inspection Block Detects and intercepts malicious payload URLs
L4 / Network Malware & Phishing Drop Block Immediate packet drop on flagged FQDN destinations

Conclusion

This infrastructure demonstrates that self-hosting a comprehensive, highly productive suite of services does not require sacrificing security:

  • Uncompromised Productivity: Over 20 containers handling dev, AI, gaming, and project workflows, orchestrated cleanly through Dockhand.
  • Zero Public Attack Surface: No web or SSH ports open to the public internet. Port scans encounter an impenetrable wall.
  • Continuous Identity Verification: Every request is verified through Pocket ID (Passkeys) and Cloudflare Access with cryptographic JWT assertions.
  • Transparent Remote Engineering: Administrative access to databases and backend tools relies on MASQUE mesh tunnels with zero friction and zero bastion servers.

A robust, modern setup built to last.