By LogicSystemIQ | May 24, 2026 | 9 min read
The hardware (less than you think)
The host is a Dell OptiPlex 7060 Micro. It runs Windows 11 because I had it lying around. The CPU is an Intel i7-8700T, which is the 35-watt low-power variant of the i7-8700. Not the fastest chip on the planet, but it sips power and stays quiet under a desk.
| Component | Spec |
|---|---|
| Form factor | Dell OptiPlex 7060 Micro (1L footprint) |
| CPU | Intel i7-8700T (6 cores, 12 threads, 35W TDP) |
| RAM | 32GB DDR4 2666 MT/s |
| Storage | 2.73TB total, 1.25TB free |
| GPU | Intel UHD 630 integrated (no dedicated) |
| Network | 1GbE built in |
The host runs Windows because that is where I do client work, and the VM is just one window I open when I need to. If you have a Linux-only machine, this works even better because you skip the VM layer entirely and run Docker on the host.
The VM (where everything actually lives)
| Setting | Value |
|---|---|
| Hypervisor | VMware Workstation Pro 17.x (free for commercial use, November 2024) |
| Guest OS | Ubuntu Server 24.04 LTS |
| vCPU | 1 socket, 4 cores |
| RAM | 12GB |
| Disk | 150GB on a single VMDK file |
| Networking | Bridged (gets a real LAN IP from my router) |
| VT-x passthrough | Disabled (Hyper-V holds the extensions for WSL2) |
| VMware Tools | Installed |
| Time sync | Enabled |
The 12GB allocation is intentional. The host has 32GB, but Windows + Edge + Slack + whatever else I am doing easily eats 16GB. Leaving 12GB to the VM and keeping a 4GB buffer for the host has worked smoothly.
The container roster (all 27)
Here is exactly what runs on the VM, grouped by deploy phase.
Phase 1: core infrastructure (8 containers)
| Container | Image | Job |
|---|---|---|
| dockge | louislam/dockge:1 | Web UI for managing all the compose stacks |
| caddy | caddy:2-alpine | Reverse proxy, auto-HTTPS for LAN via internal CA, internal routing |
| cloudflared | cloudflare/cloudflared:latest | Outbound tunnel to Cloudflare edge for public hostnames |
| postgres | postgres:16-alpine | Shared Postgres for Vaultwarden, Twenty, GoTrue, n8n |
| dozzle | amir20/dozzle:latest | Real-time log viewer for every container |
| vaultwarden | vaultwarden/server:latest | Password manager (Bitwarden-compatible, but self-hosted) |
| beszel | henrygd/beszel:latest | System and container monitoring with ntfy alerts |
| beszel-agent | henrygd/beszel-agent:latest | Metric collector that reports to Beszel hub |
| diun | crazymax/diun:latest | Image update notifier (no auto-updates, just ntfy) |
| ntfy | binwiederhier/ntfy:latest | Self-hosted push notifications to my phone |
Wait, that is 10, not 8. Phase 1 added the ntfy + diun pair after the initial bring-up. Same idea, slightly bigger than the original spec.
Phase 2: business apps (11 containers)
| Container | Image | Job |
|---|---|---|
| redis | redis:7-alpine | Shared cache + queue for AppFlowy + Twenty |
| clickhouse | clickhouse/clickhouse-server:24-alpine | Analytics DB for Plausible |
| plausible | ghcr.io/plausible/community-edition:v3 | Self-hosted privacy-respecting web analytics |
| gotrue | appflowyinc/gotrue:latest | Auth service for AppFlowy (their fork of Supabase GoTrue) |
| minio | minio/minio:latest | S3-compatible storage for AppFlowy and Twenty file uploads |
| appflowy_postgres | pgvector/pgvector:pg16 | Dedicated Postgres for AppFlowy (needs pgvector extension) |
| appflowy_cloud | appflowyinc/appflowy_cloud:latest | AppFlowy backend (Notion alternative) |
| appflowy_web | appflowyinc/appflowy_web:latest | AppFlowy frontend |
| twenty_server | twentycrm/twenty:latest | Twenty CRM server (Pipedrive alternative) |
| twenty_worker | twentycrm/twenty:latest | Twenty CRM BullMQ background worker |
Add-on stacks (7 containers, deployed over the last 48 hours)
| Container | Image | Job |
|---|---|---|
| n8n | n8nio/n8n:1 | Workflow automation (greenfield home-stack instance) |
| firecrawl_api | ghcr.io/firecrawl/firecrawl:latest | Self-hosted web scraper API |
| firecrawl_playwright | ghcr.io/firecrawl/playwright-service:latest | Headless Chromium for scraping |
| firecrawl_redis | redis:alpine | Dedicated Redis for Firecrawl (upstream warns against sharing) |
| firecrawl_rabbitmq | rabbitmq:3-management | Job queue for Firecrawl |
| firecrawl_nuq_postgres | ghcr.io/firecrawl/nuq-postgres:latest | Crawl history database |
| openwebui | ghcr.io/open-webui/open-webui:main | Self-hosted Claude / ChatGPT replacement (via OpenRouter) |
Add it all up and you get 27.
Where the 12GB of RAM goes
Here is the actual breakdown from free -h right now:
total used free shared buff/cache available
Mem: 11Gi 7.0Gi 493Mi 171Mi 4.8Gi 4.8Gi
The big consumers:
| Tier | Approximate RAM |
|---|---|
| Postgres + ClickHouse + the 4 Redis-class processes | 1.2GB combined |
| AppFlowy stack (Cloud + Web + GoTrue + dedicated PG) | 1.5GB |
| Twenty CRM (server + worker) | 1.0GB |
| Plausible | 350MB |
| Firecrawl playwright headless Chromium pool | 600MB to 1.5GB depending on active scrapes |
| Open WebUI | 350MB |
| n8n | 250MB |
| Everything else (Caddy, cloudflared, Dockge, Dozzle, Beszel, Vaultwarden, ntfy, diun) | under 600MB combined |
Buffers and cache eat the rest, which is exactly what you want, that is Linux working as designed.
The biggest variable is Firecrawl during an active scrape. Each headless Chromium tab is roughly 200MB. The compose limits it to 3 concurrent jobs and 5 browser pool, which caps Firecrawl spend around 1.5GB. If a scrape kicks off while Twenty is indexing and AppFlowy is doing background sync, that is the load spike where I might hit 9GB used, 3GB free. Still fine.
The two architectural choices that made this work
One: shared Postgres for almost everything
Instead of running a dedicated Postgres per app, one shared postgres:16-alpine container hosts databases for Vaultwarden, Twenty CRM, GoTrue, and n8n. Each gets its own database and role, scoped with grants. The exception is AppFlowy, which needs pgvector and gets a dedicated pgvector/pgvector:pg16 container.
Single Postgres saves about 700MB of RAM versus running four of them. The trade-off is one upgrade hits all apps. Acceptable in exchange for the savings.
Two: Caddy + Cloudflare Tunnel instead of port forwarding
Every public-facing service routes through Caddy on a private Docker network. Caddy talks to cloudflared, which holds an outbound persistent tunnel to Cloudflare edge. No ports forwarded on my router. No public IP needed on the host. Cloudflare Access sits in front of admin tools with email OTP gating, and webhook-only endpoints (Stripe receivers, n8n triggers) get bypass policies so external services can hit them without an OTP prompt.
This setup costs zero dollars on Cloudflare’s free tier and is more secure than anything I could have hand-rolled.
What I cancelled, what I saved
Rough monthly savings, based on what each replaced:
| Replaced SaaS | Self-hosted equivalent | Monthly savings |
|---|---|---|
| Last password manager subscription | Vaultwarden | $3 |
| Notion team plan | AppFlowy | $10 |
| Pipedrive starter | Twenty CRM | $14 |
| Google Analytics + privacy concerns | Plausible | $0 in dollars, big in privacy |
| Uptime monitoring SaaS | Beszel + ntfy | $7 |
| n8n cloud | Self-hosted n8n | $20 |
| ChatGPT Plus | Open WebUI via OpenRouter | $20 (replaced with ~$5 pay-as-you-go) |
| Firecrawl SaaS tier | Self-hosted Firecrawl | $20 to $50 |
| Total | ~$50 to $90 per month |
Annualized, somewhere between $600 and $1,080. Enough to fund the GMKtec K11 mini PC that this whole stack will migrate to once it arrives.
What does not work, and why I do not care
A few things this setup is honestly bad at:
- Heavy LLM inference: i7-8700T with no dedicated GPU is useless for local Ollama. Open WebUI uses OpenRouter for model calls. That is a permanent decision until the K11 hardware arrives.
- Sustained Playwright scraping: Firecrawl works fine for ad-hoc and low-volume crawls. If I tried to scrape 10,000 pages a day, the thermal throttling on the 35W chip would punish me. I run it under 100 pages a day in practice.
- Real high availability: if the host dies, everything goes down until I boot another machine. Daily Kopia snapshots to Backblaze B2 means I can restore to new hardware in roughly 30 minutes. Good enough for personal infrastructure.
What is next
The K11 (GMKtec, Ryzen 9 8945HS, 64GB DDR5, dual NVMe) is the planned upgrade. Once it lands, the migration is roughly:
- Restore Kopia snapshot from B2 to the K11 host
- Rebuild Docker network with the same compose files
- Update Cloudflare Tunnel public hostnames to point at the new tunnel
- Cut over DNS, verify each service end to end
- Repurpose the OptiPlex as a dev workstation
The stack is designed to be portable. Everything is in git, every secret is in Vaultwarden, every data path is in the backup. The whole thing should migrate cleanly in an afternoon.
Frequently asked questions
Why not Proxmox or unRAID instead of VMware Workstation?
I will use Proxmox on the K11. On the Dell OptiPlex, VMware Workstation Pro was the path of least resistance because it ran on my existing Windows install. No bare metal swap needed. Free for commercial use since November 2024.
Why not Kubernetes?
For 27 containers across 6 stacks on a single host, Docker Compose plus Dockge for the UI is the right tool. Kubernetes would be roughly 1.5GB of overhead for the control plane and zero practical benefit at this scale.
How do you back this up?
Kopia handles snapshots of /opt/stack/data and the Postgres dumps every night at 2 AM. Two destinations: local USB drive (when plugged in) and Backblaze B2 (always). Daily snapshots, 7 daily / 4 weekly / 6 monthly retention. Monthly test restore to a tmp directory to verify backups actually work.
What about security?
Cloudflare Access in front of admin tools (email OTP for me, IP allowlist for service-to-service calls). Vaultwarden holds every credential, with Argon2id hashed admin token. Postgres is on a separate internal Docker network with no external port. UFW on the host blocks everything except SSH from LAN. Daily automatic security updates via unattended-upgrades.
Can I see the actual compose files?
Open to it. Drop me a comment or DM on LinkedIn and I will share the relevant compose snippets sanitized for public consumption.
Bottom line
You do not need a $3,000 server, a Kubernetes cluster, or a SaaS subscription per app to run a serious self-hosted stack. A used mini PC, a free hypervisor, and Docker Compose with discipline gets you 90 percent of the way there. The remaining 10 percent (HA, GPU inference, multi-host failover) is what real infrastructure budgets are for, and most of us do not need them.
If you are thinking about starting your own self-hosted stack, the question is not “what is the perfect architecture.” It is “what is the smallest thing I can stand up this weekend that I will actually use.” Pick one tool you are paying for, replace it, and let momentum carry you.
LogicSystemIQ is an IT managed services and SaaS studio based in Peabody, Massachusetts. We build DaycarePro (daycarepro.cloud), a trilingual SaaS for licensed home daycare providers. Reach us at (978) 815-1047 or Support@LogicSystemiq.com.