Introduction
Uplink shares a local port — publicly or privately — in one command. Public URLs are as simple as ngrok. Private apps are reachable through the same URL but gated: only you and the people you share with can open them, after a one-tap emailed login. Visitors need no VPN or client install. Keep a private overlay such as Tailscale for trusted device and network access; use Uplink when a selected service needs guarded public HTTPS.
uplink login # choose a browser link or emailed code
uplink serve 3000 --open # public: prints an HTTPS URL anyone can open
uplink serve 3000 # private: same URL, only you + people you share with
uplink up # serve everything declared in ./uplink.yaml
Identity is the only centralized piece, and even that is optional when you self-host. The edges that serve your traffic run anywhere — in the cloud or on your own infrastructure — and hold no shared database.
Uplink and OpenTunnels
Uplink is the product: the uplink CLI, the desktop app, the local agent, and the
dashboards. OpenTunnels is the protocol underneath: a mutually-authenticated QUIC
transport with TLS 1.3, per-connection channel binding, and a strict
registration/stream wire format. You use Uplink; OpenTunnels carries the bytes. See
Architecture for the protocol.
How it fits together
visitor ──HTTPS──▶ edge ──OpenTunnels stream──▶ connector ──▶ localhost:3000
| Term | What it is |
|---|---|
| Connector | Your machine running uplink or the desktop app. It dials out to an edge; the edge never dials into your machine. |
Edge deployment (Caddy + uplink-edge) | The public server that owns an edge domain, terminates visitor HTTPS (serving HTTP/1.1, HTTP/2, and HTTP/3), accepts connector tunnels, and holds ephemeral in-memory routes. Edges hold no shared database. |
| Control plane | Optional. Issues identity, manages accounts and edges, and backs cross-device dashboards. It is never on the visitor request path. |
| Namespace | Assigned at login; part of every managed app host, so names never collide across accounts. |
| App name | The service label in a URL. uplink serve 3000 defaults to app3000; set it with --name or a services.<name> key in uplink.yaml. |
Install
For terminal-only machines, install the latest stable CLI on Linux (x86_64 or arm64) or Apple Silicon macOS:
curl -fsSL https://uplink.computer/install.sh | sh
This installs two binaries: uplink (the CLI) and uplink-agent (the local serving
daemon the CLI starts automatically). The installer knobs are environment variables:
UPLINK_CHANNEL picks the release channel (stable, the default, or dev),
UPLINK_MANIFEST_URL overrides the full manifest URL, and
UPLINK_INSTALL_DIR chooses the destination (default /usr/local/bin when writable,
else ~/.local/bin). It records standalone ownership at
~/.uplink/cli-install.json (or under UPLINK_STATE_DIR) so the desktop can
identify a custom install location later.
On macOS with Uplink.app, use Settings → Command line → Install instead.
That command links to the CLI and agent inside the signed app, so the desktop
updater keeps all three on exactly one build. The standalone installer detects
Uplink.app and refuses to create a competing copy unless explicitly opted in with
UPLINK_ALLOW_STANDALONE=1.
If you previously installed both forms, Settings uses that receipt plus legacy
standalone locations such as /usr/local/bin and shows the conflicting path.
Remove the old uplink and uplink-agent copies before installing the managed
command. If the receipt is malformed, Settings reports the receipt path and keeps
installation disabled until you reinstall the standalone CLI or remove that stale
receipt.
The dev channel is also reachable directly — install with
curl -fsSL https://uplink.computer/install-dev.sh | sh, and stay on it with
uplink upgrade --dev.
To build the CLI from the uplink source tree on any platform:
cargo build --release -p uplink -p uplink-agent
The operator-side edge binary is uplink-edge. See
Self-Hosting to run your own.
First app
uplink login # choose a browser link or emailed code
uplink serve 3000 --open # publishes localhost:3000, prints the public URL
uplink status # who you are + what you're serving
A served app is reachable at:
https://<name>-<device-slug><namespace>.<edge-domain>
<name> defaults to app<port> (override with --name); the rest comes from your login
profile and the edge you serve through. See
Serve a Port for the full per-part breakdown.
Share a private app
Private is the default — uplink serve 3000 (no --open) publishes the same URL,
but the edge admits only verified members of the app’s owning account and anyone you
invite explicitly. A private app owned by an organization is automatically available
to every member of that organization.
uplink status --output=json # list available account IDs
uplink serve 3000 --name demo --account acct_acme # share with an organization
uplink share demo contractor@example.com # invite someone else
uplink unshare demo contractor@example.com # revoke
Next steps
- Serve a port — the everyday flow, app URLs, and the additive access model.
- Formations — declare many services, their access and firewall settings in
uplink.yaml. - Access Control — private/identity, organization access, invitations, and bearer tokens.
- CLI Reference — every command and environment variable.
Run any command with no arguments to see its usage line. Add
--output=jsontostatus,tunnels, oredgesfor machine-readable output.