VPS Docker Architecture
1. What? — Definition and context
Section titled “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)
Overview of the 7 stacks
Section titled “Overview of the 7 stacks”| Stack | Services | Function | RAM order of magnitude |
|---|---|---|---|
| security-stack | Caddy, CrowdSec, Trivy | Reverse proxy + WAF + CVE scanner | ~500 MB |
| n8n-stack | N8N main + 5 workers, PostgreSQL, Redis | Queue-mode automation | ~2 GB |
| ai-stack | Qdrant, CLI Ollama, Claude Redis, MCP Gateway, N8N MCP | AI, vectors, MCP | ~4 GB |
| odoo-stack | Odoo 18, PostgreSQL | ERP (DB guig_db) | ~1.5 GB |
| monitoring-stack | Prometheus, Grafana, Alertmanager, OTEL Collector, exporters | Observability + telemetry | ~1.5 GB |
| notify-stack | DIUN, ntfy | Notifications + update detection | ~200 MB |
| timetrackr-stack | PostgreSQL 17 (dedicated SSH tunnel) | Time-tracking buffer | ~150 MB |
Network topology
Section titled “Network topology”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
Section titled “2. Why? — Stakes and motivations”Why Docker Compose rather than Kubernetes?
Section titled “Why Docker Compose rather than Kubernetes?”-
Operational simplicity — Kubernetes introduces etcd, control plane, ingress controllers, all unsuitable for a single server. Docker Compose starts a stack with one command.
-
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.
-
No need for horizontal scaling — No autoscaling, no multi-node distribution. Vertical scaling (more RAM/CPU) is enough.
-
Learning curve — Compose is mastered, Kubernetes would be a disproportionate investment for this use case.
Problems solved by the multi-stack approach
Section titled “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
Section titled “3. How? — Technical implementation”Repository structure
Section titled “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, ...Shared Docker network
Section titled “Shared Docker network”All HTTP stacks plug into the external webproxy network:
networks: webproxy: external: trueThis 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 for the details.
Deployment commands
Section titled “Deployment commands”# 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 pullRecommended startup order
Section titled “Recommended startup order”- security-stack — The reverse proxy must be ready before the others to terminate TLS handshakes.
- monitoring-stack — To capture scrape history from the moment the others boot.
- ai-stack, n8n-stack, odoo-stack, notify-stack — In any order.
- timetrackr-stack — Independent (does not need webproxy).
Backups
Section titled “Backups”The scripts/backup-databases.sh script (daily cron at 03:00) produces:
pg_dumpof the N8N database (n8n).pg_dumpof the Odoo database (guig_db) + filestore archive.pg_dumpof 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.
4. What if? — Outlook and limits
Section titled “4. What if? — Outlook and limits”Current limits
Section titled “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
Section titled “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.
Related pages
Section titled “Related pages”Infrastructure
Section titled “Infrastructure”- Security Stack — Caddy + CrowdSec + Trivy + llms.txt
- N8N in queue mode — Scalable automation
- AI Stack — Qdrant + CLI Ollama + MCP Gateway
- Odoo 18 on Docker — ERP configuration, custom modules
- Monitoring Stack — Prometheus + Grafana + Telemetry → Odoo
- Notify Stack — DIUN + ntfy
- TimeTrackr Stack — PostgreSQL 17 + SSH tunnel
- Why Odoo — ERP choice
Workflows orchestrating these stacks
Section titled “Workflows orchestrating these stacks”- Telegram Orchestrator — Control hub
- Docker Updates — Auto-update via DIUN + approval
- Notification Hub — Centralised routing
- Claude Code Telemetry — Sessions → Odoo via Prometheus
- GitHub-Odoo Sync — Issues / PRs / commits → Odoo tasks
- TimeTrackr → Odoo — Timesheets via webhooks
Reference
Section titled “Reference”- Glossary — Definitions of technical terms