--- title: VPS Docker Architecture url: https://blog.guigpap.com/en/infrastructure/architecture-vps/ url_md: https://blog.guigpap.com/en/infrastructure/architecture-vps.md category: infrastructure date: '2026-01-19' maturite: production techno: - docker - caddy - crowdsec application: - infrastructure - operations --- # VPS Docker Architecture > Overview of the multi-stack Docker infrastructure on Hostinger KVM (7 stacks, webproxy network, dedicated SSH tunnel) ## 1. What? — Definition and context A **multi-stack Docker architecture** describes a setup where several groups of services (stacks) coexist on the same server, each defined by its own `docker-compose.yaml` file. Each stack is autonomous but can communicate with the others through a shared Docker network. This infrastructure runs on a Hostinger KVM 4 VPS: - **16 GB RAM** / **4 vCPU** / **Debian 13** - **7 interconnected Docker stacks** - **1 reverse proxy** (Caddy) as the HTTP entry point - **1 dedicated SSH tunnel** as the non-HTTP entry point (for TimeTrackr) > **Note - Docker stack** > > A **stack** is a coherent set of containers that fulfils a function (security, automation, ERP, ...). Docker Compose starts, stops and manages every container in a stack as a single unit. ### Overview of the 7 stacks | Stack | Services | Function | RAM order of magnitude | |-------|----------|----------|------------------------| | [security-stack](/en/infrastructure/security-stack/) | Caddy, CrowdSec, Trivy | Reverse proxy + WAF + CVE scanner | ~500 MB | | [n8n-stack](/en/infrastructure/n8n-queue-mode/) | N8N main + 5 workers, PostgreSQL, Redis | Queue-mode automation | ~2 GB | | [ai-stack](/en/infrastructure/ai-stack/) | Qdrant, CLI Ollama, Claude Redis, MCP Gateway, N8N MCP | AI, vectors, MCP | ~4 GB | | [odoo-stack](/en/infrastructure/odoo-18-setup/) | Odoo 18, PostgreSQL | ERP (DB `guig_db`) | ~1.5 GB | | [monitoring-stack](/en/infrastructure/monitoring-stack/) | Prometheus, Grafana, Alertmanager, OTEL Collector, exporters | Observability + telemetry | ~1.5 GB | | [notify-stack](/en/infrastructure/notify-stack/) | DIUN, ntfy | Notifications + update detection | ~200 MB | | [timetrackr-stack](/en/infrastructure/timetrackr-stack/) | PostgreSQL 17 (dedicated SSH tunnel) | Time-tracking buffer | ~150 MB | ### Network topology ```mermaid flowchart TB Internet([Internet]) SSHClient(["TimeTrackr.exe · user workstation"]) subgraph Edge["Entry points"] direction TB Caddy["Caddy · :80, :443, :443/udp"] SSH["sshd · :22 · restricted timetrackr-tunnel"] end Internet -->|HTTPS / HTTP3| Caddy SSHClient -->|SSH key + ForceCommand| SSH subgraph WP["webproxy network (shared)"] direction TB Sec["security-stack · CrowdSec, Trivy"] N8NMain["n8n-stack · N8N main + 5 workers"] AI["ai-stack · CLI Ollama, MCP Gateway"] Odoo["odoo-stack · :8069, DB guig_db"] Mon["monitoring-stack · Grafana, Prometheus"] NT["notify-stack · ntfy"] end subgraph Isolated["Isolated networks"] direction TB AIBack["mcp-backend · gateway ↔ n8n-mcp"] Qdrant["ai-internal · Qdrant, Claude Redis"] TT["timetrackr-internal · PG 17"] end Caddy --> Sec Caddy --> N8NMain Caddy --> Odoo Caddy --> Mon Caddy --> NT AI --- AIBack AI --- Qdrant SSH -->|"forward 5433"| TT ``` Three families of Docker networks coexist: - **`webproxy`** — shared bridge, the routing point for Caddy. - **Per-stack internal networks** (`n8n-internal`, `odoo-internal`, `monitoring`, `ai-internal`) — intra-stack communication. - **Isolated networks** (`mcp-backend`, `timetrackr-internal`, `crowdsec-internal`) — reduced surfaces for sensitive components (MCP gateway, TimeTrackr database, CrowdSec API). --- ## 2. Why? — Stakes and motivations ### Why Docker Compose rather than Kubernetes? 1. **Operational simplicity** — Kubernetes introduces etcd, control plane, ingress controllers, all unsuitable for a single server. Docker Compose starts a stack with one command. 2. **Resource constraints** — The Kubernetes overhead (2–4 GB RAM for the control plane) would consume 15–25% of the VPS. Docker Compose has no such overhead. 3. **No need for horizontal scaling** — No autoscaling, no multi-node distribution. Vertical scaling (more RAM/CPU) is enough. 4. **Learning curve** — Compose is mastered, Kubernetes would be a disproportionate investment for this use case. > **Tip - When Kubernetes becomes relevant** > > When you need: multi-node high availability, load-based autoscaling, automated blue-green deployments, or managing several servers as a cluster. ### Problems solved by the multi-stack approach | Problem | Solution | |---------|----------| | **Failure isolation** | A failing stack does not impact the others | | **Independent deployment** | Update a service without restarting the whole system | | **Diverging versions** | Each stack manages its own dependencies (PG 15 for N8N/Odoo, PG 17 for TimeTrackr) | | **Security through segmentation** | Minimal surfaces: MCP backend isolated, TimeTrackr database off webproxy | | **Observability** | Dedicated stack scraping all the others | --- ## 3. How? — Technical implementation ### Repository structure ``` stacks_vps/ ├── security-stack/ # Caddy + CrowdSec + Trivy ├── n8n-stack/ # N8N in queue mode, 5 workers ├── ai-stack/ # Qdrant + CLI Ollama + MCP Gateway + N8N MCP ├── odoo-stack/ # Odoo 18 ERP (DB guig_db) ├── monitoring-stack/ # Prometheus + Grafana + OTEL ├── notify-stack/ # DIUN + ntfy ├── timetrackr-stack/ # PostgreSQL 17 + SSH tunnel (off webproxy) ├── workflows/ # N8N workflow documentation ├── docs-external/ # 📦 Submodule: external CLI docs ├── n8n-exports/ # 📦 Submodule: N8N workflow exports ├── blog/ # 📦 Submodule: this blog (Astro) └── scripts/ # deploy-all.sh, backup-databases.sh, ... ``` > **Caution - Git submodules** > > `docs-external/`, `n8n-exports/` and `blog/` are submodules. After cloning: `git submodule update --init --recursive` (or `./scripts/setup-dev.sh`). ### Shared Docker network All **HTTP** stacks plug into the external `webproxy` network: ```yaml networks: webproxy: external: true ``` This bridge network allows Caddy to reach any container by its name (`n8n:5678`, `odoo:8069`, `grafana:3000`, `ntfy:80`, ...). **Exception: `timetrackr-stack`** does not touch `webproxy`. Its PostgreSQL database is exposed only on `127.0.0.1:5433`, and access goes through a dedicated SSH user (`timetrackr-tunnel`) with `ForceCommand /bin/false` and `PermitOpen localhost:5433`. See [TimeTrackr Stack](/en/infrastructure/timetrackr-stack/) for the details. ### Deployment commands ```bash # Create the shared network (once) docker network create webproxy # Start every stack ./scripts/deploy-all.sh start # Status ./scripts/deploy-all.sh status # Logs of a stack ./scripts/deploy-all.sh logs n8n-stack # Restart ./scripts/deploy-all.sh restart odoo-stack # Pull + restart ./scripts/deploy-all.sh pull ``` ### Recommended startup order 1. **security-stack** — The reverse proxy must be ready before the others to terminate TLS handshakes. 2. **monitoring-stack** — To capture scrape history from the moment the others boot. 3. **ai-stack**, **n8n-stack**, **odoo-stack**, **notify-stack** — In any order. 4. **timetrackr-stack** — Independent (does not need webproxy). ### Backups The `scripts/backup-databases.sh` script (daily cron at 03:00) produces: - `pg_dump` of the N8N database (`n8n`). - `pg_dump` of the Odoo database (`guig_db`) + filestore archive. - `pg_dump` of the TimeTrackr database (`timetrackr_db`). - Backup of the `N8N_ENCRYPTION_KEY` (without it, credentials are unrecoverable). Local rotation (7 days) and GDrive push (30 days) via `rclone`. Failure → alert to the [Notification Hub](/en/workflows/notification-hub/). --- ## 4. What if? — Outlook and limits ### Current limits | Limit | Impact | Mitigation | |-------|--------|------------| | **Single server** | Physical SPOF | Daily GDrive backups + monitoring + alerts | | **No native HA** | Downtime on pull/restart | Planned maintenance windows, self-restart pattern for n8n-stack | | **Vertical scaling only** | KVM 4 hardware ceiling | Migration to KVM 8 or multi-node possible | | **AI stack is the most resource-hungry** | ~4 GB out of 16 available | Short-lived CLI subprocesses, no local model | ### Evolution scenarios **If I have to replicate this setup for another customer**: - The stacks become templates, packaged as Kubernetes namespaces (or Compose templates). - Helm charts for per-customer variations (DB names, secrets, domains). **If AI demands explode**: - The ai-stack becomes externalisable to a cloud GPU (the Ollama-compatible API interface makes the swap easy). - Keep Qdrant local for RAG latency, push CLI Ollama to a dedicated host. **If 16 GB becomes insufficient**: - KVM 8 upgrade (32 GB) — the simplest solution. - Or move monitoring + notify to a second server, the webproxy can be extended as a multi-host overlay. **If SSH sovereignty needs to be reinforced**: - Migration of the TimeTrackr tunnel to WireGuard (`wg.guigpap.com`). - Allows opening other private services (Postgres admin, internal Redis) without multiplying SSH bouncers. > **Note - Long-lived architecture** > > This setup covers current and foreseeable needs at 12–18 months. Its modularity allows progressive evolution (per stack) without a complete rewrite. --- ## Related pages ### Infrastructure - [Security Stack](/en/infrastructure/security-stack/) — Caddy + CrowdSec + Trivy + llms.txt - [N8N in queue mode](/en/infrastructure/n8n-queue-mode/) — Scalable automation - [AI Stack](/en/infrastructure/ai-stack/) — Qdrant + CLI Ollama + MCP Gateway - [Odoo 18 on Docker](/en/infrastructure/odoo-18-setup/) — ERP configuration, custom modules - [Monitoring Stack](/en/infrastructure/monitoring-stack/) — Prometheus + Grafana + Telemetry → Odoo - [Notify Stack](/en/infrastructure/notify-stack/) — DIUN + ntfy - [TimeTrackr Stack](/en/infrastructure/timetrackr-stack/) — PostgreSQL 17 + SSH tunnel - [Why Odoo](/en/infrastructure/why-odoo/) — ERP choice ### Workflows orchestrating these stacks - [Telegram Orchestrator](/en/workflows/telegram-orchestrator/) — Control hub - [Docker Updates](/en/workflows/docker-updates/) — Auto-update via DIUN + approval - [Notification Hub](/en/workflows/notification-hub/) — Centralised routing - [Claude Code Telemetry](/en/workflows/claude-code-telemetry/) — Sessions → Odoo via Prometheus - [GitHub-Odoo Sync](/en/workflows/github-odoo-sync/) — Issues / PRs / commits → Odoo tasks - [TimeTrackr → Odoo](/en/workflows/timetrackr/) — Timesheets via webhooks ### Reference - [Glossary](/en/reference/glossary/) — Definitions of technical terms ## Metadonnees agent - Cet article est issu du blog GuiGPaP Lab. - Contexte global du blog: https://blog.guigpap.com/llms.txt - Contact auteur: https://odoo.guigpap.com/mon-cv - Licence: CC-BY-SA 4.0