Skip to content

Cloud Migration Alternatives

This document covers alternative approaches to external app access beyond full k8s cluster migration, including single-app hosting, DMZ VLAN isolation, and hybrid architectures.

Related: See Cloud Migration Evaluation for full cluster migration options.


Table of Contents


When to Use These Alternatives

Use single-app hosting when: - Only need 1-2 apps externally accessible - Want to minimize cost (potentially $0-10/month) - Don't need full k8s cluster features - Testing external access before committing to infrastructure

Use DMZ VLAN when: - Comfortable with moderate network complexity - Want to keep apps running on local hardware - Need isolation between external-facing and internal services - Already have VPN for admin access to main network

Use hybrid architectures when: - Want static frontend performance (CDN) - Backend can stay local or needs minimal cloud resources - Separating concerns (frontend vs API)


Option 1: Ultra-Cheap Single App Hosting

For hosting a lightweight Next.js app with database access, managed platforms offer excellent free/cheap tiers.

1A: Vercel (Free Tier)

What It Is: - Vercel (creators of Next.js) - optimized for Next.js apps - Generous free tier for hobby projects

Free Tier Limits: - 100GB bandwidth/month - Unlimited deployments - Unlimited sites - Serverless function execution limits (100GB-hours/month) - No credit card required

Pricing: - Hobby (Free): $0/month - suitable for personal projects - Pro: $20/month - removes limits, adds team features

Pros: - Zero configuration for Next.js (git push = deploy) - Automatic HTTPS, CDN, preview deployments - Best Next.js performance (built by same team) - Can connect to external PostgreSQL (Supabase Cloud, etc.) - Free tier is genuinely generous

Cons: - Vendor lock-in to Vercel platform - Serverless model (not traditional VPS) - Can't run custom services (only Next.js/static) - Commercial use requires Pro tier

Database Options: - Vercel Postgres: $20/month (512MB storage) - Supabase Cloud: $0 free tier, $25 Pro - Neon: Free tier (0.5GB storage), scales up - PlanetScale: Free tier (5GB storage, 1 billion reads)

Best For: - Lightweight dashboard/portal app - Prototyping external access - Apps that fit serverless model

Setup Time: 15-30 minutes (connect GitHub, configure env vars)

Total Monthly Cost: - Free tier: $0 (app) + $0-25 (database) = $0-25/month - Pro tier: $20 (app) + $0-25 (database) = $20-45/month


1B: Railway (Developer-Friendly)

What It Is: - Modern platform-as-a-service (PaaS) - Docker-based deployments - Free $5/month credit (no credit card), then pay-as-you-go

Pricing: - Developer Plan: Free $5 credit/month - Enough for small app (1 web service + small database) - Additional usage: $0.000231/GB-hour RAM (~$10/mo for 2GB 24/7) - Pro: $20/month minimum for team features

Pros: - Flexible (not just Next.js - any Docker container) - Can run PostgreSQL in same platform - Simple git-push deployment - Generous free tier for experimentation - Easy environment variable management

Cons: - Free credit runs out fast for 24/7 services - More expensive than dedicated VPS for high uptime - Less Next.js-specific optimization than Vercel

Database Options: - Railway PostgreSQL: Included (uses same credit/billing) - Supabase Cloud: External connection

Best For: - Full-stack apps needing database - Docker-based workflows - Experimenting before committing to infrastructure

Setup Time: 30-60 minutes (GitHub connection, configure build)

Total Monthly Cost: - Light usage: $0-5/month (within free credit) - Typical Next.js app + small DB: $10-15/month - Production app + database: $20-30/month


1C: Fly.io (Global Edge Platform)

What It Is: - Docker container platform - Run apps globally on edge network - Free tier with credit card

Free Tier Allowances: - 3x shared-cpu-1x VMs (256MB RAM each) - 3GB persistent storage - 160GB outbound bandwidth/month

Pricing Beyond Free Tier: - shared-cpu-1x (256MB): $1.94/month - shared-cpu-1x (1GB RAM): $5.70/month - Persistent storage: $0.15/GB-month

Pros: - True VMs (not just serverless) - Run any Docker container - Global deployment (low latency worldwide) - Can run PostgreSQL alongside app - Free tier covers small production apps

Cons: - Requires credit card for free tier - More manual configuration than Vercel - Smaller community than AWS/DO

