local.dev Tunnel Service - Product Requirements & Implementation Guide

Executive Summary

A profit-focused tunnel service competing with ngrok, built on Fly.io infrastructure using Node.js and open-source technologies. The service provides production-grade development environments with real DNS, custom domains, and automatic SSL.

Table of Contents

  1. Business Model
  2. Technical Architecture
  3. Fly.io Implementation
  4. Code Implementation
  5. Deployment Guide
  6. Cost Analysis
  7. Development Timeline

Business Model

Core Value Proposition

Production-grade development environments with real DNS, custom domains, and automatic SSL - not just basic tunneling.

Target Market

Professional developers, agencies, and SaaS companies needing reliable staging/preview environments.

Pricing Tiers

TierPrice/monthFeatures
FREE$01 tunnel, 1hr session, HTTP only, 100MB/day, aggressive branding
DEVELOPER$193 tunnels, HTTPS, 8hr sessions, 10GB transfer, email support
PROFESSIONAL$4910 tunnels, custom domains, persistent tunnels, 100GB transfer, Docker support
TEAM$14950 tunnels, team workspace, RBAC, 500GB transfer, API access, SSO
ENTERPRISE$499+Unlimited tunnels, white-label, dedicated infrastructure, 24/7 support

Revenue Projections

Month 1:   100 free,  10 paid = $440 MRR
Month 3:  1000 free, 100 paid = $4,400 MRR
Month 6:  5000 free, 500 paid = $22,000 MRR
Month 12: 20000 free, 2000 paid = $88,000 MRR

Targets:
- Conversion rate: 10% free-to-paid
- Average revenue per user: $44
- CAC: <$20
- LTV: >$500

Technical Architecture

High-Level Architecture on Fly.io

┌──────────────────────────────────────────────────────────────┐
│                   Global Anycast (Fly Edge)                   │
│                    *.local.dev → Fly App                      │
└────────────┬─────────────────────────────────────────────────┘

    ┌────────▼─────────────────────────────────┐
    │   Fly Gateway App (Multi-Region)          │
    │   - tunnel-gateway.fly.dev                │
    │   - Auto-scales based on connections      │
    └────────┬─────────────────────────────────┘
             │ Fly Private Network (6PN)
    ┌────────▼─────────────────────────────────┐
    │   Customer Workspace Machines             │
    │   (Dynamically Created via Machines API)  │
    │   - Isolated Docker containers            │
    │   - Per-customer DNS/Proxy                │
    │   - Auto-sleep when inactive               │
    └───────────────────────────────────────────┘

Technology Stack

  • Backend: Node.js with Express/Fastify
  • Tunneling: WebSocket with SSE fallback
  • Proxy: Node.js HTTP/HTTPS proxy with SNI routing
  • SSL: Caddy for automatic certificates
  • DNS: CoreDNS per workspace
  • Database: PostgreSQL (via Fly Postgres)
  • Cache: Redis (via Fly Redis)
  • Infrastructure: Fly.io Machines API
  • Container Runtime: Docker on Fly Machines

Key Components

  1. Gateway Router - Main entry point, routes to workspace machines
  2. Workspace Manager - Creates/manages customer Docker environments
  3. DNS Manager - Handles custom domains and DNS configuration
  4. SSL Manager - Automatic SSL via Let's Encrypt/Cloudflare
  5. Billing Manager - Usage tracking and Stripe integration
  6. Tunnel Protocol - WebSocket-based tunneling

Fly.io Implementation

Why Fly.io is Perfect for This

  1. Global Edge Network: 30+ regions with automatic Anycast
  2. Built-in WireGuard: Secure tunneling out of the box
  3. Machines API: Dynamic container orchestration
  4. Pay-per-use: Only pay when tunnels active
  5. Instant SSL: Automatic certificates
  6. 6PN: Private IPv6 networking between containers

Fly.io Specific Features

  • Auto-stop/start: Free tier machines stop after inactivity
  • Global scaling: Deploy to multiple regions instantly
  • Usage-based billing: Perfect for freemium model
  • Sub-second cold starts: Machines wake instantly

Code Implementation

