Install
Install Noite on a fresh server with one command, or by hand with Docker Compose — bundled RustFS or your own S3 bucket.
One-command install
On a fresh Ubuntu or Debian server (amd64 or arm64, 2 GB of memory or more, ports 80 and 443 free):
curl -fsSL https://noite.run/run.sh | bash -s install
It elevates itself with sudo (no sudo in front: piped into sudo bash, the script runs in the background and cannot ask anything). It installs Docker Engine and the Compose plugin when missing, asks for the base domain, and writes /opt/noite/compose.yaml (option A, from main) and /opt/noite/.env with generated secrets, CONTROL_SUBDOMAIN=app and the edge on 80/443. It then opens 80/443 in ufw when it is active, starts the stack and waits for /ready. Press Enter for the default <public-ip>.sslip.io, which needs no DNS and is enough to try Noite out; with no terminal to ask on, it uses that default. For a real domain, point app., api., git. and *.<domain> at the server first.
Re-running the script is the upgrade: it refreshes compose.yaml, keeps .env (the domain and secrets never change after the first install, since passkeys bind to BETTER_AUTH_URL), pulls and recreates. Inputs are environment variables, set on bash, not on curl: curl … | NOITE_DOMAIN=example.com bash -s install (a variable in front of curl only reaches curl).
| Variable | Default | Effect |
|---|---|---|
NOITE_DOMAIN |
asked, else <ip>.sslip.io |
BASE_DOMAIN on first install |
NOITE_VERSION |
alpha |
release channel or version; see Releases and channels |
NOITE_ADMIN_EMAIL |
none | account promoted to admin at boot |
NOITE_EMAIL_WEBHOOK_URL |
none | receives lost-passkey sign-in codes; without it, recover from the server |
NOITE_DIR |
/opt/noite |
install directory |
NOITE_REF |
main |
git ref the installer and compose.yaml are fetched from |
The script always runs the installer from the ref’s latest commit, so a CDN-cached copy is never stale.
Before you invite anyone, read Known limits: Noite is alpha, and tenant isolation is not a hard boundary yet.
For your own bucket (option B), lost-passkey email (NOITE_EMAIL_WEBHOOK_URL) or any other setting, edit /opt/noite/.env and run cd /opt/noite && docker compose up -d. The sections below are the same install by hand, for hosts where you’d rather not run the script.
Uninstall or start over
curl -fsSL https://noite.run/run.sh | bash -s uninstall
The script removes the stack’s containers, network and volumes (the runner database, git mirrors, Caddy certificates and the bundled bucket), the Noite and RustFS images, and /opt/noite with its .env. Docker and the ufw rules for 80/443 stay. It asks you to type the base domain first; without a terminal, set NOITE_CONFIRM=1. Then run install again for a fresh install, which is also how you change the domain.
| Variable | Default | Effect |
|---|---|---|
NOITE_KEEP_DATA |
0 |
1 removes the containers only; volumes, images and .env stay, and install brings the same install back |
NOITE_CONFIRM |
0 |
1 skips the confirmation |
NOITE_DIR |
/opt/noite |
install directory |
A bucket of your own is never touched: a reinstall pointed at it restores the old state from its snapshot, so empty it or set a new NOITE_S3_BUCKET to start fresh. Each full reinstall also issues new TLS certificates, and Let’s Encrypt allows 5 certificates per hostname per week, so reinstall a real domain sparingly (NOITE_KEEP_DATA=1 keeps the certificates).
Manual install
The same result without the script, on any host with Docker (or Podman) and Compose. Only two files are needed; nothing is cloned or built.
Prerequisites
- A Docker or Podman host with Compose.
- DNS:
app.<domain>(control UI),api.<domain>,git.<domain>, plus*.<domain>for tenant apps. The apex stays free for your marketing site. - Ports 80/443 reachable — Caddy terminates per-host TLS itself via on-demand certificates (ask-gated, so only live hosts get certs).
A: bundled RustFS
mkdir -p /opt/noite && cd /opt/noite
curl -fsSLO https://raw.githubusercontent.com/ryuzcorp/noite/main/docker/compose.yaml
# write .env (below), then:
docker compose up -d && docker compose logs -f noite
.env next to compose.yaml:
BASE_DOMAIN=<domain>
CONTROL_SUBDOMAIN=app
BETTER_AUTH_URL=https://app.<domain>
GIT_PUBLIC_BASE=https://git.<domain>
HTTP_PORT=80
HTTPS_PORT=443
NOITE_IMAGE=ghcr.io/ryuzcorp/noite:alpha
RUNNER_TOKEN=<openssl rand -hex 32> # shared runner↔ui bearer token
BETTER_AUTH_SECRET=<openssl rand -hex 32> # session signing secret
RUSTFS_ACCESS_KEY=<openssl rand -hex 8> # dev defaults are refused
RUSTFS_SECRET_KEY=<openssl rand -hex 24>
CONTROL_SUBDOMAIN empty means the control UI is served on the bare domain (the localhost default); app is the production setting. Without an .env, compose.yaml boots a local trial on http://localhost:9080.
Open https://app.<domain> and create the owner account (first signup), then deploy an app as in the Quickstart.
Other variables worth setting: NOITE_S3_BUCKET, NOITE_ADMIN_EMAIL, NOITE_EMAIL_WEBHOOK_URL (without it a lost passkey is recovered from the server), NOITE_SMTP_FROM, CONTROL_EXTRA_HOSTS (extra hostnames serving the control UI), CADDY_AUTO_HTTPS, NOITE_TENANCY (see Tenancy). Full detail for every variable lives in Environment variables.
B: your own bucket
Add the bucket to .env (dropping the RUSTFS_* keys) and start without the bundled store:
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
S3_PUBLIC_ENDPOINT=https://s3.us-east-1.amazonaws.com
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
NOITE_S3_BUCKET=noite
docker compose up -d --scale rustfs=0
Key requirements: Get/Put/Delete/List on the bucket. The bucket is created if the key allows, otherwise create it first. This works the same for an installer-made /opt/noite.
Verify
cd /opt/noite
docker compose exec noite curl -s http://127.0.0.1:8080/ready # {"ok":true,...}
curl https://api.<domain>/health # edge → runner
/ready is the summary: 200 only when the first reconcile ran, the bucket answers, isolation holds (in multi), the control fleet is healthy and Caddy accepted its config, and its body names whatever is failing; it is also the container’s healthcheck. celld’s own view of the control node: docker compose exec noite curl -s http://127.0.0.1:8090/.well-known/celld/health.