Tools: The $3/Month Enterprise-Ready Automation Stack: Zero-Trust n8n With One Command (n8n, Cloudflare, UpCloud, Pulumi) (2026)

Tools: The $3/Month Enterprise-Ready Automation Stack: Zero-Trust n8n With One Command (n8n, Cloudflare, UpCloud, Pulumi) (2026)

The Problem With Self-Hosted Automation

What You Get

Why Zero-Trust Matters Here

The 6-Pillar Stack

How It Wires Together

1. Pulumi Creates the Cloudflare Tunnel

2. The Tunnel Token Gets Fetched via API

3. DNS Points to the Tunnel

4. Cloud-Init Bootstraps the Server

5. Three Containers, One Compose File

The Setup (5 Minutes)

Prerequisites

Deploy

Tear Down

What About Persistence?

What About n8n Cloud?

The Full Lifecycle I run one command and get a fully working n8n instance — HTTPS, zero-trust tunnel, PostgreSQL, the works. One more command and it's all gone. Total cost: $3/month. No SSH. No open ports. No clicking around in dashboards. Here's exactly how it works. Every self-hosting tutorial I find tells me to do the same thing: open ports 80 and 443, set up Nginx, wrestle with Let's Encrypt, and pray nobody scans my server before I've hardened it. Then there's the database. Most n8n guides default to SQLite. It works — until two workflows fire at the same time and the whole thing locks up. I don't want to babysit a server. I want to deploy, use it, and tear it down when I'm done. No manual steps. No leftover infrastructure. So I built exactly that. One pulumi up command creates: One pulumi destroy removes everything. Server, tunnel, DNS — gone. Traditional setups expose your server to the internet. You open ports, configure firewalls, manage certificates. Every open port is an attack surface. Cloudflare Tunnels flip this model. The cloudflared daemon on your server creates an outbound-only connection to Cloudflare's network. Traffic flows in reverse: User → Cloudflare (HTTPS + DDoS) → Tunnel → n8n container Your server has zero open inbound ports. No firewall rules to manage. No certificates to renew. Cloudflare handles all of it at the edge. Each tool has one job: The entire deployment is 180 lines of TypeScript. Here's what happens when you run pulumi up: A 32-byte random secret gets generated. The tunnel routes traffic from your domain to http://n8n:5678 inside the Docker network. This is the key trick. Instead of SSHing into the server after boot to inject the token, Pulumi fetches it directly from Cloudflare's API. The token gets baked into the cloud-init script — zero post-deploy steps. A proxied CNAME record. Cloudflare's edge handles TLS termination and DDoS filtering before traffic ever reaches your server. Pulumi builds a cloud-init script that: The server boots, runs this script, and 60 seconds later n8n is live. No SSH required. PostgreSQL starts first. n8n waits for the health check. cloudflared connects the tunnel. The Docker network handles service discovery — n8n:5678 just works. That's it. Visit https://n8n.yourdomain.com and log in. Server, tunnel, DNS — all removed. Nothing left running. Nothing left billing. PostgreSQL data is saved as a Docker volume on the server itself. Your workflows, credentials, and execution history survive container restarts and docker compose down / up cycles. What they don't survive is pulumi destroy — that deletes the server and everything on it. For production, consider switching to a managed PostgreSQL service (UpCloud, Aiven, Neon) or mounting resilient block storage for the database volume. Either option decouples your data from the server lifecycle. n8n Cloud is the easiest way to get started. Managed hosting, automatic updates, built-in auth, and zero infrastructure to think about. If you don't want to manage servers, it's the right choice. This self-hosted stack is a different tradeoff. You get full control over the environment — custom domains, your own database, and the ability to spin up or tear down the whole thing in seconds. You also manage the infrastructure. Both are valid paths. Pick the one that fits how you work. Here's the entire flow, start to finish: You (one-time): Fill in a .env file (API tokens, domain, Cloudflare IDs). Run 4 secret config commands. SSH key is auto-detected. Pulumi (automated): Creates tunnel → fetches token → configures routes → creates DNS → builds cloud-init → provisions server. Server (automated): Boots → installs Docker → writes configs → starts containers. Result: n8n live at your domain with HTTPS, zero-trust networking, and PostgreSQL. No SSH. No open ports. $3/month. Done? pulumi destroy. Everything gone. Want to try it? The full source code is on GitHub. Clone the repo, set your API keys, and deploy in 5 minutes. Need help with your setup? I build and consult on automation infrastructure professionally. Reach out and let's talk about your use case. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Code Block

Copy

