This essay is the "watch this" tour of
asd. Eighteen specific moments where asd does something other tools don't, three stories that tie them together, and the hooks for talking about them. Written for readers who already know roughly what asd is and want to know why it's interesting.
asd.host's own taglines hit hard, and they're the right anchors for
this essay:
Three feelings they're pointing at:
The rest of this essay is the operational wow moments that make
those taglines land — the "watch this" beats.
curl -fsSL https://asd.host/install.sh | bash drops ~100 MB on
your machine. That single binary contains:
No apt install. No npm install -g. No
docker pull caddy && docker pull tailscale && docker pull mitmproxy.
One file. Linux / macOS / Windows / Termux / WSL all from the same
binary family with platform-specific builds.
The wow moment: install on a fresh laptop in under a minute,
already have a reverse proxy + tunnel client + web editor + web
terminal + network inspector + database GUI ready.
Hook: "100 MB. One binary. Everything you used to need apt for."
asd expose 3000 — public HTTPS in five secondsThe shortest path from localhost:3000 to a real https:// URL
anyone can hit:
asd expose 3000
✓ Exposed http://localhost:3000 → https://3000-xyz1.eu2.tn.example.com
No manifest. No asd.yaml. No login dance after asd login was
done once. Basic auth on by default so you don't accidentally
publish the world. Subdomain customisable with one positional arg.
The wow moment: the gap between "I have a Python http.server
running" and "my colleague in another country sees it in their
browser" is one command.
Hook: "asd expose 3000 is the shortest path from localhost
to the internet ever invented."
asd net apply reads every manifest, compares against the live
state (registry + Caddy + tunnel daemons), applies the diff. Run
it twice → second run is a no-op. Run it after a half-failed first
run → converges. Run it from CI → safe to retry on flake.
There's no "did this apply succeed or did I half-apply" question
to ask. The next apply tells you.
The wow moment: CI pipelines don't need "have I already
applied this?" guards. Idempotency is the design, not a bolted-on
afterthought.
Hook: "Apply twice and the second time is a no-op. Apply
after a flake and it converges. CI doesn't need a state machine."
asd treats Caddy as a stateful daemon. asd caddy stop snapshots
the live config to caddy-state.json before sending SIGINT.
asd caddy start restores from that file. Net effect: route
state survives across stops, and is never dropped without an
explicit stop.
The pathological case — changing ASD_CADDY_ADMIN_PORT mid-flight —
goes through stop → edit .env → start → net apply, never through
SIGKILL.
The wow moment: in five years of running asd, "every service
went down because someone restarted Caddy wrong" has not happened.
Hook: "Caddy doesn't die during config changes. By design."
15 services with tunnel.public: true to the same tunnel server →
one outbound SSH connection. The tunnel server demultiplexes by
subdomain.
Not one-process-per-service. Not one-SSH-per-service. One SSH per
destination, all tunnels riding it.
The wow moment: scaling to a dozen public URLs costs you no
extra connections, no extra firewall punches, no extra
authentication round-trips.
Hook: "15 public URLs, 1 SSH connection. That's the whole
exposure surface."
The manifest:
host: "my-app-${{ macro.tunnelClientId() }}.${{ macro.tunnelEndpoint() }}"
resolves when you run asd net apply, with the current .env,
the current credentials, the current tunnel-server FQDN. Edit
.env → re-apply → new values flow through every reference.
Each developer with the same manifest sees a different subdomain
(their own tunnelClientId). The manifest is portable; the
expansion is local.
The wow moment: the manifest doesn't change between dev and
prod, but the URLs do. No if env == "prod" branches, no per-env
templates. The macros do the work.
Hook: "The same YAML produces different URLs on every machine
that runs it — without conditional logic."
tunnel.public: false + hostRoute.host: my-app.localhost →
Caddy mints a cert from its internal CA, serves HTTPS, and you
trust the root cert once per machine. No certbot, no Let's Encrypt
rate limits, no public DNS.
The wow moment: "my dev environment has HTTPS" without any of
the things that normally make it painful.
Hook: ".localhost + Caddy + asd = HTTPS on dev that just
works. No certbot."
${{ env.X }} and ${{ macro.Y() }} form a small, composable
language inside YAML:
default("env:X", "fallback") — value or fallbackcoalesce("env:A", "env:B", ...) — first non-empty winsternary("env:X", "ifSet", "ifEmpty")replace("env:X", "search", "replace")concat("env:X", "literal", ...) — join with dash, skip emptiesjoinPath("env:DIR", "literal") — OS-aware path joininggetRandomPort(min, max) — port allocationgetRandomString(length, charset) — secret generationhostname("env:URL") / origin("env:URL") — URL parsinglower / upper / urlEncodePlus tunnel-specific: tunnelClientId, tunnelEndpoint,
exposedOrigin, exposedOriginWithAuth, internalUrl.
That's ~32 macros expanding inside YAML, all documented in
asd macro. The result feels like a tiny config DSL rather than
"templated YAML".
The wow moment: declaring a port range, a callback URL, a
secret, and an OAuth issuer URL — all without leaving the
manifest. No separate init.sh to compute things first.
Hook: "Declarative YAML with a small functional language
inside it. 32 macros, ~zero procedural escapes needed."
Adding a service to the project = dropping
packages/<svc>/net.manifest.yaml into the tree. asd net apply
walks packages/* and picks it up. No CLI asd service add
step, no global registry to update. The filesystem is the
registration mechanism.
The wow moment: the difference between "we have one service"
and "we have ten" is nine files, not nine config edits.
Hook: "Add a service by dropping a file. asd notices."
.asd/workspace/ is per-project, gitignored, but portable.
asd data push snapshots it to the asd org registry; asd data pull reconstitutes it on another machine. You move from your
laptop to a fresh dev VM by pulling the workspace.
Combined with manifests in git, "set up the project on a new
machine" becomes git clone && asd up && asd data pull — code
from git, runtime state from the snapshot.
The wow moment: dev environments become transferable the way
Docker volumes never quite are.
Hook: "Move your dev environment to a fresh laptop by pulling
the workspace. Not by re-running setup scripts."
asd (interactive shell) → opens the services TUI with keyboard
navigation. CI=true asd → emits the same data as a plain text
table. Same command, two surfaces, no flag forest.
asd actions → fuzzy-search command palette. asd docs →
in-terminal documentation browser.
The wow moment: the CLI feels modern (TUI when you can use
one) without sacrificing scriptability (CI=true is the universal
opt-out).
Hook: "Interactive when you want it, scriptable when you
don't. One env-var flip."
Every common dev-time tool has a one-command launcher:
asd code start — code-server (VS Code in the browser).asd database start — DbGate (database GUI).asd terminal start — ttyd (web terminal).asd inspect start — mitmproxy (network inspector).asd ttyd start — alternate web terminal.Each one gets its own subdomain through the same Caddy + tunnel
pipeline. SSH into a server, run asd code start, open the URL in
your laptop's browser → you're editing in VS Code on a server you
never had to set up.
The wow moment: the tools you used to install separately on
every dev machine are now subcommands of the one binary that's
already on every dev machine.
Hook: "VS Code in the browser, on any server, in one
command. Same for the DB, terminal, and network inspector."
tpl.env per-mode keysasd.yaml's project.env_modes.available: [dev, prod] + tpl.env
with _mode_dev_PORT=3000 / _mode_prod_PORT=8000 + asd mode set <name> + asd env apply = the same project produces a
different .env.<mode> per environment. Manifests read PORT
through ${{ env.PORT }} and get the right value automatically.
No per-env manifest forks. No environment-aware conditional logic.
One mode-switch verb flips the whole machine.
The wow moment: flipping from "running as dev" to "running as
staging" is one command and zero file edits.
Hook: "asd mode set prod && asd net apply is your entire
environment-switch ceremony."
asd vault set <key> stores a secret encrypted locally. asd vault inject <template> substitutes asd://<key> references in
config files. asd vault run <env-template> -- <command> runs a
command with secrets injected from an env template (secrets live
in the env of one process and disappear when it exits).
No HashiCorp Vault dependency. No direnv hack. Local secret
storage that integrates with asd's other commands.
The wow moment: "where do my secrets live?" has an in-house
answer that's encrypted and one binary away.
Hook: "Secrets management ships in the binary. asd vault set, asd vault inject, done."
asd doctor — one-command diagnosisSystem-wide health probes: Docker daemon, Caddy admin
reachability, restarting containers, stale compose-project clones,
env drift. Each probe shows ✅ / ⚠️ / ❌ with a one-line summary
and (on failure) detail lines that often quote the fix command.
Run it the moment something is off. Walk the red lines top to
bottom. Most fixes are one liners.
The wow moment: the diagnostic tool tells you what to fix, not
just what's broken.
Hook: "asd doctor returns ❌? Walk the lines. Each one
quotes the command that fixes it."
asd's tunnels speak the sish protocol (reverse SSH). SSH is the
most permissive outbound protocol there is — works behind any
NAT, corporate firewall, captive portal, hotel wifi. No new
listener on your machine, no inbound port forwarding, no
WireGuard kernel module to install.
The wow moment: the wifi at this conference is hostile to
everything. asd just works.
Hook: "If your terminal can SSH out, asd can give you a
public URL."
asd update pulls the latest CLI from
github.com/asd-engineering/asd-cli/releases. The release surface
is multi-platform (darwin-arm64, darwin-x64, linux-arm64, linux-x64,
linux-x64-baseline, termux-arm64, windows-x64) plus install
scripts plus SHA256SUMS.
The tunnel server (sish-compatible) is also self-hostable. If you
don't want to use asd-managed tunnel servers, point
ASD_TUNNEL_HOST at your own. The CLI doesn't care.
The wow moment: there's no lock-in tier. You can run the whole
asd pipeline on your own infrastructure — tunnel server included.
Hook: "Self-host the binary. Self-host the tunnel server.
asd is yours all the way down."
asd schemaasd schema prints the JSON / TypeScript-like schema for the
entire asd.yaml + net.manifest.yaml surface. The contract
between user manifests and asd's parser is readable, not buried
in the source.
Combined with config.validate, you get a manifest-validation
loop that fails loudly and early.
The wow moment: "what fields can I put in my manifest?" has a
one-command answer, and the answer is the actual source of truth.
Hook: "asd schema is the contract. Read it, write the YAML
to match, never guess."
The categorical magic above clusters into three stories:
asd expose <port> (B) + Caddy's internal CA for local-only (G) +
SSH all-the-way-down (P) + 100MB one binary (A).
This is the devrel / Hacker News angle. Single tagline:
"Curl install. Expose any port. HTTPS. Five seconds."
Audience: solo devs, indie hackers, conference speakers, anyone
demoing things, anyone working from a coffee shop.
Idempotent apply (C) + Caddy-stays-alive (D) + recursive
discovery (I) + macros at apply time (F) + mode system (M).
This is the DevOps / platform-engineer angle. Single tagline:
"Edit the YAML. Run one verb. The world matches. Always."
Audience: DevOps, platform engineers, teams managing dev
environments for others, anyone whose Mondays got wrecked by
"the tunnel is broken again".
Dev tools as commands (L) + tunnels multiplex (E) + workspace
portability (J) + vault built-in (N) + TUI/headless same surface
(K).
This is the modern dev experience angle. Single tagline:
"The whole dev toolkit, in one binary, one URL space, one auth."
Audience: full-stack devs, dev-experience engineers, anyone who's
tired of remembering 12 separate install paths.
(For the in-depth persona breakdown — 25 distinct readers and
the specific pain each one feels — see Why people start using
asd. This matrix below is the which magic
shortcut for the same readership.)
| Persona | First magic that lands |
|---|---|
| Solo dev / indie hacker | A (one binary), B (expose) |
| Frontend dev with backend buddy | B (expose), G (local HTTPS) |
| Backend dev | B (expose), L (inspect for live debugging) |
| Full-stack dev | I (discovery), M (mode), L (dev tools) |
| DevOps engineer | C (idempotent), D (Caddy-alive), O (doctor) |
| Tech lead | I (discovery), J (workspace portability), M (mode) |
| CTO / VP Eng | Q (self-host), A (one binary), J (portability) |
| QA | B (expose), L (inspect) |
| PM | B (expose) — for "click this URL" demos |
| Designer | B (expose), L (code-server for reviewing live) |
| Sales engineer | B (expose), N (vault for customer data) |
| Support engineer | B (expose), L (inspect for reproducing customer bugs) |
| Startup founder (technical) | A (curl install), Q (self-host) |
| Open-source maintainer | C (idempotent), I (discovery), R (schema) |
| Bootcamp student | A (one binary), O (doctor) |
| Freelancer / consultant | M (mode for client-per-mode), J (workspace push/pull) |
| Educator | A (curl install on student laptops), L (dev tools bundled) |
| Conference speaker | B (expose), P (SSH-all-the-way-down for hostile wifi) |
| Security engineer | Q (self-host), N (vault), R (schema in CI) |
| Compliance officer | Q (self-host the tunnel server), J (data local) |
| AI agent operator | B (expose), R (schema for agent-callable contracts) |
| Indie SaaS founder | E (multiplex), M (mode) |
| Self-hoster / homelab | A (one binary), G (local HTTPS), Q (self-host tunnel) |
asd expose 3000 is the shortest path from localhost to the internet ever invented.".localhost + Caddy + asd = HTTPS on dev that just works. No certbot."asd mode set prod && asd net apply is your entire environment-switch ceremony."asd doctor returns ❌? Walk the lines. Each one quotes the command that fixes it."asd schema is the contract."Most magic moments can be approximated by stitching tools together.
A few are genuinely asd-only as far as we can tell in 2026:
One binary that bundles reverse-proxy + tunnel client + four
bundled dev tools, all sharing one URL/auth space. ngrok +
Caddy + code-server + DbGate + mitmproxy = five separate
installs and configs in any other stack.
Caddy-state preservation across stop/start. Every other
project that wraps Caddy seems to forget that the admin API has
live state. asd snapshots it before SIGINT, restores it after
start.
Idempotent declarative apply for tunnels. ngrok, cloudflared,
even traditional reverse proxies don't have an "apply this YAML
and converge" verb that handles tunnels + routes + registry
atomically.
Apply-time template macros with a small functional DSL.
Helm has templates; they're verbose and have their own syntax.
docker-compose has env interpolation; it's flat. asd's macro
system is composable inside YAML — concat, coalesce,
default, internalUrl — without leaving the file.
Workspace portability via asd data push/pull. Move dev
environments between machines the way you move git branches.
Nobody else does this for dev-time state.
Multi-mode env from one tpl.env. Most projects fork
.env per environment and edit by hand. asd's _mode_<n>_KEY
prefix + asd env apply is a small but real innovation.
Together these make asd qualitatively different from "I run
Caddy plus a tunnel client by hand".
The asd.host tagline is "Secure webservice exposure for humans
and AI." What actually backs that up:
asd schema — the manifest contract is dumpable in a form anasd help — 229 commands, structured one-line descriptions,--json everywhere — most commands emit structured outputasd actions — an interactive command palette that can alsoasd login key — headless auth for CI / agent contexts.asd expose --auth=none + asd expose <port> — five secondsThe AI agent isn't simulating a human at a terminal; it's a
first-class operator with structured access to the same surface.
This is rare and worth saying out loud.
Hook: "asd was designed for two operators: humans and AI. The
CLI exposes the same surface to both."
asd and PMA together tell a layered story:
| Layer | asd's magic | PMA's magic |
|---|---|---|
| One binary | Yes — that's asd's identity. | Built on the asd binary. |
| Manifest-driven | YAML + macros for routing. | YAML + manifests for services, SSO, backups. |
| AI-friendly | Schema, --json, expose for agent callbacks. | MCP gateway with per-service tool surface. |
| Self-host all the way down | Tunnel server self-hostable. | Whole stack self-hosted. |
| Audience | Anyone serving HTTP from a laptop. | Anyone running a multi-service business stack. |
The under-told joint story: asd is the tool you reach for first.
PMA is what you build with it when the stakes get higher. asd
buys the developer; PMA buys the company.