1. Gateway Application (fly.toml)

app = "localdev-gateway"
primary_region = "iad"

[build]
  dockerfile = "Dockerfile.gateway"

[env]
  NODE_ENV = "production"
  PORT = "3000"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = false
  auto_start_machines = true

[[services]]
  protocol = "tcp"
  internal_port = 3000
  
  [[services.ports]]
    port = 80
    handlers = ["http"]
    
  [[services.ports]]
    port = 443
    handlers = ["tls", "http"]

[services.concurrency]
  type = "connections"
  hard_limit = 1000
  soft_limit = 800

[[regions]]
  iad = 2
  lax = 1
  lhr = 1
  sin = 1
  syd = 1

2. Workspace Manager (workspace-manager.js)

const { MachinesClient } = require('@fly/machines');

class FlyWorkspaceManager {
  constructor() {
    this.client = new MachinesClient({
      apiToken: process.env.FLY_API_TOKEN
    });
    this.appName = 'localdev-workspaces';
  }

  async createCustomerWorkspace(customer, tier) {
    const machineConfig = this.getMachineConfig(tier);
    
    const machine = await this.client.create({
      app: this.appName,
      region: customer.preferredRegion || 'iad',
      name: `workspace-${customer.id}`,
      config: {
        image: 'localdev/workspace:latest',
        
        guest: {
          cpus: machineConfig.cpus,
          memory_mb: machineConfig.memory,
          cpu_kind: tier === 'enterprise' ? 'performance' : 'shared'
        },
        
        env: {
          CUSTOMER_ID: customer.id,
          TIER: tier,
          WORKSPACE_ID: generateWorkspaceId(),
          DNS_ENABLED: String(tier !== 'free'),
          SSL_ENABLED: String(tier !== 'free'),
          CF_API_TOKEN: encrypted(customer.cfToken)
        },
        
        services: [{
          protocol: 'tcp',
          internal_port: 8080,
          auto_stop_machines: tier === 'free',
          auto_start_machines: true
        }],
        
        mounts: tier !== 'free' ? [{
          name: `workspace_${customer.id}`,
          path: '/data',
          size_gb: this.getStorageSize(tier)
        }] : [],
        
        restart: {
          policy: tier === 'free' ? 'no' : 'always'
        },
        
        schedule: tier === 'free' ? 'stop_after_1h' : null
      }
    });
    
    await this.setupWireGuardTunnel(machine, customer);
    
    if (customer.customDomain) {
      await this.setupCustomDomain(machine, customer.customDomain);
    }
    
    return {
      machineId: machine.id,
      hostname: `${machine.id}.vm.${this.appName}.internal`,
      publicUrl: `${customer.subdomain}.local.dev`,
      status: 'running',
      region: machine.region,
      ipv6: machine.private_ip
    };
  }

  getMachineConfig(tier) {
    const configs = {
      free: { cpus: 1, memory: 256, storage: 0 },
      developer: { cpus: 1, memory: 512, storage: 1 },
      professional: { cpus: 2, memory: 2048, storage: 10 },
      team: { cpus: 4, memory: 4096, storage: 50 },
      enterprise: { cpus: 8, memory: 8192, storage: 100 }
    };
    return configs[tier];
  }
}

3. Tunnel Gateway Router (gateway-router.js)

const proxy = require('http-proxy').createProxyServer();

class FlyGatewayRouter {
  constructor() {
    this.routes = new Map();
    this.flyResolver = new FlyPrivateResolver();
  }

  async handleRequest(req, res) {
    const subdomain = this.extractSubdomain(req.hostname);
    const route = await this.getRoute(subdomain);
    
    if (!route) {
      return res.status(404).send('Tunnel not found');
    }
    
    const machine = await this.getMachine(route.machineId);
    
    if (machine.state === 'stopped') {
      await this.startMachine(route.machineId);
      return res.send(this.getStartupPage(route));
    }
    
    // Free tier: inject marketing
    if (route.tier === 'free') {
      this.injectMarketingBanner(req, res);
    }
    
    // Route to workspace via Fly's private network
    const target = `http://${route.internalHost}`;
    
    proxy.web(req, res, {
      target,
      ws: true,
      headers: {
        'X-Forwarded-Host': req.hostname,
        'X-Customer-Id': route.customerId,
        'X-Tier': route.tier
      }
    });
  }