const tunnel = new cloudflare.ZeroTrustTunnelCloudflared("n8n-tunnel", { accountId: cfAccountId, name: "n8n-tunnel", configSrc: "cloudflare", tunnelSecret: crypto.randomBytes(32).toString("base64") }); const tunnel = new cloudflare.ZeroTrustTunnelCloudflared("n8n-tunnel", { accountId: cfAccountId, name: "n8n-tunnel", configSrc: "cloudflare", tunnelSecret: crypto.randomBytes(32).toString("base64") }); const tunnel = new cloudflare.ZeroTrustTunnelCloudflared("n8n-tunnel", { accountId: cfAccountId, name: "n8n-tunnel", configSrc: "cloudflare", tunnelSecret: crypto.randomBytes(32).toString("base64") }); const tunnelToken = cloudflare.getZeroTrustTunnelCloudflaredTokenOutput({ accountId: cfAccountId, tunnelId: tunnel.id }); const tunnelToken = cloudflare.getZeroTrustTunnelCloudflaredTokenOutput({ accountId: cfAccountId, tunnelId: tunnel.id }); const tunnelToken = cloudflare.getZeroTrustTunnelCloudflaredTokenOutput({ accountId: cfAccountId, tunnelId: tunnel.id }); const dnsRecord = new cloudflare.DnsRecord("n8n-dns", { zoneId: cfZoneId, name: domain, type: "CNAME", content: pulumi.interpolate`${tunnel.id}.cfargotunnel.com`, proxied: true }); const dnsRecord = new cloudflare.DnsRecord("n8n-dns", { zoneId: cfZoneId, name: domain, type: "CNAME", content: pulumi.interpolate`${tunnel.id}.cfargotunnel.com`, proxied: true }); const dnsRecord = new cloudflare.DnsRecord("n8n-dns", { zoneId: cfZoneId, name: domain, type: "CNAME", content: pulumi.interpolate`${tunnel.id}.cfargotunnel.com`, proxied: true }); services: postgres: image: postgres:18-alpine # Health check ensures n8n waits for DB n8n: image: n8nio/n8n:latest depends_on: postgres: condition: service_healthy # Full PostgreSQL config, HTTPS via tunnel cloudflared: image: cloudflare/cloudflared:latest command: tunnel run # Token from .env file services: postgres: image: postgres:18-alpine # Health check ensures n8n waits for DB n8n: image: n8nio/n8n:latest depends_on: postgres: condition: service_healthy # Full PostgreSQL config, HTTPS via tunnel cloudflared: image: cloudflare/cloudflared:latest command: tunnel run # Token from .env file services: postgres: image: postgres:18-alpine # Health check ensures n8n waits for DB n8n: image: n8nio/n8n:latest depends_on: postgres: condition: service_healthy # Full PostgreSQL config, HTTPS via tunnel cloudflared: image: cloudflare/cloudflared:latest command: tunnel run # Token from .env file # Clone and install cd infra && npm install # Create .env from the template (in the project root) cp ../.env.example ../.env # Edit .env — fill in your API tokens, domain, and Cloudflare IDs # SSH key is auto-detected from ~/.ssh/ (no config needed) # Load env vars and set secrets set -a && source ../.env && set +a pulumi config set --secret postgresPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nBasicAuthUser "admin" pulumi config set --secret n8nBasicAuthPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nEncryptionKey "$(openssl rand -hex 32)" # Deploy everything pulumi up # Clone and install cd infra && npm install # Create .env from the template (in the project root) cp ../.env.example ../.env # Edit .env — fill in your API tokens, domain, and Cloudflare IDs # SSH key is auto-detected from ~/.ssh/ (no config needed) # Load env vars and set secrets set -a && source ../.env && set +a pulumi config set --secret postgresPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nBasicAuthUser "admin" pulumi config set --secret n8nBasicAuthPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nEncryptionKey "$(openssl rand -hex 32)" # Deploy everything pulumi up # Clone and install cd infra && npm install # Create .env from the template (in the project root) cp ../.env.example ../.env # Edit .env — fill in your API tokens, domain, and Cloudflare IDs # SSH key is auto-detected from ~/.ssh/ (no config needed) # Load env vars and set secrets set -a && source ../.env && set +a pulumi config set --secret postgresPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nBasicAuthUser "admin" pulumi config set --secret n8nBasicAuthPassword "$(openssl rand -hex 16)" pulumi config set --secret n8nEncryptionKey "$(openssl rand -hex 32)" # Deploy everything pulumi up pulumi destroy pulumi destroy pulumi destroy - A $3/month UpCloud server (1 CPU, 1 GB RAM, 10 GB storage, Ubuntu 24.04) - A Cloudflare Tunnel — zero-trust, outbound-only connection. No open ports. - Automatic HTTPS and DDoS protection via Cloudflare's edge - PostgreSQL 18 for concurrent workflow execution - n8n running behind the tunnel, accessible at your custom domain - DNS records pointed at the tunnel automatically - Pulumi — Infrastructure as Code. Provisions everything. - UpCloud — $3/month compute (Frankfurt, DE). - Cloudflare — Zero-trust tunnel + HTTPS + DDoS protection. - Docker — Container runtime via Docker Compose. - n8n — Workflow automation engine. - PostgreSQL 18 — Production database for concurrent execution. - Installs Docker Engine - Writes docker-compose.yml with all secrets baked in - Writes the tunnel token to .env - Runs docker compose up -d - An UpCloud account (API credentials) - A Cloudflare account with a domain (API token with Zone:DNS:Edit + Account:Tunnels:Edit) - Pulumi CLI installed - Node.js 18+