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
- Business Model
- Technical Architecture
- Fly.io Implementation
- Code Implementation
- Deployment Guide
- Cost Analysis
- 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
| Tier | Price/month | Features |
|---|---|---|
| FREE | $0 | 1 tunnel, 1hr session, HTTP only, 100MB/day, aggressive branding |
| DEVELOPER | $19 | 3 tunnels, HTTPS, 8hr sessions, 10GB transfer, email support |
| PROFESSIONAL | $49 | 10 tunnels, custom domains, persistent tunnels, 100GB transfer, Docker support |
| TEAM | $149 | 50 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: >$500Technical 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
- Gateway Router - Main entry point, routes to workspace machines
- Workspace Manager - Creates/manages customer Docker environments
- DNS Manager - Handles custom domains and DNS configuration
- SSL Manager - Automatic SSL via Let's Encrypt/Cloudflare
- Billing Manager - Usage tracking and Stripe integration
- Tunnel Protocol - WebSocket-based tunneling
Fly.io Implementation
Why Fly.io is Perfect for This
- Global Edge Network: 30+ regions with automatic Anycast
- Built-in WireGuard: Secure tunneling out of the box
- Machines API: Dynamic container orchestration
- Pay-per-use: Only pay when tunnels active
- Instant SSL: Automatic certificates
- 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 = 12. 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
- Fly.io account with payment method
- Domain name (local.dev) pointed to Fly.io
- Stripe account for payments
- 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-workspacesStep 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-cacheStep 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-gatewayStep 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-gatewayStep 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.devStep 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:latestStep 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-monitoringCost 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
| Stage | Customers | Revenue | Infrastructure | Profit |
|---|---|---|---|---|
| Month 1 | 10 paid | $440 | $35 | $405 |
| Month 3 | 100 paid | $4,400 | $190 | $4,210 |
| Month 6 | 500 paid | $22,000 | $1,012 | $20,988 |
| Month 12 | 2000 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
- Session Timeout: 1 hour maximum
- Bandwidth Cap: 100MB/day hard limit
- No HTTPS: HTTP only for free
- Branding: Large banner injection
- Rate Limiting: 100 requests/minute
- No Custom Domains: Random subdomains only
Upsell Tactics
- Usage Warnings: Alert at 80% of limits
- Feature Gates: Show "Pro only" features
- Email Campaigns: Target active free users
- In-App Prompts: Upgrade suggestions
- Time-Limited Offers: 20% off first month
Enterprise Features
- SSO Integration: SAML/OAuth
- Audit Logs: Complete activity tracking
- SLA Guarantee: 99.99% uptime
- White Label: Remove all branding
- Priority Support: 24/7 dedicated team
- 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
- Developer Communities: Reddit, HN, Dev.to
- Content Marketing: Technical blog posts
- Open Source: Partial open-source strategy
- Partnerships: IDE integrations
- Affiliate Program: 30% recurring commission
Launch Strategy
- Beta Launch: 100 invited developers
- ProductHunt: Launch with special pricing
- HackerNews: "Show HN" post
- 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
- GitHub: github.com/do-dev/localdev
- Documentation: docs.do.dev/local
- Support: help@do.dev
- Contact Page: https://do.dev/contact
Last updated: November 2025