> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ns.rocks/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Hosted Single-Host

> Install the OSS single-host appliance, connect with the SDK or CLI, and run the operator checks.

Run the OSS appliance when you want one machine to own the API, local Supabase,
Caddy/static console, and the local microVM runtime. This path is separate from
hosted private beta and from the hosted fleet topology.

## Support Matrix

Use one Ubuntu 22.04 or 24.04 x86\_64 bare-metal host with KVM available; x86\_64
is the only validated host class today (the installer also builds for aarch64,
but that path is unvalidated). macOS and Windows are not supported hosts:
Firecracker needs
`/dev/kvm`, which a typical laptop — or a nested VM / WSL2 — does not provide. In
the cloud, use a bare-metal instance (for example an AWS `*.metal`); standard VM
instances do not expose KVM. Size the host with at least 8 vCPU, 32 GiB RAM, and
200 GiB free disk for basic smoke tests. The host needs root or sudo access,
Docker Engine with Compose v2, Caddy, and a reflink-capable XFS data filesystem
mounted at `/var/lib/nullspace/data`.
Keep runtime state, template-control, snapshots, runtime artifact cache, and
active rootfs sources on that same filesystem.

| Surface               | OSS v0 support                                                   |
| --------------------- | ---------------------------------------------------------------- |
| API/runtime ownership | One `nullspace-api` process in `single_host` mode                |
| Durable state         | Local self-hosted Supabase                                       |
| Console               | Static build served by Caddy at `/console` or `console.<domain>` |
| Preview ingress       | API-compatible Caddy ingress                                     |
| Operator tool         | `nullspace-host` on the appliance host                           |
| Release source        | Checksummed release manifest artifacts                           |

Unsupported in OSS v0:

* multi-host scheduling, autoscaling, HA control planes, and hosted edge
* `apps/edge` as a required ingress component
* managed custom preview domains
* self-hosted Supabase HA
* hibernate, resume, and fork launch claims until the runtime compatibility
  gates for snapshot and guest-agent upgrades are complete

## Install From A Release Manifest

If you are installing from a source checkout, use the source installer:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
git clone https://github.com/catamaran-research/nullspace.git
cd nullspace
sudo ./install-nullspace.sh
```

It builds the checkout, installs local Supabase and appliance services, starts
them, and runs doctor/status/smoke/launch-gate checks. See
`docs/product/single-host-setup.md` in the source checkout for storage,
owned-domain, and rerun options.

For packaged releases, download or mount a release bundle that contains
`manifest.json` and the `nullspace-host` operator tool. Install the operator
tool first, then converge the appliance from the manifest:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo install -m 0755 /mnt/nullspace-release/nullspace-host /usr/local/bin/nullspace-host

sudo nullspace-host install \
  --manifest /mnt/nullspace-release/manifest.json \
  --operator-email you@example.com \
  --evidence-dir /var/lib/nullspace/evidence/install
```

`install` renders `/etc/nullspace/env`, generates local Supabase secrets,
renders the Supabase Compose and Caddy configs, applies migrations, seeds the
first DB-backed operator API key, fetches and verifies release artifacts, and
writes redacted evidence.

The first API key is written once on the host:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo cat /etc/nullspace/operator-api-key
```

Keep `/etc/nullspace/operator-api-key`, `/etc/nullspace/env`, and
`/etc/nullspace/supabase.env` root-readable only. Service-role keys stay on the
server and are not part of the static console build.

Enable services after the install has rendered configs and fetched artifacts:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo systemctl enable --now nullspace-supabase.service
sudo systemctl enable --now nullspace-api.service
sudo systemctl enable --now caddy.service
```

## Check The Host

Run non-mutating diagnostics first:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host doctor --json \
  --evidence-dir /var/lib/nullspace/evidence/doctor

sudo nullspace-host status --json \
  --evidence-dir /var/lib/nullspace/evidence/status
```

`doctor` checks host, service, Supabase, artifacts, storage, network,
ingress, and Firecracker hardening facts. `status` reports service liveness,
API readiness/degraded state, worker status, runtime capacity, storage
pressure, and ingress state.

Collect bounded redacted logs when a check fails:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host logs --json \
  --evidence-dir /var/lib/nullspace/evidence/logs
```

## Connect With SDK Or CLI

The localhost/no-domain appliance serves API traffic through Caddy on the same
origin as the console:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
export NULLSPACE_API_KEY="$(sudo cat /etc/nullspace/operator-api-key)"
export NULLSPACE_API_URL=http://localhost

python -m pip install "nullspace-sdk[cli]"
nullspace doctor
```

Run a machine from Python:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
from nullspace import Machine

with Machine.create(template="base") as machine:
    result = machine.commands.run("echo self-hosted", shell=True)
    print(result.stdout.strip())
```

Open the static console at:

```text theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
http://localhost/console
```

Use the same API key from `/etc/nullspace/operator-api-key` on the console sign
in page.

## Preview URLs

Localhost/no-domain mode leaves `NULLSPACE_PUBLIC_HOSTNAME` unset. It is for
private operator-only use and does not issue signed public preview hostnames.
Use direct local mappings from the SDK or CLI:

```python theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
info = machine.get_host_info(8080)
print(info.host)
```

Owned-domain mode requires a DNS name you control, wildcard DNS pointing preview
subdomains at the host, Caddy TLS for the apex/console hosts, and these
server-side env values in `/etc/nullspace/env`:

```dotenv theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
NULLSPACE_PUBLIC_HOSTNAME=nullspace.example
NULLSPACE_EDGE_PUBLIC_BASE_URL=https://nullspace.example
NULLSPACE_EDGE_ACCESS_TOKEN_SIGNING_KEY=replace_with_32_plus_bytes_random_secret
```