Database Options: - Fly Postgres: Free tier covers small DB (shared VM) - Supabase Cloud: External connection

Best For: - Apps needing consistent uptime (VM-based) - Global users (edge deployment) - Learning Docker deployment

Setup Time: 1-2 hours (Dockerfile, fly.toml config)

Total Monthly Cost: - Free tier (1 app + small DB): $0/month - Small production: $5-10/month - Larger instances: $15-25/month


1D: Cloudflare Pages + Workers (Serverless)

What It Is: - Static site hosting (Cloudflare Pages) - Serverless functions (Cloudflare Workers) - Next.js supported via adapter

Free Tier: - Unlimited sites - Unlimited requests - Unlimited bandwidth - 100,000 Worker requests/day

Pricing: - Free: $0/month (very generous limits) - Workers Paid: $5/month (10M requests, then $0.50/1M)

Pros: - Extremely generous free tier - Cloudflare's global CDN (excellent performance) - Simple git-based deployment - No credit card for free tier

Cons: - Serverless architecture (not traditional server) - Limited to 10ms CPU time per request (Worker limits) - Database must be external (no bundled option) - Next.js support requires adaptation (not native)

Database Options: - Cloudflare D1: Free tier (5M reads/day) - SQLite-based - Supabase Cloud: External PostgreSQL - Neon: Serverless PostgreSQL

Best For: - Mostly static sites with light dynamic features - Global CDN performance priority - Absolute minimal cost

Setup Time: 1-2 hours (configure adapter, deploy)

Total Monthly Cost: - Free tier: $0 (site) + $0-25 (database) = $0-25/month


1E: Render (Vercel Alternative)

What It Is: - PaaS similar to Railway/Vercel - Free tier for web services

Free Tier: - Static sites: Unlimited, always free - Web services: Free (spins down after inactivity) - PostgreSQL: Free (90 days, then $7/month)

Pricing: - Free Static: $0/month - Web Service (Starter): $7/month (512MB RAM, always-on) - PostgreSQL: $7/month (1GB storage, 1GB RAM)

Pros: - True free tier for static sites - Simple deployment (git-based) - Can run full Next.js (not just static export)

Cons: - Free web services sleep after inactivity (slow cold starts) - PostgreSQL not free long-term - Less Next.js optimization than Vercel

Best For: - Static sites (free forever) - Apps okay with cold starts - Lower cost than Vercel Pro

Setup Time: 30-60 minutes

Total Monthly Cost: - Static only: $0/month - Always-on app + DB: $14/month


Option 2: DMZ VLAN with Cloudflare Tunnel

This approach creates an isolated network segment (DMZ) that can be safely exposed to the internet while protecting your main network.

Architecture Overview

                    Internet
              Cloudflare Tunnel
              ┌────────────────┐
              │   DMZ VLAN     │  (e.g., 10.89.98.0/24)
              │                │
              │  LXC Container │  Runs cloudflared + apps
              │  (External)    │
              └────────────────┘
                  Firewall Rules
                 (DROP all to main)
              ┌────────────────┐
              │  Main Network  │  (10.89.97.0/24)
              │                │
              │  • Supabase    │  Can't be reached from DMZ
              │  • Media Stack │
              │  • NAS Storage │
              └────────────────┘
                  VPN Required

Security Model: - DMZ VLAN has NO route to main network - Firewall explicitly drops DMZ → Main traffic - Cloudflare Tunnel runs in DMZ only - External users → Cloudflare → DMZ (isolated) - Main network accessible only via VPN


Implementation Steps

Phase 1: Network Planning

1. Choose VLAN ID and subnet:

# Example:
VLAN ID: 98
DMZ Subnet: 10.89.98.0/24
Gateway: 10.89.98.1 (Proxmox vmbr0.98)
Main Network: 10.89.97.0/24 (existing)

2. Document network layout: - Main network: Keep existing 10.89.97.0/24 - DMZ network: New 10.89.98.0/24 - No routing between VLANs


Phase 2: Proxmox VLAN Configuration

1. Create VLAN-aware bridge (if not already):

# On Proxmox host
# Edit /etc/network/interfaces

# Add VLAN to existing bridge
auto vmbr0.98
iface vmbr0.98 inet static
    address 10.89.98.1/24
    # No gateway - isolated network

