Five ideas. Once you've got these, every other page in this section is just detail.
asd is a small machine: you declare intent in YAML, run one command,
and a reverse proxy plus a tunnel pop into existence. The five
concepts below name the moving parts.
You write the manifests. asd net apply is the verb that reads them,
writes the registry, configures Caddy, and brings tunnels up. The
registry is the source of truth at runtime; Caddy and the tunnels are
the things that actually serve traffic.
The YAML you write. Two kinds:
asd.yaml at the project root — project-level config (name, Caddyreference/asd-yaml.packages/<service>/net.manifest.yaml — per-service routing. Oneasd to manage. Reference:reference/net-manifest.# Minimal net.manifest.yaml
endpoint:
url: "http://127.0.0.1:3000"
caddy:
hostRoute:
host: "my-app-${{ macro.tunnelClientId() }}.${{ macro.tunnelEndpoint() }}"
tunnel:
public: true
Manifests are your declared intent. They never change at runtime.
The verbs that turn intent into reality. Two layers, depending on
what you're declaring:
asd up reads the automation block in asd.yaml and runs theup task — start containers, dev servers, watchers, anything you'veasd down reverses it.asd net apply reads every net.manifest.yaml, populates thenet.manifest.yaml need$ asd up
▶ docker compose up -d
✓ Container my-app Started
$ asd net apply
✅ Apply complete: 0 seeded, 34 routes
Both are idempotent — running either twice in a row converges to a
no-op. Most of what you do day-to-day is edit a YAML, re-run the
relevant verb.
Reference: cli/starter §3.
The runtime source of truth. A JSON file that says which services
exist, what ports they bind to, what hostnames route to them, and
which tunnel processes are alive. asd writes it, asd reads it,
nothing else should touch it.
You rarely look at it directly. When you do, it's because you're
debugging — asd net discover prints what asd thinks is in the
project, which is the data that lands in the registry on the next
apply.
$ asd net discover | jq '.manifests[] | {id, host: .caddy.hostRoute.host}'
Reference: internals/registry.
Caddy mapping: hostname → upstream. A route says "requests to
my-app-xyz1.eu2.example.com go to http://127.0.0.1:3000". Caddy
is the reverse proxy that holds these routes in memory and applies
them per request.
You don't write routes directly. You declare a caddy.hostRoute in
your net.manifest.yaml; asd net apply translates that into Caddy
JSON and sends it to Caddy's admin API.
When the declarative options aren't enough, drop to
caddy.rawRoutes — the same Caddy JSON, hand-written. See
reference/net-manifest.
Caddy is stateful, and stopping it the wrong way drops every route at
once. internals/caddy-state covers
why and how to stop it safely.
Public exposure via SSH. A tunnel is an asd-managed process
that holds a reverse SSH connection to a public tunnel server
(typically a sish instance). The server takes incoming HTTPS on
<subdomain>.<tunnel-host> and forwards it down the SSH connection
to your machine, where Caddy picks it up.
You opt in per-service via tunnel.public: true in the manifest. The
tunnel daemon for that service starts at the next apply.
Without a tunnel, your routes still exist — they just only serve
traffic from the same host (or from your LAN if Caddy listens on
0.0.0.0). The tunnel is what makes the URL reachable from anywhere.
Reference: internals/tunnels.
learn/01-hello-world — five minutes from zero to a working tunnel.reference/net-manifest + reference/asd-yaml + cli/starter.internals/architecture.