  injectMarketingBanner(req, res) {
    const originalWrite = res.write;
    res.write = function(chunk) {
      if (res.getHeader('content-type')?.includes('text/html')) {
        const banner = `
          <div style="position:fixed;top:0;width:100%;background:#ff6b35;
                      color:white;text-align:center;padding:10px;z-index:99999">
            🚀 Powered by local.dev | Upgrade for custom domains!
            <a href="https://local.dev/upgrade" 
               style="color:white;margin-left:10px">Upgrade Now →</a>
          </div>
        `;
        chunk = chunk.toString().replace('<body>', `<body>${banner}`);
      }
      originalWrite.call(this, chunk);
    };
  }
}

4. CLI Client (cli.js)

#!/usr/bin/env node
const WebSocket = require('ws');
const http = require('http');
const { program } = require('commander');

class LocalDevClient {
  constructor(options) {
    this.localPort = options.port;
    this.serverUrl = options.server || 'wss://tunnel.local.dev';
    this.apiKey = options.apiKey;
  }

  async connect() {
    console.log(`Connecting to ${this.serverUrl}...`);
    
    this.ws = new WebSocket(this.serverUrl, {
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'X-Local-Port': this.localPort
      }
    });
    
    this.ws.on('open', () => {
      console.log('Connected! Waiting for tunnel URL...');
    });
    
    this.ws.on('message', async (data) => {
      const message = JSON.parse(data);
      
      switch(message.type) {
        case 'tunnel-ready':
          console.log(`\n🚀 Tunnel ready at: ${message.url}\n`);
          break;
          
        case 'request':
          const response = await this.forwardToLocal(message);
          this.ws.send(JSON.stringify({
            type: 'response',
            id: message.id,
            ...response
          }));
          break;
          
        case 'error':
          console.error(`Error: ${message.message}`);
          if (message.code === 'LIMIT_EXCEEDED') {
            console.log('\n💡 Upgrade your plan at https://local.dev/pricing\n');
          }
          break;
      }
    });
    
    this.ws.on('close', () => {
      console.log('Tunnel closed');
      process.exit(0);
    });
  }

  async forwardToLocal(request) {
    return new Promise((resolve) => {
      const req = http.request({
        hostname: 'localhost',
        port: this.localPort,
        method: request.method,
        path: request.url,
        headers: request.headers
      }, (res) => {
        let body = '';
        res.on('data', chunk => body += chunk);
        res.on('end', () => {
          resolve({
            status: res.statusCode,
            headers: res.headers,
            body
          });
        });
      });
      
      if (request.body) req.write(request.body);
      req.end();
    });
  }
}

program
  .option('-p, --port <port>', 'Local port to tunnel', '3000')
  .option('-k, --api-key <key>', 'API key for authentication')
  .option('-s, --server <url>', 'Tunnel server URL')
  .parse(process.argv);

const client = new LocalDevClient(program.opts());
client.connect();

5. Workspace Container (Dockerfile.workspace)

FROM node:20-alpine

# Install required tools
RUN apk add --no-cache caddy wget

# Install CoreDNS
RUN wget https://github.com/coredns/coredns/releases/download/v1.11.1/coredns_1.11.1_linux_amd64.tgz && \
    tar xzf coredns_1.11.1_linux_amd64.tgz && \
    mv coredns /usr/local/bin/ && \
    rm coredns_1.11.1_linux_amd64.tgz

# Install workspace manager
COPY workspace-manager.js /app/
COPY package.json /app/
WORKDIR /app
RUN npm install

# Ports
EXPOSE 8080 53 443

CMD ["node", "workspace-manager.js"]

6. Database Schema (schema.sql)

-- Customers
CREATE TABLE customers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  tier VARCHAR(50) NOT NULL DEFAULT 'free',
  stripe_customer_id VARCHAR(255),
  api_key VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- Workspaces