2. Apply network configuration:

# CAREFUL: This can disrupt network connectivity
# Have console/IPMI access before running
ifreload -a

3. Verify VLAN interface:

ip addr show vmbr0.98
# Should show: 10.89.98.1/24

ping 10.89.98.1
# Should succeed


Phase 3: Firewall Rules (Critical for Isolation)

1. Create firewall rules file:

# /etc/pve/firewall/cluster.fw

[RULES]
# Allow DMZ → Internet (for updates, Cloudflare tunnel)
GROUP dmz_outbound

# Deny DMZ → Main Network
-i vmbr0.98 -o vmbr0 -s 10.89.98.0/24 -d 10.89.97.0/24 -j DROP

# Deny Main → DMZ (except from Proxmox host for management)
-i vmbr0 -o vmbr0.98 -s 10.89.97.0/24 -d 10.89.98.0/24 -j DROP
IN ACCEPT -i vmbr0 -s 10.89.97.1 -d 10.89.98.0/24 # Proxmox host can manage

# Allow established connections back
-m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

2. Enable Proxmox firewall:

# Edit /etc/pve/firewall/cluster.fw
[OPTIONS]
enable: 1

# Restart firewall
pve-firewall restart

3. Test isolation:

# From Proxmox host, try to reach main network from DMZ
# This should FAIL after firewall rules applied
pct exec <DMZ_CONTAINER_ID> -- ping 10.89.97.1
# Should timeout or be rejected

# From DMZ, internet should work
pct exec <DMZ_CONTAINER_ID> -- ping 8.8.8.8
# Should succeed


Phase 4: Create DMZ LXC Container

1. Create container in DMZ VLAN:

# Use template (Debian 12)
pct create 199 \
  local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname dmz-external \
  --cores 2 \
  --memory 2048 \
  --rootfs local-lvm:20 \
  --net0 name=eth0,bridge=vmbr0,tag=98,ip=10.89.98.10/24,gw=10.89.98.1 \
  --nameserver 1.1.1.1 \
  --unprivileged 1 \
  --features nesting=1

# Start container
pct start 199

2. Enter and configure container:

pct enter 199

# Update and install basics
apt update && apt upgrade -y
apt install -y curl git sudo

# Create user
adduser external
usermod -aG sudo external


Phase 5: Install Cloudflare Tunnel

1. Install cloudflared in DMZ container:

# Inside DMZ container (pct enter 199)

# Download cloudflared
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb

# Install
dpkg -i cloudflared.deb

# Verify
cloudflared --version

2. Authenticate with Cloudflare:

# This will open browser for login
cloudflared tunnel login

3. Create tunnel:

# Create named tunnel
cloudflared tunnel create homelab-dmz

# Note the tunnel ID and credentials file location
# Example: ~/.cloudflared/<TUNNEL_ID>.json

4. Configure tunnel routing:

# Create config file
mkdir -p ~/.cloudflared
nano ~/.cloudflared/config.yml

Example config.yml:

tunnel: <TUNNEL_ID>
credentials-file: /root/.cloudflared/<TUNNEL_ID>.json

ingress:
  # Route subdomain to local app
  - hostname: external.yourdomain.com
    service: http://localhost:3000

  # Catch-all rule (required)
  - service: http_status:404

5. Configure DNS:

# Create CNAME record pointing to tunnel
cloudflared tunnel route dns homelab-dmz external.yourdomain.com

6. Run tunnel as service:

# Install as systemd service
cloudflared service install

# Start service
systemctl start cloudflared
systemctl enable cloudflared

# Check status
systemctl status cloudflared


Phase 6: Deploy App in DMZ Container

1. Install Node.js (for Next.js app):

# Inside DMZ container
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt install -y nodejs

# Verify
node --version
npm --version

2. Deploy your app:

# Clone or copy app to DMZ container
git clone <YOUR_APP_REPO> /opt/app
cd /opt/app

# Install dependencies
npm install

# Build
npm run build

3. Configure environment variables:

# Create .env.local
nano /opt/app/.env.local

Example .env.local for isolated database:

# Option A: Supabase Cloud (recommended for DMZ)
NEXT_PUBLIC_SUPABASE_URL=https://yourproject.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-key

# Option B: External PostgreSQL
DATABASE_URL=postgresql://user:pass@external-db.com:5432/dbname

