← All posts

Running 15+ Production Services on a Notebook: My HomeLab Setup

June 24, 2026

Updated June 28, 2026

My HomeLab runs on a single notebook 24/7 — a recycled HP Pavilion with 8GB RAM — and it powers 15+ services including this blog. Here’s how it’s built, and how I’ve been hardening it as I go.

The Stack

  • Hypervisor: Proxmox VE
  • Containers: Portainer (stable services) + Dokploy (new deploys, host mode)
  • Networking: Cloudflare Tunnel (zero open ports) + Tailscale VPN for admin access
  • DNS: Cloudflare, with Terraform management in progress
  • Observability: Prometheus + Grafana + Jaeger + OpenTelemetry Collector
  • Automation: n8n + a custom MCP server for AI-agent administration

Why This Matters: Running everything locally gives me complete control over my data and infrastructure. No cloud bills for staging environments, no vendor lock-in for experimental services. Cloudflare Tunnel keeps the attack surface at zero while still exposing what needs to be public; Tailscale handles everything administrative that shouldn’t be.

Engineering Decision: Why Not Docker Swarm?

One real engineering decision worth mentioning: I initially tried Docker Swarm for orchestration, but hit a wall — Swarm’s IPVS-based routing mesh doesn’t work inside unprivileged LXC containers on Proxmox.

Rather than fight the kernel, I moved stable services to Portainer (standalone/compose) and adopted Dokploy for new deploys, migrating services over progressively.

Security Audit: Fixing My AI Agent

The Problem: I built a custom MCP server that lets AI agents manage this homelab directly: checking container status, starting/stopping services, pulling logs. When I reviewed it properly, I found the agent was connecting as root, directly to the Proxmox hypervisor, with a tool that accepted arbitrary shell commands with no input validation.

That’s a real risk, not a hypothetical one — full host compromise if the MCP server itself were ever exploited.

The Fix:

  1. Replaced root/hypervisor access with a non-root user scoped to the container itself
  2. Removed the arbitrary-shell-command tool entirely
  3. Added an allowlist for every operation that takes a container name, with input sanitization
  4. Added structured audit logging for every tool call

The whole fix is documented as an ADR. The Docker socket inside the container is still root-equivalent — that’s a known, accepted risk of how Docker works, not something I’m pretending is solved.

Troubleshooting Done

OTEL collector stuck in restart loop: deprecated exporter `otlp_grpc` removed in collector v0.103.0. Fixed by upgrading the config to the new exporter name — now running stable with traces from AI agent sessions confirmed landing in Jaeger. The full pipeline is healthy: agent action → OTLP → collector → Jaeger.

Next Up

  • Terraform DNS reconciliation (state divergence detected during plan)
  • Ansible playbook execution (3 investigation specs written)
  • Docker socket risk assessment

Production Services (15+ Running 24/7)

Core Infrastructure

ServicePurpose
n8nWorkflow automation (public)
DokployPaaS platform (public)
erickguedes.comPersonal portfolio site (SSR)
Wiki (MkDocs)Internal documentation

Media Stack

ServicePurposeAccess
JellyfinMedia streamingLAN only
qBittorrentTorrent clientLAN only
RSS aggregatorsContent feed aggregationLAN only

Observability Stack

ServicePurpose
GrafanaDashboards & alerts
PrometheusMetrics collection
JaegerDistributed tracing
OpenTelemetry CollectorOTLP HTTP receiver

Additional Services

ServicePurposeAccess
PortainerContainer managementTailscale only
CloudflaredTunnel agentLAN only

Architecture Highlights

Zero Open Ports

Implementation: Cloudflare Tunnel configured to route only necessary services to the internet. Router has zero ports forwarded.

Result: Attack surface is effectively zero. Cloudflare handles all ingress, TLS, DDoS protection.

Non-Root Container Execution

Problem: Docker by default runs containers as root. In a security-conscious environment, this is unacceptable.

Solution: Container runs as non-root user (1000) inside LXC, with bind mounts restricted to container’s home directory.

Tradeoffs:

  • Can’t run some root-only Docker operations (depends on how container is started)
  • More complex configuration for volumes

Status: Production, stable, audited