CREATE TABLE workspaces (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID REFERENCES customers(id),
  machine_id VARCHAR(255) UNIQUE,
  subdomain VARCHAR(255) UNIQUE,
  region VARCHAR(50),
  status VARCHAR(50),
  created_at TIMESTAMP DEFAULT NOW()
);

-- Tunnels
CREATE TABLE tunnels (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  workspace_id UUID REFERENCES workspaces(id),
  subdomain VARCHAR(255),
  target_port INTEGER,
  created_at TIMESTAMP DEFAULT NOW(),
  closed_at TIMESTAMP
);

-- Usage tracking
CREATE TABLE usage_metrics (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID REFERENCES customers(id),
  metric_type VARCHAR(50), -- bandwidth, requests, tunnels
  value BIGINT,
  timestamp TIMESTAMP DEFAULT NOW()
);

-- Custom domains
CREATE TABLE custom_domains (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  customer_id UUID REFERENCES customers(id),
  domain VARCHAR(255) UNIQUE NOT NULL,
  verified BOOLEAN DEFAULT FALSE,
  ssl_cert_id VARCHAR(255),
  created_at TIMESTAMP DEFAULT NOW()
);

-- Indexes for performance
CREATE INDEX idx_usage_customer_time ON usage_metrics(customer_id, timestamp);
CREATE INDEX idx_tunnels_workspace ON tunnels(workspace_id);
CREATE INDEX idx_workspaces_customer ON workspaces(customer_id);

Deployment Guide

Prerequisites

  1. Fly.io account with payment method
  2. Domain name (local.dev) pointed to Fly.io
  3. Stripe account for payments
  4. Cloudflare account (optional, for custom domains)

Step 1: Initial Setup

# Install Fly CLI
curl -L https://fly.io/install.sh | sh

# Login to Fly
fly auth login

# Create applications
fly apps create localdev-gateway
fly apps create localdev-workspaces

Step 2: Database Setup

# Create PostgreSQL cluster
fly postgres create --name localdev-db --region iad
fly postgres attach --app localdev-gateway localdev-db

# Create Redis instance
fly redis create --name localdev-cache --region iad
fly redis attach --app localdev-gateway localdev-cache

Step 3: Configure Secrets

# Set environment variables
fly secrets set \
  STRIPE_SECRET_KEY=sk_live_... \
  STRIPE_WEBHOOK_SECRET=whsec_... \
  FLY_API_TOKEN=... \
  CLOUDFLARE_API_TOKEN=... \
  JWT_SECRET=... \
  --app localdev-gateway

Step 4: Deploy Gateway

# Build and deploy gateway
cd gateway
fly deploy --app localdev-gateway

# Scale globally
fly scale count 6 \
  --region iad=2,lax=1,lhr=1,sin=1,syd=1 \
  --app localdev-gateway

# Configure autoscaling
fly autoscale set min=2 max=20 --app localdev-gateway

Step 5: Configure DNS & SSL

# Add wildcard certificate
fly certs add "*.local.dev" --app localdev-gateway

# Configure DNS records at your provider
# A record: local.dev -> Fly.io IP
# CNAME: *.local.dev -> localdev-gateway.fly.dev

Step 6: Deploy Workspace Image

# Build workspace Docker image
cd workspace
docker build -t localdev/workspace:latest .

# Push to Fly registry
fly registry push localdev/workspace:latest

Step 7: Initialize Database

# Connect to database
fly postgres connect -a localdev-db

# Run schema
psql < schema.sql

# Create initial admin user
psql -c "INSERT INTO customers (email, tier, api_key) 
         VALUES ('admin@local.dev', 'enterprise', 'admin_key');"

Step 8: Launch Monitoring

# Deploy Grafana for monitoring
fly apps create localdev-monitoring
fly deploy --app localdev-monitoring -i grafana/grafana

# Set up alerts
fly scale count 1 --app localdev-monitoring

Cost Analysis

Fly.io Infrastructure Costs

Basic Setup (Month 1)

  • Gateway: 6x shared-cpu-1x @ $1.94/each = $11.64
  • PostgreSQL: shared-1x-256 = $10
  • Redis: 256MB = $10
  • Bandwidth: 100GB free, then $0.02/GB
  • Total: ~$35/month