4. Run app with PM2 (process manager):

# Install PM2
npm install -g pm2

# Start app
cd /opt/app
pm2 start npm --name "external-app" -- start

# Save PM2 config
pm2 save
pm2 startup


Database Options for DMZ

Option A: Separate Supabase Cloud Project (Recommended) - Create new Supabase Cloud project for DMZ apps - Isolated from main network database - No connection between DMZ and main network - Cost: $0 (free tier) or $25/month (Pro)

Option B: Separate PostgreSQL in DMZ - Install PostgreSQL in DMZ container - Completely isolated - Manual backup management - Cost: Included in DMZ container resources

Option C: Read-Only Access to Main Network Database (Advanced) - Create read-only user in main network Supabase - Allow ONLY specific DMZ IP → Main DB on port 5432 - Requires careful firewall configuration - Security Risk: Creates connection between DMZ and main - Not Recommended: Violates isolation principle


Security Verification Checklist

After setup, verify isolation:

Test 1: DMZ cannot reach main network

# From DMZ container
pct exec 199 -- ping 10.89.97.1        # Should FAIL
pct exec 199 -- curl http://10.89.97.50  # Should TIMEOUT

Test 2: Main network cannot reach DMZ (except Proxmox)

# From a container in main network (e.g., LXC 150)
pct exec 150 -- ping 10.89.98.10       # Should FAIL

Test 3: DMZ can reach internet

# From DMZ container
pct exec 199 -- ping 8.8.8.8           # Should SUCCEED
pct exec 199 -- curl https://google.com # Should SUCCEED

Test 4: External access via Cloudflare works

# From external network (your phone, not on VPN)
curl https://external.yourdomain.com   # Should return app

Test 5: Main network requires VPN

# From external network (not on VPN)
curl http://10.89.97.50                # Should TIMEOUT

All tests must pass for proper isolation.


DMZ Pros and Cons

