---
title: Operations
description: Update Noite, back up and restore its volumes, and troubleshoot ready gates and boot failures.
---

## Releases and channels

Installs follow releases, not `main`. `NOITE_IMAGE` (or `NOITE_VERSION` for the installer) picks what an update pulls:

| Tag | What it is |
| --- | --- |
| `alpha` | The newest release of any kind. The default while Noite is alpha. |
| `stable` | The newest final release (no `-alpha.N` in its version). Absent until the first one. |
| `0.1.0-alpha.1` | One release, pinned: an update changes nothing. |
| short SHA, `edge` | A build of `main`. Not for installs: for CI and rollbacks. |

Every release is tagged `v<version>` and passes the end-to-end lanes (passkey signup, invites, git push, deploys, and the hostile-tenant suite) before its image is published. Its notes are in CHANGELOG.md; each has an **Operator action required** section, even when it is "None". Read it before you update.

To roll back, set `NOITE_IMAGE` to the previous version tag. That works across releases that did not change the database schema; a release that did refuses to be downgraded (see [Troubleshooting](#troubleshooting)), so take a [backup](#backups) first.

## Updating

Re-run the installer, or pull and recreate by hand:

```bash
curl -fsSL https://noite.run/run.sh | bash -s install
# or
cd /opt/noite && docker compose pull && docker compose up -d
```

The image is disposable and state lives in the volumes and the bucket, so an update is a pull plus a recreate. The runner stops every fleet in parallel inside its stop budget (`NOITE_STOP_BUDGET_MS`, 25 s; Compose's `stop_grace_period` is 35 s), then Caddy, then writes a final state snapshot. A new image restarts every tenant fleet with it (cold boots), so batch upgrades.

Stop-budget tuning lives in [Environment variables](/reference/environment-variables).

## Backups

A consistent backup snapshots the runner database, stops the stack, tars both volumes and starts again. Downtime is the tar time.

```bash
cd /opt/noite
STAMP=$(date -u +%Y%m%dT%H%M%SZ) && mkdir -p "backups/$STAMP"
docker compose exec -T noite sh -c \
  'curl -fsS -X POST -H "Authorization: Bearer $RUNNER_TOKEN" http://127.0.0.1:8080/v1/admin/snapshot'
docker compose stop
for vol in noite-data rustfs-data; do
  docker run --rm --entrypoint tar -v "noite_$vol:/vol:ro" -v "$PWD/backups/$STAMP:/out" \
    ghcr.io/ryuzcorp/noite:alpha -cf "/out/$vol.tar" -C /vol .
done
docker compose up -d
```

With your own bucket, turn on versioning at the provider instead; `rustfs-data` then does not exist and only `noite-data` needs the tar.

## Restore

:::danger
Restoring replaces the live volumes: everything written since that backup is gone.
:::

```bash
cd /opt/noite
docker compose down
for vol in noite-data rustfs-data; do
  docker volume rm -f "noite_$vol" && docker volume create "noite_$vol"
  docker run --rm --entrypoint tar -v "noite_$vol:/vol" -v "$PWD/backups/<stamp>:/in:ro" \
    ghcr.io/ryuzcorp/noite:alpha -xf "/in/$vol.tar" -C /vol
done
docker compose up -d
```

Compose warns that the volumes were not created by it; that is expected. Verify with `docker compose exec noite curl -s http://127.0.0.1:8080/ready`, then open an app.

## Troubleshooting

- **Boot stops with `refusing to start: the runner database is schema version N`** (or the same for the control database): the data was written by a newer Noite than the image you started. Run the newer image again, or [restore](#restore) a backup taken before the upgrade. Downgrading across a schema change is not supported, and the refusal is what stops an old image from damaging newer data.
- **Locked out after losing the passkey**: see [Lost passkey](/self-hosting/accounts#lost-passkey).
- **`/ready` answers 503**: its body names the failing gate (`reconcile`, `bucket`, `isolation`, `control`, `caddy`). A fresh install waits for the control bundle's first deploy into `s3://<bucket>/control` (seconds; revision-gated, so a restart with the same bundle and vars skips it).
- **`isolation: …` in multi**: the container lacks a capability or `nft`. Grant `NET_ADMIN`, `SETUID`, `SETGID` and `CHOWN`, or run `NOITE_TENANCY=single`.
- **Boot stops with `runner state restore failed`**: the volume is empty and the bucket did not answer. The runner refuses to start with an empty database (it would overwrite the good snapshot); fix the store and it retries on restart.
- **A deploy fails with `celld deploy does not support these config keys`**: a Wrangler key celld does not accept. Noite strips the keys it consumes (`release`, `build`) before deploying; anything else must go.
- **A deploy fails with `nothing to deploy`**: the tree has no Wrangler config, no `index.html` in `dist/`, `build/` or `out/`, and no `package.json` `main`. See [No config at all](/apps/build#no-config-at-all).
- **A deploy fails with `starts a Node HTTP server`**: `package.json` `main` calls `.listen()`. Noite runs Workers; see [Frameworks and Node servers](/apps/build#frameworks-and-node-servers).
- **Many `429` responses**: the edge rate limits. Behind a proxy, check `NOITE_TRUSTED_PROXIES`; otherwise raise `NOITE_EDGE_RPM` or the app's own limit ([Edge protection](/self-hosting/protection)).