Growth Phase (100 customers)

  • Gateway scaled: 10x shared-cpu-1x = $19.40
  • Customer machines: ~$100 (mostly stopped)
  • Database: shared-2x-1gb = $25
  • Redis: 1GB = $25
  • Bandwidth: ~1TB = $18
  • Total: ~$190/month

Scale Phase (1000 customers)

  • Gateway: 20x shared-cpu-2x = $77.60
  • Customer machines: ~$500
  • Database: dedicated-4x-8gb = $185
  • Redis cluster: 4GB = $70
  • Bandwidth: ~10TB = $180
  • Total: ~$1,012/month

Revenue vs Costs

StageCustomersRevenueInfrastructureProfit
Month 110 paid$440$35$405
Month 3100 paid$4,400$190$4,210
Month 6500 paid$22,000$1,012$20,988
Month 122000 paid$88,000$4,000$84,000

Development Timeline

Phase 1: MVP (Weeks 1-4)

Week 1-2: Core Infrastructure

  • Set up Fly.io accounts and infrastructure
  • Deploy basic gateway application
  • Implement WebSocket tunneling protocol
  • Create basic CLI client

Week 3-4: Basic Features

  • User authentication system
  • Free tier with limitations
  • Basic subdomain allocation
  • PostgreSQL integration

Phase 2: Monetization (Weeks 5-8)

Week 5-6: Payment System

  • Stripe integration
  • Tier management
  • Usage tracking
  • Billing webhooks

Week 7-8: Premium Features

  • Docker workspace creation
  • Custom domain support
  • SSL automation
  • Bandwidth/request limits

Phase 3: Polish (Weeks 9-12)

Week 9-10: Advanced Features

  • Team workspaces
  • API development
  • Request inspection UI
  • DNS management

Week 11-12: Production Ready

  • Monitoring and alerting
  • Documentation
  • Marketing site
  • Launch preparation

Monetization Strategies

Aggressive Free Tier Limitations

  1. Session Timeout: 1 hour maximum
  2. Bandwidth Cap: 100MB/day hard limit
  3. No HTTPS: HTTP only for free
  4. Branding: Large banner injection
  5. Rate Limiting: 100 requests/minute
  6. No Custom Domains: Random subdomains only

Upsell Tactics

  1. Usage Warnings: Alert at 80% of limits
  2. Feature Gates: Show "Pro only" features
  3. Email Campaigns: Target active free users
  4. In-App Prompts: Upgrade suggestions
  5. Time-Limited Offers: 20% off first month

Enterprise Features

  1. SSO Integration: SAML/OAuth
  2. Audit Logs: Complete activity tracking
  3. SLA Guarantee: 99.99% uptime
  4. White Label: Remove all branding
  5. Priority Support: 24/7 dedicated team
  6. Custom Integration: API webhooks

Security Considerations

Infrastructure Security

  • All traffic encrypted via TLS 1.3
  • WireGuard tunnels between machines
  • Isolated Docker containers per customer
  • Network segmentation via Fly's 6PN

Application Security

  • JWT-based authentication
  • Rate limiting per IP and API key
  • DDoS protection via Fly's edge
  • Input validation and sanitization
  • SQL injection prevention

Compliance

  • GDPR compliant data handling
  • SOC 2 Type II (via Fly.io)
  • PCI DSS for payment processing
  • Data encryption at rest

Open Source Components

Core Dependencies

  • express: Web framework
  • ws: WebSocket implementation
  • http-proxy: Proxy functionality
  • node-forge: SSL certificate generation
  • bull: Job queue management
  • ioredis: Redis client

Infrastructure Tools

  • Caddy: Automatic HTTPS
  • CoreDNS: DNS server
  • PostgreSQL: Primary database
  • Redis: Caching and sessions

Competitive Analysis

vs ngrok

Advantages:

  • Full DNS control
  • Docker environments
  • Better pricing for teams
  • Custom domain SSL

Disadvantages:

  • Less mature
  • Smaller network
  • Fewer integrations

vs Cloudflare Tunnels