Pros: - Apps run on local hardware (no cloud costs) - Proper network isolation (security) - Can use Cloudflare CDN/DDoS protection - Keep data local (except what's in DMZ) - Learning experience (VLANs, firewalls)

Cons: - Complex setup (network, firewall, tunnel) - Higher maintenance (manage container, tunnel, updates) - Still exposes local hardware to internet (though isolated) - Cloudflare Tunnel required (dependency on Cloudflare) - Database duplication if using separate DB

Best For: - Learning network security concepts - Want to keep apps on local hardware - Comfortable with ongoing maintenance - Okay with Cloudflare dependency

Time Investment: - Initial setup: 4-8 hours (learning VLANs, testing isolation) - Ongoing maintenance: 1-2 hours/month (updates, monitoring)


Option 3: Hybrid Architectures

Combine cloud and local resources for optimal cost/performance.

3A: Static Frontend (Cloud) + API (Local/DMZ)

Architecture:

User → Cloudflare Pages (static Next.js) → API in DMZ/Cloud → Database

How It Works: 1. Build Next.js as static export (next export) 2. Host static files on Cloudflare Pages (free, global CDN) 3. API routes run on separate backend (DMZ or cheap VPS) 4. Frontend makes API calls to backend

Pros: - Free frontend hosting (Cloudflare Pages) - Fast global delivery (CDN) - Backend can be minimal (just API server) - Separation of concerns

Cons: - Can't use Next.js SSR (static only) - More complex deployment (two separate deploys) - CORS configuration required

Cost: - Frontend: $0 (Cloudflare Pages) - Backend: $5-10/month (Railway, Fly.io, or DMZ) - Database: $0-25/month (Supabase Cloud) - Total: $5-35/month


3B: Lightweight Gateway (Cloud) + Full App (Local)

Architecture:

User → Cloud Gateway (auth, proxy) → Cloudflare Tunnel → Local App

How It Works: 1. Small Node.js app in cloud handles authentication 2. After auth, proxies requests through Cloudflare Tunnel to local 3. Full app runs locally (keeps data local) 4. Gateway adds security layer

Pros: - Apps stay local (data locality) - Cloud gateway adds auth, rate limiting - Minimal cloud resources (tiny gateway) - Can use existing local Supabase

Cons: - Complex architecture - Latency (cloud → tunnel → local) - Requires maintaining tunnel

Cost: - Gateway: $0-5/month (Fly.io free tier or Railway) - Cloudflare Tunnel: $0 - Total: $0-5/month


3C: Edge Functions (Cloud) + Local Database (VPN)

Architecture:

User → Cloudflare Workers (serverless) → Tailscale VPN → Local Supabase

How It Works: 1. Cloudflare Workers host API logic (serverless) 2. Workers connect to local database via Tailscale 3. Database stays on local network (VPN access only)

Pros: - Minimal cloud cost (Workers free tier generous) - Database stays local - Workers globally distributed

Cons: - Complex (VPN from Workers, not officially supported) - Security concerns (VPN from cloud to local) - Latency through VPN

Cost: - Workers: $0-5/month - Tailscale: $0 (free for personal use) - Total: $0-5/month

Not Recommended: VPN from cloud to local violates your security requirement.


Option 4: Single VPS for One App

Simple, traditional approach: One VPS running your app and database.

4A: Hetzner Single VPS

Instance: CX11 (1 vCPU, 2GB RAM, 20GB SSD) Cost: €3.79/month (~$4.10)

What You Get: - Full VPS (not shared/serverless) - Root access - 20TB included traffic - Dedicated resources

Setup: 1. Create Hetzner account and VPS 2. Install Docker and Docker Compose 3. Deploy app + PostgreSQL via docker-compose 4. Configure Caddy/nginx for HTTPS

Pros: - Extremely cheap ($4/month) - Full control - Can run multiple apps if needed - Predictable performance

Cons: - Manual server management (updates, security) - Single point of failure - Need to configure HTTPS, backups - More sysadmin work

Cost Breakdown: - VPS: $4.10/month - Total: $4.10/month


4B: DigitalOcean Basic Droplet

Instance: Basic Droplet (1GB RAM, 1 vCPU, 25GB SSD) Cost: $6/month

Similar to Hetzner but: - More expensive ($6 vs $4) - Better docs/tutorials - Easier dashboard

Cost: $6/month


4C: Oracle Cloud Free Tier (Single Instance)

Instance: VM.Standard.E2.1.Micro (1 OCPU, 1GB RAM) Cost: $0 (Always Free)

What You Get: - 2x free AMD VMs OR 4x ARM VMs - 200GB total storage - Actually free forever

Pros: - Free - Can run app + small database - Permanent free tier

Cons: - ARM architecture (2GB ARM instance free) - Account approval process - Limited resources (1GB RAM tight)

Cost: $0/month


Comparison Matrix

Option Monthly Cost Complexity Database Included Local Network Exposed Setup Time
Vercel Free $0-25 Low No ($0-25 extra) No 30 min
Railway $0-15 Low Yes No 1 hour
Fly.io $0-10 Medium Yes (small) No 1-2 hours
Cloudflare Pages $0-25 Medium No ($0-25 extra) No 1-2 hours
Render $0-14 Low Yes ($7/mo) No 1 hour
DMZ VLAN + Cloudflare $0 High Separate needed No (isolated) 4-8 hours
Hybrid (Static + API) $5-35 Medium Depends Varies 2-4 hours
Hetzner VPS $4 Medium DIY No 2-3 hours
DO Droplet $6 Medium DIY No 2-3 hours
Oracle Free $0 Medium DIY No 2-4 hours

Recommendation 1: Absolute Minimal Cost

Goal: Spend $0, test external access

Setup: 1. App: Vercel Free Tier (Next.js) 2. Database: Supabase Cloud Free Tier 3. Cost: $0/month

Limitations: - Supabase pauses after 7 days inactivity - Can't use for production long-term

Use Case: Experimentation, proof-of-concept


Recommendation 2: Budget Production Single App

Goal: Reliable, cheap, single app externally accessible

Setup: 1. App: Railway or Fly.io ($5-10/month) 2. Database: Included (Railway PostgreSQL or Fly Postgres) 3. Cost: $5-10/month

Alternative: 1. App: Hetzner CX11 VPS ($4/month) 2. Database: Self-hosted PostgreSQL on same VPS 3. Setup: Docker Compose 4. Cost: $4/month

Use Case: Single production app, minimal budget


Recommendation 3: New Lightweight App (Your Use Case)

Goal: Create new app specifically for external access, full Next.js + DB

Setup: 1. App: Vercel Hobby (free) or Railway ($0-10/mo) 2. Database: Supabase Cloud Pro ($25/mo) - Shared with other apps (schema isolation) - Professional backup/management 3. Cost: $0-35/month

Workflow: 1. Create new Next.js app locally (standard skeleton) 2. Add schema to existing k8s Supabase OR use Supabase Cloud 3. Deploy to Vercel/Railway (git push) 4. Configure env vars (Supabase URL, keys) 5. Access externally via Vercel URL or custom domain

Why This Works: - Minimal infrastructure (PaaS handles it) - Keep existing local setup unchanged - New app isolated from local network - Can iterate quickly (git-based deploys)

Time to Deploy: 2-4 hours (including app creation)


Recommendation 4: DMZ Isolation (Learning Experience)

Goal: Learn VLANs/network security, keep apps local

Setup: 1. Network: DMZ VLAN (10.89.98.0/24) 2. Container: LXC in DMZ (no access to main network) 3. Tunnel: Cloudflare Tunnel for external access 4. Database: Separate Supabase Cloud project OR PostgreSQL in DMZ 5. Cost: $0 (if using free Supabase) or $25/mo (Supabase Pro)

Learning Outcomes: - VLAN configuration on Proxmox - Firewall rule management - Network isolation verification - Cloudflare Tunnel setup

Time Investment: 6-10 hours (initial setup + testing)

Best For: Want to learn networking, have time to invest


Next Steps

Based on your answers: - Want new lightweight app: Vercel/Railway (Recommendation 3) - Want to learn VLANs: DMZ approach (Recommendation 4) - Want absolute cheapest: Hetzner VPS $4/mo (Recommendation 2)

Questions to decide:

  1. Do you want to build a new app from scratch?
  2. Yes → Vercel/Railway path (2-4 hours, $0-35/month)
  3. No → Consider which existing app to migrate

  4. Want to learn network segmentation?

  5. Yes → DMZ VLAN path (6-10 hours, $0-25/month)
  6. No → Cloud PaaS path (simpler)

  7. Budget preference?

  8. $0-10/month → Vercel Free + Railway OR Hetzner VPS
  9. $25-35/month → Professional setup (Supabase Pro + Vercel/Railway)
  10. Willing to invest time for $0 → DMZ VLAN + free Supabase

  11. Time available for setup?

  12. 2-4 hours → Vercel/Railway (quick)
  13. 6-10 hours → DMZ VLAN (learning investment)

Let me know which direction interests you most and I can provide detailed step-by-step implementation!


Appendix: DMZ VLAN Commands Reference

Quick command reference for DMZ VLAN setup:

# 1. Create VLAN interface on Proxmox host
cat >> /etc/network/interfaces <<EOF
auto vmbr0.98
iface vmbr0.98 inet static
    address 10.89.98.1/24
EOF
ifreload -a

# 2. Create DMZ container
pct create 199 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \
  --hostname dmz-external --cores 2 --memory 2048 --rootfs local-lvm:20 \
  --net0 name=eth0,bridge=vmbr0,tag=98,ip=10.89.98.10/24,gw=10.89.98.1 \
  --nameserver 1.1.1.1 --unprivileged 1 --features nesting=1

# 3. Configure firewall
nano /etc/pve/firewall/cluster.fw
# Add rules to block DMZ <-> Main traffic

# 4. Test isolation
pct exec 199 -- ping 10.89.97.1    # Should FAIL
pct exec 199 -- ping 8.8.8.8       # Should SUCCEED

# 5. Install Cloudflare Tunnel
pct exec 199 -- bash
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
dpkg -i cloudflared.deb
cloudflared tunnel login
cloudflared tunnel create homelab-dmz

Firewall rule template:

# /etc/pve/firewall/cluster.fw
[OPTIONS]
enable: 1

[RULES]
# Block DMZ → Main
-i vmbr0.98 -d 10.89.97.0/24 -j DROP

# Block Main → DMZ (except Proxmox)
IN ACCEPT -i vmbr0 -s 10.89.97.1 -d 10.89.98.0/24
-i vmbr0 -d 10.89.98.0/24 -j DROP

# Allow DMZ → Internet
IN ACCEPT -i vmbr0.98 -s 10.89.98.0/24 -d 0.0.0.0/0