Your server works.
Just not anywhere
you actually need it.

Your phone. Your teammate's laptop. app.yourdomain.com — not a URL that resets.

Run it privately for your team — or flip one flag and make it public. Same domain either way.

dynamoip is an open-source npm tool for macOS and Linux. It gives your local services real domain names with trusted HTTPS — accessible on your LAN or exposed to the public internet via Cloudflare Tunnel. No port forwarding. No firewall rules. Configure once, one command every time. Three modes: Quick (mDNS .local), Pro (Cloudflare DNS + Let's Encrypt, LAN only), Max (Cloudflare Tunnel, public internet).

$ npm install --save-dev dynamoip
LAN or internetsame URL alwaysno port forwardingno inbound ports0 request limitsMIT licensemacOS + Linuxopen source

/01

Who it's for.

Developers

YOUR PHONE ISN'T localhost.

Camera permissions, PWA install prompts, touch events, geolocation — none of it surfaces the same way on localhost. You need a real HTTPS URL on a real device. Configure once. Test on your phone every time with the same URL.

"Camera API doesn't work over HTTP. Neither does your PWA install prompt."
Teams

STOP SENDING IP ADDRESSES.

192.168.1.105:3000 is not a URL. dynamoip gives everyone on the network a real link — LAN only, no traffic leaving your office, no port forwarding, no VPN. Set it up once. Share the link every standup.

"What's your local IP again?" — every standup, forever.
Home lab

YOUR OWN SERVER SHOULDN'T FEEL SKETCHY.

Home Assistant, Grafana, Plex, Jellyfin — give them all real subdomains with trusted HTTPS. Bookmark them. Open from your phone. Configure once, trusted everywhere on your network. No more clicking through browser security warnings.

"Your connection is not private." — every browser, about your own server.
Remote access

SHARE YOUR LOCAL APP WITH ANYONE.

Webhook endpoint in dev? Client demo from your laptop? Staging environment you want to share? Max mode gives you a real, persistent URL — not a random ngrok subdomain that expires and breaks every webhook you registered.

"Can you send me a link?" — yes, now you can.

/02

How it works.

Configure once. Works every time after that.

01 —

Install

One command adds dynamoip as a dev dependency. It never runs in production — it's a tool for you, not your users.

npm i -D dynamoip

02 —

Configure

Drop a config JSON, add env vars, add an npm script. Five minutes — once. You never touch it again. Add "tunnel": true to go public.

03 —

Run

npm run dev:proxy. Your domains go live instantly — on your LAN or the internet. Same command, same domain, every time.

npm run dev:proxy

/03

Three modes.

Local dev to public internet — same tool, same domain.

No tunnels on LAN. Quick mode and Pro mode are pure local — your traffic never leaves your network. Tunnels only kick in when you go public with Max mode.
Max modeNew

Cloudflare
Tunnel

Your services on the public internet. No port forwarding. No firewall rules. No inbound ports. cloudflared installs itself. Works from anywhere.

dynamoip.config.json
{
  "baseDomain": "acme.dev",
  "tunnel": true,
  "domains": { "app": 3000 }
}
cloudflared installed automatically
app.acme.dev PUBLIC
No sudo required
No port forwarding
Requires: Cloudflare domain + API token
Pro mode

Cloudflare
+ Let's Encrypt

Uses your real domain. Sets Cloudflare DNS A records and issues a wildcard cert. Every device on your LAN trusts it — no CA install on phones or tablets. LAN only. No tunnel.

dynamoip.config.json
{
  "baseDomain": "acme.dev",
  "domains": { "app": 3000 }
}
app.acme.dev LAN

Requires: Cloudflare domain + API token

Quick mode

mDNS
.local

No domain needed. Broadcasts .local hostnames on your LAN via mDNS with a self-signed cert from mkcert. Zero accounts. Zero Cloudflare. LAN only. No tunnel.

dynamoip.config.json
{
  "domains": { "app": 3000 }
}
https://app.local

Requires: mkcert, sudo

The key insight: your domain stays the same across modes. app.yourdomain.com works in Pro (LAN) and Max (internet) — same URL, no reconfiguring anything. Unlike ngrok, your URLs never reset.

/04

Quick start.

Configure once. One command every time after that.

— one-time setup · ~5 min —

01
Install

One command. Never runs in production.

npm install --save-dev dynamoip
02
Create dynamoip.config.json

Pick your mode. Expand to see the config.

Max mode — public internetNo sudo
{
  "baseDomain": "yourdomain.com",
  "tunnel": true,
  "domains": {
    "app": 3000,
    "api": 4000
  }
}

Requires: Cloudflare API token with Zone:DNS:Edit + Account:Cloudflare Tunnel:Edit

Pro mode — LAN, your domain
{
  "baseDomain": "yourdomain.com",
  "domains": {
    "app": 3000,
    "api": 4000
  }
}

Requires: Cloudflare API token with Zone:DNS:Edit. No tunnel — traffic stays on your LAN.

Quick mode — LAN, .local, zero accounts
{
  "domains": {
    "app": 3000
  }
}

No Cloudflare. No domain. Just mkcert + mDNS. Domains become app.local — LAN only, no tunnel.

03
Add script to package.json

One line. You run this command forever after.

"scripts": {
  "dev:proxy": "dynamoip --config dynamoip.config.json"
}
04
Add credentials to .env

For Pro and Max modes. Not needed for Quick mode.

CF_API_TOKEN=your_cloudflare_api_token
CF_EMAIL=you@example.com   # Pro mode only

— then, every time —

Run (Max mode — no sudo)
npm run dev:proxy

Domains go live in seconds. Same domain. Every time. No reconfiguring.

Pro / Quick modes: use sudo npm run dev:proxy(ports 80/443 need root — Max mode doesn't)

/05

Examples.

Runnable Docker projects. Clone, run one command, done.

Docker · Max mode

Tunnel

Public internet

Two services exposed to the public internet via Cloudflare Tunnel. No port forwarding. Works on macOS, Linux, Windows.

inventory.yourdomain.comPUBLIC:3001
dashboard.yourdomain.comPUBLIC:6000
Docker · Pro mode

LAN

LAN only · no tunnel

Two services on your LAN with your real domain and Let's Encrypt cert. No tunnel — traffic stays on your network.

inventory.yourdomain.comLAN:3001
dashboard.yourdomain.comLAN:6000
Docker · Quick mode

Local

.local · zero accounts

No Cloudflare account needed. mDNS .local hostnames + mkcert. The simplest possible setup.

inventory.local:3001
dashboard.local:6000