Advantages:

  • Simpler setup
  • Better developer experience
  • More flexible pricing
  • Docker workspaces

Disadvantages:

  • Smaller network
  • No WAF features
  • Less enterprise features

vs Tailscale Funnel

Advantages:

  • No VPN required
  • Public accessibility
  • Custom domains
  • Instant setup

Disadvantages:

  • Not peer-to-peer
  • Requires internet
  • Central dependency

Marketing Strategy

Target Channels

  1. Developer Communities: Reddit, HN, Dev.to
  2. Content Marketing: Technical blog posts
  3. Open Source: Partial open-source strategy
  4. Partnerships: IDE integrations
  5. Affiliate Program: 30% recurring commission

Launch Strategy

  1. Beta Launch: 100 invited developers
  2. ProductHunt: Launch with special pricing
  3. HackerNews: "Show HN" post
  4. Dev Influencers: Free enterprise accounts

Support & Documentation

Documentation Site

  • Getting started guide
  • API documentation
  • Framework integrations
  • Video tutorials
  • FAQ section

Support Tiers

  • Free: Community forum only
  • Paid: Email support (24h response)
  • Team: Priority support (4h response)
  • Enterprise: Dedicated Slack channel

Future Enhancements

Year 1 Roadmap

  • Q1: Core features, basic monetization
  • Q2: Team features, API v1
  • Q3: Enterprise features, white-label
  • Q4: Global expansion, partnerships

Potential Features

  • GitHub/GitLab integration
  • CI/CD pipeline support
  • Kubernetes operator
  • Edge compute capabilities
  • WebAssembly support
  • Database tunneling
  • File sharing
  • Collaborative debugging

Conclusion

local.dev on Fly.io provides a superior architecture for a tunnel service with:

  • Global edge network by default
  • Pay-per-use infrastructure
  • Automatic scaling
  • Built-in security
  • Lower operational costs

The aggressive monetization strategy with strict free tier limits and compelling premium features should drive strong conversion rates and sustainable revenue growth.


Contact & Resources


Last updated: November 2025

On this page

local.dev Tunnel Service - Product Requirements & Implementation GuideExecutive SummaryTable of ContentsBusiness ModelCore Value PropositionTarget MarketPricing TiersRevenue ProjectionsTechnical ArchitectureHigh-Level Architecture on Fly.ioTechnology StackKey ComponentsFly.io ImplementationWhy Fly.io is Perfect for ThisFly.io Specific FeaturesCode Implementation1. Gateway Application (fly.toml)2. Workspace Manager (workspace-manager.js)3. Tunnel Gateway Router (gateway-router.js)4. CLI Client (cli.js)5. Workspace Container (Dockerfile.workspace)6. Database Schema (schema.sql)Deployment GuidePrerequisitesStep 1: Initial SetupStep 2: Database SetupStep 3: Configure SecretsStep 4: Deploy GatewayStep 5: Configure DNS & SSLStep 6: Deploy Workspace ImageStep 7: Initialize DatabaseStep 8: Launch MonitoringCost AnalysisFly.io Infrastructure CostsBasic Setup (Month 1)Growth Phase (100 customers)Scale Phase (1000 customers)Revenue vs CostsDevelopment TimelinePhase 1: MVP (Weeks 1-4)Week 1-2: Core InfrastructureWeek 3-4: Basic FeaturesPhase 2: Monetization (Weeks 5-8)Week 5-6: Payment SystemWeek 7-8: Premium FeaturesPhase 3: Polish (Weeks 9-12)Week 9-10: Advanced FeaturesWeek 11-12: Production ReadyMonetization StrategiesAggressive Free Tier LimitationsUpsell TacticsEnterprise FeaturesSecurity ConsiderationsInfrastructure SecurityApplication SecurityComplianceOpen Source ComponentsCore DependenciesInfrastructure ToolsCompetitive Analysisvs ngrokvs Cloudflare Tunnelsvs Tailscale FunnelMarketing StrategyTarget ChannelsLaunch StrategySupport & DocumentationDocumentation SiteSupport TiersFuture EnhancementsYear 1 RoadmapPotential FeaturesConclusionContact & Resources