After rerendering ingress with
`sudo nullspace-host install --repair-env --manifest /mnt/nullspace-release/manifest.json`,
SDK helpers return signed
`https://{PORT}-{MACHINE_ID}.nullspace.example/...` and matching `wss://`
preview URLs through API-compatible ingress. Caddy obtains exact-host preview
certificates on demand after asking the loopback-only API TLS gate; a wildcard
certificate is not required for OSS single-host mode.

See [Preview URLs](../networking/preview-urls) and
[WebSockets](../networking/websockets) for client behavior.

## Smoke And Launch Gate

Run the fast Caddy fixture smoke without creating a microVM:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host smoke --ingress-fixture --json \
  --evidence-dir /var/lib/nullspace/evidence/smoke-ingress
```

Run the live machine smoke after doctor/status pass:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host smoke --live --json \
  --api-key-file /etc/nullspace/operator-api-key \
  --evidence-dir /var/lib/nullspace/evidence/smoke-live
```

Before making public launch claims for a release, run the launch gate on a
fresh supported host. Mutating install, restart, reboot, live upgrade, and
failure-injection stages require the exact confirmation phrase
`run nullspace launch gate`.

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host launch-gate \
  --mode localhost \
  --manifest /mnt/nullspace-release/manifest.json \
  --console-url http://localhost/console/ \
  --restart-api \
  --restart-supabase \
  --reboot \
  --confirm "run nullspace launch gate" \
  --evidence-dir /var/lib/nullspace/evidence/launch-gate
```

After the host returns from reboot, resume the same run:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host launch-gate \
  --after-reboot \
  --mode localhost \
  --evidence-dir /var/lib/nullspace/evidence/launch-gate
```

For owned-domain evidence, use `--mode owned-domain` and
`--public-hostname nullspace.example`.

## Back Up, Restore, Upgrade, Roll Back

Create a protected appliance backup archive:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
BACKUP=/var/lib/nullspace/backups/nullspace-$(date +%Y%m%d).tar.gz

sudo nullspace-host backup \
  --output "$BACKUP" \
  --evidence-dir /var/lib/nullspace/evidence/backup
```

Rehearse restore safety before extracting:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host restore \
  --input "$BACKUP" \
  --dry-run \
  --evidence-dir /var/lib/nullspace/evidence/restore-dry-run
```

Mutating restore requires:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host restore \
  --input "$BACKUP" \
  --confirm "restore nullspace appliance"
```

Validate an upgrade manifest before switching releases:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host upgrade \
  --manifest /mnt/nullspace-release-next/manifest.json \
  --dry-run \
  --evidence-dir /var/lib/nullspace/evidence/upgrade-dry-run
```

Apply the upgrade, then run status and smoke:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host upgrade \
  --manifest /mnt/nullspace-release-next/manifest.json \
  --evidence-dir /var/lib/nullspace/evidence/upgrade

sudo nullspace-host status --json
sudo nullspace-host smoke --ingress-fixture --json
```

If the recorded upgrade state supports returning to the previous release:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host rollback --json \
  --evidence-dir /var/lib/nullspace/evidence/rollback
```

Database migrations are not automatically rolled back. Treat DB rollback as a
manual operator boundary and restore from a verified backup when a migration
must be reversed.

## Troubleshooting

| Symptom                                             | What to check                                                                                                                                                                                |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `doctor` reports KVM or CPU virtualization failures | Confirm the host is x86\_64 bare metal with KVM enabled and that the service user can use `/dev/kvm`.                                                                                        |
| Storage falls back to byte copy                     | Put `NULLSPACE_DATA_DIR`, active rootfs sources, template-control, snapshots, and runtime artifact cache on the same reflink-capable XFS filesystem.                                         |
| Console opens but API calls fail                    | Check `sudo nullspace-host status --json`, Caddy status, and that the console build uses `VITE_NULLSPACE_API_BASE=same-origin` for `/console` mode.                                          |
| Localhost preview URL is missing signed URL fields  | This is expected when `NULLSPACE_PUBLIC_HOSTNAME` is unset. Use direct `get_host_info()` mappings or configure owned-domain mode.                                                            |
| Owned-domain preview URLs are not issued            | Confirm `NULLSPACE_PUBLIC_HOSTNAME`, `NULLSPACE_EDGE_PUBLIC_BASE_URL`, and `NULLSPACE_EDGE_ACCESS_TOKEN_SIGNING_KEY` are set, then rerender with `sudo nullspace-host install --repair-env`. |
| Backup warns about missing Supabase dump            | Confirm Docker Compose v2 is installed and the local Supabase stack is running, or use the packaged Supabase volumes with the warning recorded in evidence.                                  |

## Uninstall

Remove installed surfaces while preserving appliance data:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host uninstall \
  --preserve-data \
  --confirm "uninstall nullspace preserve data"
```

Destroy config, data, Supabase state, and evidence only after taking and
verifying a backup:

```bash theme={"theme":{"light":"one-light","dark":"one-dark-pro"}}
sudo nullspace-host uninstall \
  --destroy-data \
  --confirm "uninstall nullspace destroy data"
```

## Related

* [Environment Variables](../reference/env-vars)
* [Single-Host Release Artifacts](../reference/single-host-release-artifacts)
* [Hosted Endpoints](../reference/hosted-endpoints)
* [Preview URLs](../networking/preview-urls)
* [SDK Config](../reference/config)
* [Supported Clients](../reference/clients)
