Your first org and app
A sandbox lives as long as you hold it. An app is meant to run for
months: the isb serve daemon keeps it running, health-checks it,
load-balances it, rolls out new versions without downtime and rolls them
back. This page takes a Linux host with isb installed from nothing to an app
answering on a URL, then opens the web UI. (On a Mac the daemon runs in the
isb machine; start at step 3.)
1. Prepare the host, once#
Each org gets its own bridge network. A host with a default-deny firewall
(ufw) drops DHCP, DNS and forwarding on new bridges (and on incus' own
incusbr0, where image builds run), and service names need a directory the
daemon writes. One command, as root, does all of it:
$ sudo "$(command -v isb)" host setup # --dry-run prints what it would do
See host setup for exactly what
it changes. Run it before the daemon: the daemon then makes the default org
with service names. (The other order works too: a running daemon turns service
names on for the orgs that lack them within a minute of the setup, and at
every start.)
2. Run the daemon#
$ isb serve install
$ systemctl --user status isb
isb serve install writes the systemd user unit
~/.config/systemd/user/isb.service and its settings file
~/.config/isb/serve.env, sets up the daemon's secrets key, starts the
service and waits until it answers on http://127.0.0.1:8092/healthz. The
daemon also listens on a unix socket, which the isb CLI on this host uses.
Two things it may tell you:
- Lingering is off: user services stop when you log out. Keep it running
with
loginctl enable-linger $USER. - The unit runs a versioned path (a mise install): run
isb serve installagain after upgrading isb.
The daemon's user needs the incus socket (the incus-admin group). Logs:
journalctl --user -u isb -f.
3. The first admin#
isb keeps its own users. While none exists, create the first one on the
host, as the daemon's user; it is a platform admin and the owner of the
default org:
$ isb user create you@example.com # asks for the password (at least 12 characters)
Or do it in the browser: open http://127.0.0.1:8092/setup and paste the
one-time setup token the daemon wrote to ~/.local/state/isb/setup-token
($XDG_STATE_HOME/isb/setup-token). See the first admin.
4. An org#
An org is the trust boundary: its own incus project, network, quotas and secrets. Its members administer what is in it and nothing outside it.
$ isb org create acme --cpus 4 --memory 8GiB
$ isb org show acme
Every later command takes --org acme (or set ISB_ORG=acme). Without it,
platform commands work in the default org, which always exists once the
daemon has started (the incus project isb-default). See
orgs.
5. A project and an app#
Apps live in a project's environment (production unless you add others),
and each environment runs as one stack:
$ export ISB_ORG=acme
$ isb project create shop
$ isb app create web --project shop --image docker:traefik/whoami \
--port 80 -p 127.0.0.1:8080:80 -e GREETING=hello --deploy
created app web: service web.shop-production
...
deployment 1 done
$ curl -s localhost:8080 | head -3
--deploy deploys right away and follows the deployment's log until it is
done. The image is pinned to its digest at deploy, so a moved tag never
changes a running app behind your back. -p 127.0.0.1:8080:80 publishes the
app on the host's loopback, load-balanced over its healthy replicas.
(docker: images need skopeo on the host.)
Day to day:
$ isb app ls
$ isb app env web > web.env && $EDITOR web.env && isb app env-set web web.env --deploy
$ isb app update web --replicas 2 --deploy
$ isb app deployments web
$ isb app rollback web # the last good deployment before this one
$ isb stack ps shop-production # the stack underneath: replicas, health, ports
An app from a git repository is the same with --git URL --ref main; the
daemon builds it in a fresh sandbox. See deploying apps.
6. A public URL#
To serve the app on a hostname with a certificate, the daemon runs an ingress (Caddy) on the host's 80 and 443, or an org uses its own Cloudflare Tunnel:
$ sudo "$(command -v isb)" host setup --public-ingress # open 80/443, let the daemon bind them
$ echo 'ISB_INGRESS_HTTP=:80' >> ~/.config/isb/serve.env
$ echo 'ISB_INGRESS_HTTPS=:443' >> ~/.config/isb/serve.env
$ systemctl --user restart isb
$ isb app create hello --project shop --image docker:traefik/whoami --port 80 --domain shop.example.com --deploy
The name's DNS must point at the host. Without a name of your own, --domain auto gets a generated *.sslip.io name for the host's public address. See
domains and ingress.
7. The web UI#
Open http://127.0.0.1:8092 in a browser on the host (or through an SSH
tunnel: ssh -L 8092:127.0.0.1:8092 host) and sign in. Pick acme in the
org switcher: Projects shows shop with the app, its deployments, logs,
metrics and a terminal. See the web UI.
The daemon listens only on loopback. To reach it from elsewhere, and to let agents in over MCP, put Cloudflare Tunnel and Access or a tailnet in front: remote access and agents and MCP.
A compose file instead#
Apps sit on top of stacks, which take an isb.yaml directly: the file
you develop with, plus deploy: for replicas and rolling updates.
# isb.yaml
services:
api:
image: docker:traefik/whoami
ports: ["127.0.0.1:8081:80"]
deploy:
replicas: 3
update_config: {order: start-first} # no gap during a rollout
$ isb stack deploy demo
$ isb stack ps demo
$ isb stack rollback demo
$ isb stack rm demo
See stacks.