Docker Socket Risk

Problem: If container can access Docker socket, it can run arbitrary commands as root on the host.

Solution: Containerized Docker daemon (docker:dind) with scoped access, no host socket exposed.

Status: Production, documented risk, mitigation in progress

Self-Automation: Custom MCP Server

What It Does

Builds a custom MCP server that allows AI agents to manage infrastructure directly:

  • Operations: Check status, start/stop/restart containers, view logs
  • Security: Scoped to container level, no root access to Proxmox
  • Hardening: Allowlist-based, input sanitization, audit logging
  • Integrations: Traces send to Jaeger via OpenTelemetry

The Fix (Security Audit)

Initial implementation had two critical issues:

  1. Root access: Agent connected as root to Proxmox hypervisor (full host compromise risk)
  2. Arbitrary shell execution: Tool accepted arbitrary shell commands with no input validation (RCE risk)

Implemented Fix:

  1. Replaced root/hypervisor access with non-root user scoped to LXC container
  2. Removed arbitrary shell execution entirely
  3. Implemented allowlist for all operations (container names, actions)
  4. Added structured audit logging for every tool call

The whole fix is documented as an ADR. The Docker socket inside the container is still root-equivalent — that’s a known, accepted risk of how Docker works, not something I’m pretending is solved.

Real-Time Alerting: Prometheus → Alertmanager → n8n → Telegram

Complete Alert Pipeline:

Prometheus → Alertmanager (9093) → n8n Webhook → Telegram Bot → Me (and others)

Benefits

  • No automated incident response: ✅ Alerts reach Telegram automatically
  • No human delay: ✅ Get notified in minutes, not hours
  • 24/7 coverage: ✅ Don’t miss critical alerts during off-hours
  • Accountability: ✅ Know exactly when alerts fire and who gets notified

7 Automated Alert Rules

RuleConditionSeverity
High Error Raterate(agent_error_rate[5m]) > 0.1 for 2mWarning
High LLM Call Countsum(agent_llm_calls_total) > 100 for 5mWarning
Slow Tool Latencyhistogram_quantile(0.95, rate(agent_tool_latency_seconds_bucket[5m])) > 2 for 5mWarning
Token Usage Spikeincrease(agent_token_usage[1h]) > 10000 for 1hInfo
Critical LLM Failurerate(agent_llm_calls_total{status="error"}[5m]) > 0.5 for 1mCritical
No Tool Callsagent_tool_calls_total == 0 for 10mInfo
Agent Session Timeoutagent_session_duration > 3600 for 5mInfo

n8n Workflow

  • Parsing: n8n parses alerts, saves to database
  • Structured data: Better alert formatting and routing
  • Notifications: One-click Telegram integration

Infrastructure as Code

Terraform (Cloudflare DNS)

Managed Cloudflare records with Terraform scripts. Currently working on state divergence handling and automation.

Ansible (Container Management)

Container management playbooks, updates, and backups. Currently validating playbook execution (3 investigation specs written).

What’s Next

Immediate Priorities

  1. Terraform DNS reconciliation: State divergence handling, automation for 100+ Cloudflare records
  2. Ansible playbook validation: 3 investigation specs written, execution pending
  3. Docker socket risk assessment: Documented, mitigation in progress
  4. OTEL agent tracing activation: Extending tracing from workflow nodes to AI agent sessions

Future Roadmap

FeatureStatusDetails
Kubernetes migrationResearchLXC → K3s pilot environment
Enhanced MCP agent capabilitiesIn ProgressMore granular operations, self-healing
Advanced monitoringPlannedDistributed tracing for all services, anomaly detection
Disaster recovery testingPlannedBackup validation, restore procedures
Service mesh (Istio/Linkerd)FutureProduction-grade service-to-service security

Built with:

Proxmox VE, Docker, Kubernetes, Terraform, Ansible, Cloudflare, Tailscale, n8n, OpenTelemetry, Prometheus, Grafana, Jaeger, MCP

Infrastructure status: 15+ services running 24/7, production-grade reliability, actively maintained and evolving


EG

Erick Guedes

AI · SaaS · Sales Engineering · Solutions Consulting. Turning complex processes into scalable solutions.