Skip to content

Releases: ryuzcorp/noite

v0.1.0-alpha.9

Choose a tag to compare

@github-actions github-actions released this 22 Oct 21:01
58a977a

Admins hear about new releases, telemetry is split per preview environment, and the runner revokes bucket keys whose credential rows were lost. Unpinned npm and pnpm projects run the image's bundled releases, a dropped DB setup pass no longer stalls the control plane, and concurrent creates no longer collide on a fleet port. CI now merges only through a queue that runs the full suite on both lanes, with no retries.

Operator action required

  • The runner now checks for new releases once a day (opt-out). Five minutes after boot, and then every hour, the runner checks whether 24 hours have passed since its last check. When they have, it makes one anonymous GET of the public GitHub release list (https://api.github.com/repos/ryuzcorp/noite/releases — fixed URL, fixed User-Agent, no auth) and shows instance admins a dismissible banner when a newer promoted release exists on the install's channel. Nothing about the instance is sent — no version, no identifiers. To turn it off: clear Check for new Noite releases on /account (Admin tab), or set NOITE_UPDATE_CHECK=0 in .env (the installer accepts it; false and off work too). The variable wins over the UI toggle. make doctor reports the check state and the newest release seen.
  • metrics.sqlite auto-migrates on boot: no action, no replay, no history loss. The upgrade adds environment_id to the telemetry tables and rebuilds their primary keys around it, copying every row (existing rows become production). Downgrade across this change is unsupported, like every other downgrade.
  • Direct runner API callers: RPC limits.set now takes the caller's role and refuses requests without the admin role, like the domains and environments mutations already do. Callers through the control UI are unaffected.
  • AWS installs using scoped keys (NOITE_CREDENTIAL_PROVIDER=aws) grant one more IAM permission, and minted users move under an install tag. The runner now reconciles the store against its scoped_credential rows and revokes orphaned users, which needs iam:ListUsers on the runner's keys (IAM allows no narrower resource for this call: add a statement with "Action": ["iam:ListUsers"] and "Resource": "*", as shown in Storage) plus iam:GetUser on the names it mints (when a user name collides, the runner reads it back and only clears a user under its own path). Every minted user now carries this install's tag — a short hash of the bucket and region shown as install_tag in /ready — in its IAM path (/noite/<tag>/, instead of the old default /) and its name (noite-<tag>-…). Users minted before this change stay under / with untagged names and are never touched by the new sweep; revoke, rename and delete keep working for them by handle. Without ListUsers the sweep logs a warning and revokes nothing; everything else keeps working.

Added

  • Admins hear about new releases. The opt-out daily release check above feeds a dismissible per-version banner in the control UI shell (admins only; dev and SHA builds never notify) linking the release notes and the upgrade docs, plus instance.update_status (GET /v1/admin/update) for the toggle state, the running version and the newest release seen.
  • Per-environment telemetry. Preview environments are first-class in telemetry: the ingest reads envs/<slug>/<label>/telemetry/ alongside production, every metric/span/log/error row keys on (app, environment), and the Metrics and Errors tabs scope by environment with a picker (Production first, then environments, ?env= deep-links) while logs scope through the Deployments tab's environment groups. The sleep sweep parks environments on their own request activity, environment deploys keep source maps under their own prefix (environment errors symbolicate like production's), and the pull request page shows the head branch's environment card — status, URL, last deploy, links to its Metrics and Errors — or a Create environment action when there is none. MCP app_logs and app_errors take an optional environment. Custom events and the storage browsers stay app-wide.
  • Release notes live in changelog.d/ fragments, one file per PR. Concurrent PRs no longer rebase-conflict on ## [Unreleased] (which stays empty): each PR adds changelog.d/<unique-topic>.md using the CHANGELOG's own ### <Section> headings with - bullets. bun docker/check-changelog.ts rejects unknown sections, bullet-less fragments, and any [Unreleased] entries; the release PR runs bun docker/changelog-release.ts <version> [date] to assemble the dated section (canonical order, ### Operator action required always present) and delete the fragments.
  • bun docker/pr-ready.ts runs every local gate before pushing. It detects changed areas against origin/main, runs bun run check, the runner/UI gates, check-changelog/check-versions, and a merge-tree conflict check (conflicting PRs get no CI), then prints a summary table. --skip-build skips the UI bundle build for fast iterations; a missing changelog.d/ file on runtime changes warns without failing.
  • The store is reconciled against the credential rows. At boot and every ten minutes the runner lists the principals it manages (list-users on RustFS/MinIO, paginated ListUsers on AWS, nothing on R2/root) and revokes any with no scoped_credential row, live or revoking — the orphans a crash inside the snapshot loss window leaves behind. Every install mints under its own tag (a short hash of the endpoint plus bucket, or bucket plus region on AWS, computed at boot so it survives DB loss), stamped into MinIO canned-policy names and AWS user names and paths; the sweep requires the tag back, so two installs sharing a store or account never revoke each other's live keys, and restoring an older snapshot revokes keys minted after it (restart fleets after a restore). /ready gains an additive credential_sweep object (install tag, seconds since the last run, total revoked, last listing error truncated to 300 chars) and make doctor reports it.
  • Per-fleet peak RSS is logged on every fleet stop. When the runner removes a fleet's cgroup leaf (stop, delete, teardown) it first reads that leaf's memory.peak and logs it at info (fleet=… peak_bytes=…), so per-fleet high-waters survive the leaf's deletion. The e2e lane merges those lines with the live leaves at suite end into the always-uploaded fleet-peaks.json (beside the human-readable lane-stats.txt lines) on every run — the D3 sizing basis for the default per-fleet cap.

Changed

  • The Ilha + Void template lives at ryuzcorp/noite-template-ilha. Its repository was renamed from ryuzcorp/noite-template-oxide, and new apps clone it under the new name. Installs on 0.1.0-alpha.8 and older keep creating apps from it: GitHub still serves git clones of the old name directly, with no redirect, which is what the runner's redirect-refusing clone needs (verified after the rename).
  • Runner state snapshots land faster and report their health. The sync task polls for changes every 2 s (was 10 s), waits 5 s for bursts to settle (was 10 s) and uploads at most every 15 s (was a minute), so an ungraceful crash with a lost volume loses about 22 s of API mutations instead of about 70 s. Uploads are still skipped when the database bytes are unchanged, and the single-writer fence, the fail-closed restore and the snapshot format are untouched: no new config, and an older image reads the new snapshots. /ready gains an additive snapshot object (still the bucket owner, seconds since the last upload, last upload error) and make doctor prints it.
  • Observability reads without an environment mean production only. The metrics, spans, devices, paths, refs, logs and errors endpoints and RPCs take an optional environment; absent or empty returns production rows, where production was previously the only data. The Events tab is always app-wide: custom event writes carry no environment identity.
  • Unpinned npm and pnpm projects run the image's bundled releases. A deploy of a project that doesn't pin its package manager no longer resolves the registry's newest npm/pnpm (and downloads it per app): the runner names the exact bundled release (npm 12.2.1, pnpm 12.15.0) and seeds each app's JUP_HOME from the image's read-only store, so the manager itself never touches the registry. The app's own dependencies still come from the registry. A pin of any other release still downloads that release. Bumping a bundled release is an image release: upgrade to pick it up. The image grows by the 72 MB seed store (56 MB pnpm, 17 MB npm).
  • RPC limits.set now enforces the app role runner-side. It takes the caller's role and requires admin (a missing, view or push role is a 403), the same defense in depth the domains and environments mutations already have. limits.get stays role-free like the other runner reads.
  • CI merges only on green, with a human review on sensitive changes. ci.yml now also runs on the squash merge queue — always the full release suite on both lanes, never skipped by paths, never cancelling other queued entries — and ends in a ci-ok aggregate job; the branch ruleset requires ci-ok plus the new security-gate check. security-gate fails any PR touching .github/security-paths.yml's sensitive: paths (auth, isolation, secrets, the image, the gates themselves) until a reviewer reads the diff and adds the security-reviewed label — new commits strip it and fail again, and the change's own author never self-labels. Labels no longer start or cancel CI runs: adding e2e upgrades the next run (push, close/reopen, or manual dispatch) to the release suite. CodeRabbit skips drafts until marked ready and ignores lockfiles, generated runner-types bindings and changelog.d fragments.
  • **C...
Read more

v0.1.0-alpha.8

Choose a tag to compare

@github-actions github-actions released this 21 Oct 20:11
7fd2659

Browser errors arrive through any Sentry SDK with stacks mapped back to your source, custom domains need DNS proof of ownership on multi-tenant installs, and every fleet holds bucket keys scoped to its own prefix. The edge no longer drops requests whenever an app starts, stops or deploys, and every pinned tool moves to its latest.

Operator action required

  • Back up before upgrading: an install on 0.1.0-alpha.7 (runner schema version 8) upgrades itself to version 11 on boot (Sentry ingest keys, custom-domain ownership claims, scoped bucket keys). An older image refuses the newer data.
  • Existing custom domains keep working. Every custom domain attached before the upgrade is kept as verified.
  • Direct runner REST callers: GET/POST /v1/apps/{id}/domains, DELETE …/domains/{hostname} and the new POST …/domains/verify (RPC domains.list|add|remove|verify) now take the caller's role (view to list, admin to change) and refuse requests without it. The control UI, the CLI and MCP aren't affected.
  • Scoped bucket keys need nothing on bundled installs, one var on AWS. scoped_credential replaces the never-written app_credential table, and every fleet and release command now holds keys limited to its own prefix — minted lazily at the next spawn or deploy, so existing installs pick them up with no manual step. What each backend needs: bundled RustFS and MinIO work with no configuration (verified against RustFS 1.0.0 and 1.0.3); AWS S3 needs NOITE_CREDENTIAL_PROVIDER=aws and an IAM-capable key (the runner creates one IAM user per app and environment); Cloudflare R2 has no prefix-scoped keys, so R2 installs run an explicit root mode. Root mode is never silent: the boot log, /ready (credentials), and make doctor all name it, and it warns loudly in multi.
  • The edge is stricter about odd requests. Caddy 2.11 answers 431 past 16 KiB of request headers, drops header fields whose names contain _ or ., and closes a body upload or response write that makes no progress for a minute (a quiet event stream between writes is unaffected). Apps behind the edge that relied on underscored or dotted headers, giant cookies or stalled uploads will see those requests fail where they used to pass.

Added

  • Browser errors through any Sentry SDK. Settings → Error reporting shows the app's DSN (https://<key>@<control host>/<n>, rotatable by admins). A stock Sentry SDK pointed at it reports exceptions to POST /api/<n>/envelope/, and they land in the Errors tab as the same issues as the Worker's own errors, labelled Browser (or SDK for server SDKs) with the page URL and release. Transactions, sessions, replays and other non-error items are accepted and dropped.
  • Error stacks show your original source. Each production deploy keeps its source maps next to the bundle: the runner maps the deployed Worker itself, and browser maps come from your build if it emits them (Vite: build.sourcemap: "hidden"). The Errors tab maps each frame back to the original file, line and function, with the minified frame still shown beside it. There's no upload step. Maps are kept for the current deploy and the last 10 successful ones. Preview environments don't keep maps yet.
  • Custom domains need DNS proof of ownership on multi-tenant installs. Before, any tenant could claim any hostname, first come first served. Now a new domain starts as pending, and Settings shows the TXT record to create (_noite-challenge.<hostname> with the domain's token) plus a Verify button that names what's wrong (no record, wrong value, DNS error). A pending domain gets no route and no certificate. Several apps may hold a pending claim for the same hostname, and the first to verify wins. On single installs a domain is verified as soon as it's added.
  • Fleets and release commands hold scoped bucket keys. Each app's fleet and each preview environment's fleet now hold keys minted through the store's own IAM that read, write and list only their own prefix — an environment's keys never reach production's data, and no fleet reaches another app's. Covered per store (SPEC, Scoped credentials): bundled RustFS and MinIO through the MinIO admin API, AWS S3 through the IAM API, R2 and unrecognized stores on an explicit root mode. Renames supersede keys, deletes and environment teardowns revoke them (a retrying sweep finishes refused store deletes), and a deleted-then-recreated slug waits for the old key's revocation first. Release commands, which used to be skipped in multi, now run with the scope's own keys. The hostile e2e suite asserts a deployed Worker can't read another app's objects.

Changed

  • The Ilha + Void template replaces Oxide + ilha. The creator offers exactly Ilha + Void and TanStack Start. Ilha + Void lives in the same repository, ryuzcorp/noite-template-oxide, now rewritten as Ilha pages on Void (Vite) with server-rendered pages, a server action and an island. The template id changed from oxide to ilha-void with no alias, so MCP create_app calls that pass source: { kind: "template", template: "oxide" } must switch to ilha-void. A new e2e spec creates an app from the template, waits for the deploy and checks that it serves.
  • E2E runs a fast gate on pull requests and the full suite on release. The lanes run two suites over the same specs: fast (the tagged critical paths — create, push and deploy, logs, Code mode edits, PR merge, auth/invite gates, template deploy, rollback, environments, error ingest — plus the isolation-relevant set on the multi lane) and release (every spec, including the new storage-editing and admin-page coverage). Pull requests that touch runtime code run fast automatically; the e2e label runs release; every v* tag and a nightly run gate on release. Locally: E2E_SUITE=fast make e2e.
  • Every Docker-pinned tool moves to its latest. celld 0.6.1 → 0.6.2, Caddy 2.10.0 → 2.11.7, esbuild sidecar 0.25.12 → 0.28.2, Node 24.21.0 → 24.22.0, rustfs 1.0.0 → 1.0.3, DuckDB 1.5.5 → 1.5.6, jup 0.6.3 → 0.6.5. The bun base images stay on the floating 1.4 line (1.4.3 is the newest 1.x). celld 0.6.2 bundles exactly as 0.6.1 did, so the runner's shadow bundle and the Errors tab's mappings are unchanged; the only celld behaviour to know about is a forwarded Durable Object stub.fetch() now rejecting when the owner's handler fails, a WebSocket answer to a request without Upgrade: websocket failing as a TypeError/500, and self being defined in Worker isolates. esbuild 0.28.2 changes output in two places that matter: the binary loader, which Noite bundles never select (the runner strips Wrangler rules, and celld's default is .wasm=copy only), and, since 0.27.5, TypeScript parameter properties under "useDefineForClassFields": true, which now use define semantics as tsc does. A Worker that relies on the old assignment order can behave differently after its next deploy. DuckDB, rustfs, Node and jup are bug-fix or additive releases with nothing that changes how Noite uses them. Tenant Workers are redeployed onto the new toolchain by their next deploy, and fleets restart on the new celld with the upgrade.

Fixed

  • install --pre installs the newest release again. It took the first entry of GitHub's release list, which puts the release marked Latest ahead of newer pre-releases, so it installed 0.1.0-alpha.6 instead of 0.1.0-alpha.7. It now picks the most recently published release. run.sh is served from noite.now, so the fix applies without a new image.
  • Status badges stay in sync. After a deploy, a stop or a sleep, the app header's status and the Deployments tab's Production and environment dots kept the old state (a yellow "Deploying" dot after a finished deploy) while the sidebar moved on, until a reload. An app's pages now follow a live state stream (/api/apps/:id/state/stream) and refresh whatever it shows has changed, and the sidebar's app list updates every 2 s instead of every 10 s.
  • The Ilha + Void template no longer loses early input, and adding a todo no longer reloads the page. Its todo form and About-page counter were server-rendered in place but worked only once the page's scripts had run. A todo typed and added before that turned into a plain form GET that reloaded the page without adding it, and an early counter click did nothing. Both now stay disabled until the page can handle them. The todo action also answered with a bare {"ok":true} instead of the page data Void's client expects, so every add reloaded the whole page; it now updates in place. The template is fetched from GitHub when an app is created, so the fix applies without a new image.
  • A rollback issued right after a deploy no longer vanishes. Roll back answered OK, but if the app's previous deploy was still finishing, the rollback was silently skipped: no deploy row appeared, and the page kept waiting for a deploy that never started. A rollback now creates its queued deploy before answering and waits its turn behind the in-flight deploy, the way environment deploys already did. The runner's rollback response (deploys.rollback, POST /v1/apps/{id}/rollback) also returns the new deployId.
  • Deleting an app with a slow fleet no longer wedges later deletes. stop_fleet waited 15 s for graceful shutdown before SIGKILL, so deleting an app whose celld was slow to exit (e.g. right after a (re)start) outlasted the UI's 15 s action timeout: the caller gave up, the half-finished delete held the per-app build/delete lock, and every retry answered 409 deploy in flight forever. The lock is now a drop-guard (a cancelled, failed or panicked holder always releases it), and deletes stop the fleet with a 3 s grace instead of 15 s — the data is wiped right after, so a slow graceful shutdown is pure delay. Stops, sleeps and orphan sweeps keep the full grace.
  • **Requests no longer drop when any app...
Read more

v0.1.0-alpha.7

Choose a tag to compare

@github-actions github-actions released this 20 Oct 14:42
c088a2f

Fixes the Deployment logs pane alpha.6 shipped broken, makes every live panel recover on its own and share one connection per page, and makes "Running" mean the app actually serves.

Operator action required

  • None. No schema, config, DNS or certificate change.
  • Behaviour to know about: a deploy now reports success only once its fleet answers celld's health check, waiting up to RUNNER_WAKE_TIMEOUT_S (120 s by default). A fleet that never becomes healthy fails the deploy with fleet not healthy within … where it used to report success.

Fixed

  • Deployment logs stream again. In 0.1.0-alpha.6 the Deployments tab's Deployment logs pane only said "Log stream disconnected — retrying…": the release image shipped without the control UI's /api/apps/:id/logs/stream route, which answered 404. A repo-wide ignore rule for logs directories had kept that file out of git.
  • Live panels recover on their own. Deployment logs, deploy history, errors, events, usage and the app list went quiet for good, showing "… disconnected — retrying…", once the control UI or runner answered an error (a 502 while either restarts, say): the browser's EventSource does not retry after an HTTP error. They now reconnect with backoff (1 s up to 30 s), and the control UI answers a short 502 runner unavailable while the runner is down instead of failing the request.
  • "Running" means serving. An app or environment showed Running as soon as its fleet was spawned, before celld listened, and a deploy reported success the same way. Both now wait for celld's health gate. A deploy whose celld exits before it serves fails at once, and one whose fleet is stopped or deleted in the meantime completes without waiting. A stop or delete during a deploy or start always wins over the status change.
  • Code mode: typing right after New File kept its order. Under load, the first character typed into a new file could end up last (xport const extra = 1;e): the editor moved the caret back to the top after typing had begun.
  • The serving deploy's logs open on their own again. The Deployments tab should open the deploy serving traffic with its logs showing. It stayed collapsed whenever the deploy list arrived before the app's details.
  • Pages with many live panels no longer freeze on plain-HTTP installs. Each live panel held its own connection, and browsers allow six HTTP/1.1 connections per host. The Deployments tab with an environment reached six, so its links and API calls queued forever on make dev, LAN and other installs without HTTPS. A page now streams all its live panels over one connection (/api/streams), and a panel whose feed drops reconnects on its own while the others keep streaming. HTTPS installs (HTTP/2) weren't affected.
  • The login page's tabs and buttons wait for the page to be ready. A click on Create account before the page had finished loading did nothing. The controls are now disabled until they can respond.

Changed

  • CI: the control UI and CLI unit tests run on every pull request and release tag; among them, a guard that every /api/... path the browser requests has a committed route file. The e2e lanes run spec files in parallel (3 workers) on a cached image build.

v0.1.0-alpha.6

Choose a tag to compare

@github-actions github-actions released this 20 Oct 08:15
dcdb29b

The control UI moves to the Void framework, the CLI ships on npm as @noitenow/cli, and the runner gets stricter boot validation plus a round of concurrency and robustness fixes.

Operator action required

  • Malformed numeric runner env vars now refuse boot. A typo in any numeric RUNNER_*/NOITE_* setting, NOITE_FLEET_PORTS, the RUNNER_BUILD_UID_BASE/RUNNER_FLEET_UID/RUNNER_FLEET_GID sentinels, or the edge NOITE_EDGE_*_RPM/RUNNER_WAKE_CONCURRENCY knobs used to be silently ignored in favour of the default; the runner now fails once with a config invalid error naming each var, its bad value and the expected form. After upgrading, check make doctor/the runner logs for that error and fix the named variables. Unset (or empty, as Compose renders it) still means the default.
  • HTTPS_PORT now reaches the runner, so the first-push URL follows the public edge instead of the runner API's raw port. Coolify/Railway installs should set HTTP_PORT=80/HTTPS_PORT=443 (see the platforms docs); a prod install that leaves HTTPS_PORT unset links the 9443 dev default, which is unreachable.
  • Otherwise none. No schema, DNS, or certificate change; no other env-var change.

Added

  • Control UI on Void: the control UI (apps/noite) migrates from Oxide to the Void framework on ilha 0.16 (void@1.1.12 + @ilha/void@0.1.4), with the same routes, actions and serving contract. Behaviour is unchanged apart from the fixes below.
  • @noitenow/cli 0.1.0-alpha.6 on npm: the published bundle runs on plain Node 20+ (previously it required Bun even when installed via npm). Every v* tag publishes the CLI at that exact version (dist-tag alpha for prereleases, latest for finals) after the release gate passes.
  • noite environments vars unset <environment> <name> removes an environment variable override.
  • A rollback end-to-end spec pins that rolling back to an earlier deploy serves that revision again.

Fixed

  • Deployments include dotfiles and redeploys drop stale ones; noite deploy prints the tenant's public host (https://{slug}.{base}, scheme/port from the git base) and keeps a path in the git base URL (GIT_PUBLIC_BASE=http://host/v1/git works now).
  • The first-push remote: noite: … line links the public edge URL (http://<slug>.noite.localhost:9080 in dev, https://<slug>.<domain> in prod) instead of the runner API's raw port.
  • Pod assets answer 404 on control hosts: the runner-owned Caddyfile denies the six pod-asset paths (/noite-pod-host.html/.js, /__sw__.js, /__worker__.js, /__nodepod_bridge__.html/.js) on every control-host site before proxying, because the worker's asset fallback would otherwise serve them with a 200. The control UI's own bundle under /assets/* still serves, and preview-host behaviour is unchanged.
  • Runner concurrency: creating, renaming or deleting apps while deploys or pushes are in flight no longer resurrects deleted data, races renames onto one slug, or orphans storage on malformed listings; snapshot runs no longer share temp paths, and a failed storage check at boot refuses to start instead of seeding a fresh generation.
  • Runner robustness: S3 GET/PUT/LIST retry transient 5xx/SlowDown/transport errors (never 4xx); missing-key detection no longer mistakes key names or transport failures for "not found"; timed-out commands and stopped fleets kill the whole process group including grandchildren; deploy/log streams drop unserializable snapshots with a warning instead of blank events; snapshot setup failures surface as errors; expired or revoked credentials on delete/rename are logged; trailing-dot FQDNs pass the TLS gate like the custom-domain branch.
  • Non-UTF-8 work-dir paths now fail the git request/deploy/rehydrate with an error instead of panicking the task, and a deploy whose target environment row is missing reports an error instead of crashing.
  • Control UI server-rendering no longer crashes on the app-creation, onboarding and preview paths (browser-only globals guarded); a failed app grant rolls back the created row and surfaces the cause instead of masking it as a 500; deploy logs and file previews stay out of persistent snapshots.
  • Code mode could stay on the file you were leaving. Clicking another file right after typing could leave the editor and ?file= on the old file: the old file's last write settling after the click cancelled the open. The open now wins.

Changed

  • JS deps, all at latest: better-auth 1.7.7 → 1.7.11, effect + @effect/sql-d1 + @effect/platform-node 4.0.4 → 4.0.5, kysely 0.29.6 → 0.29.7, void 1.1.11 → 1.1.12 with @ilha/void 0.1.4, @types/node 26 for the CLI, oxlint 1.89 / oxfmt 0.74, plus blume, daisyui, @cloudflare/workers-types, @playwright/test, @types/bun and the sample apps' cf / vite / @cloudflare/vite-plugin; the lint toolchain moves to ultracite 8. Code mode's language service stays on TypeScript 5.9: TypeScript 7 ships no createLanguageService.
  • Runner deps, all at latest (majors included): sqlx 0.9, reqwest 0.13, tower-http 0.7, RustCrypto aes-gcm 0.11 / hkdf 0.13 / hmac 0.13 / sha2 0.11, rand 0.10, base64 0.23, zstd 0.14, constant_time_eq 0.6, tokio 1.54. Stored credentials, tokens and SigV4 signatures are byte-identical (pinned by fixed-vector tests). The runner's outbound HTTPS (PostHog, BYO S3) now trusts the OS certificate store, which the image ships; no schema or config change.
  • CI: the release image target now builds on every PR (no push), and the e2e lanes run on PRs carrying the e2e label (plus manual dispatch). GitHub Action pins move to current majors, and the schema gate diffs the full 17-table allowlist.
  • Docs refresh: release references to 0.1.0-alpha.5, control-host denied-asset 404s, preview environments starting empty, and the environments bucket prefix; dev installs derive BETTER_AUTH_URL/GIT_PUBLIC_BASE from BASE_DOMAIN, and make nuke also removes the UI's .wrangler/.void dirs.

v0.1.0-alpha.5

v0.1.0-alpha.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 14 Oct 17:16

Preview environments (one deployment per branch, and Deploy preview from Code mode), resizable Code mode panes, and a pass over Code mode and pull request UX.

Operator action required

  • The runner SQLite upgrades itself to schema version 8 (the environment and environment_env tables, and deploy.environment_id). As before, an older image refuses newer data, so back up before upgrading.
  • Three new runner variables, all optional: RUNNER_MAX_ENVIRONMENTS_PER_APP (5), RUNNER_MAX_ENVIRONMENTS (25) and RUNNER_ENVIRONMENT_TTL_H (168 = 7 days; 0 disables the workspace-preview sweep). Their defaults are the documented behaviour; nothing has to be set.
  • No DNS or certificate change is needed: environment hosts are <label>.<slug>.<base>, and the *.<domain> record every install already has matches every depth, so the edge mints their certificates on demand like any tenant host.
  • CI/users of the CLI: noite environments talks to the control UI's origin (app.<domain>) rather than the git base, because the runner's REST does not accept a profile API key. The origin is derived from --url (git.<domain> → app.<domain>, and the bare base on a localhost install); an install with a non-default CONTROL_SUBDOMAIN sets NOITE_APP_URL (or passes --app-url).

Added

  • Preview environments: one real deployment per branch, at its own <label>.<slug>.<base> host, built by the same pipeline as production and with its own bucket prefix (envs/<slug>/<label>) — so its storage starts empty (new D1 databases, Durable Objects and R2 buckets on the first deploy) and production data can be neither read nor wiped. main is the app itself, never an environment. Environments are members only by default, with a per-environment Public switch (admins), and scale to zero like apps. Variables are inherited from the app with per-environment overrides. Caps: RUNNER_MAX_ENVIRONMENTS_PER_APP (5) and RUNNER_MAX_ENVIRONMENTS (25), enforced in the runner. Docs: Apps → Environments.
  • The app page's Deployments tab groups deploys by environment: Production first, then one group per environment (/apps/<id>?t=deployments&env=<id> deep-links to one) showing its branch, status, last deploy, URL, public toggle, variable overrides and delete in the group header, with the environment's own deploy history below it. Create environment sits in the tab header with the branch picker; the standalone Environments tab is gone. A preview environment's deploy rows show that environment's own fleet's live log — GET /v1/apps/{id}/logs[/stream]?environmentId= scopes the tail, an environment of another app is a 404, and never production's lines — and carry no Roll back, which re-runs production's pipeline (the group's Redeploy covers redeploys).
  • Code mode's Preview panel deploys the workspace instead of running it in the browser: Deploy preview snapshots the uncommitted changes as one commit on a hidden ref (refs/previews/<user>/<branch>, never in branch lists, pull request heads or git clone), deploys that (user, branch) environment and frames its host — a new snapshot redeploys the same environment, so its storage survives between previews. Workspace previews expire after 7 days without a deploy.
  • Environment access gate: the control UI's session cookie is host-only, so a members-only environment 302s an unauthenticated visitor to the control UI (/api/env-auth), which checks the session and the app's view role and hands back a short-lived token; the runner's edge sets a host-only __noite_env cookie it then verifies on every request (and strips before the app sees it).
  • noite environments list|create|delete|deploy|public|vars in @noitenow/cli, and MCP tools list_environments, create_environment, delete_environment, deploy_environment, set_environment_public, environment_env_list, environment_env_set, environment_env_unset (deploy_status gained an optional environment). The CLI calls the control UI's MCP endpoint — the only JSON API a profile API key can reach — so both surfaces run the same tools and the same role gates.
  • Code mode's file tree reads the checkout's .gitignore — the root's and any nested one, with git's own rules (! negation, a folder-only trailing /, an anchored /, **, comments, escapes) — and paints what it excludes dimmed, the way an editor does: a dist/, out/, .wrangler, .ilha or installed node_modules a Terminal build or install just created is visible where git would not list it at all, and an ignored folder is shown as the folder alone (nothing under it is walked, because git does not look inside it either). An excluded path is never a change and never pushed, so a build or an install can no longer look like unpushed work; a path the commit already holds is not excluded, as git keeps tracking what it has committed. The rules apply as soon as the .gitignore is written, from the editor or the Terminal.
  • Pull requests have a Commits tab listing the head's commits over the base, each linking to its commit page; the head and base branch names in the header and list rows link into Code mode at those refs, with a Compare link beside them.
  • The pull request review box tells you where you stand — You approved / You requested changes — with the buttons relabelled Approve again / Change review, and the merge box warns <name> requested changes when any reviewer's latest review asks for them (it does not block a merge when no approvals are required).
  • Code mode's Preview panel prefers your own workspace preview while you have uncommitted changes (or right after a Deploy preview), lets you switch between Branch and My preview when a branch has both, says "Your N unsaved changes aren't in this preview" above a frame that doesn't include them, and shows how long a workspace preview has left before the 7-day sweep.
  • Code mode's branch picker moves with the arrow keys, shows each branch's actions on hover/focus with tooltips, labels the commit production serves Production deploy (read-only), and validates a new branch name against git's own rules as you type — as does the Changes panel's New branch… input.

Fixed

  • Code mode's Changes panel commits to any branch. The commit target was a two-entry select — the branch you are browsing, and New branch… — so committing to an existing branch you did not have open (say staging while on main) was impossible. The select now lists the app's branches, the browsed one first, then the default branch, then the rest newest-first, and a protected default branch is greyed out there exactly as it was before. The pull-request shortcut after a push now appears only when the push actually created a branch: committing to a branch that already existed is no longer reported as a new one.
  • Code mode's Preview panel is usable on a laptop and a phone. The side panel took a third of the editor's width, so the frame was 250px wide on a 1280px screen; it now opens at an even share. The bar wraps, the address field flexes and an empty address is a no-op instead of a reload, the environment's name is visible beside its status dot, the deploys link uses a list icon, and the address can be copied. The page fits the viewport under the mobile top bar instead of overflowing it by 49px, and below lg the side panel covers the full width.
  • Every Code mode pane is resizable, built on @data-slot/resizable: the app sidebar (200–400px, one width app-wide), the file tree, the editor/side panel split and the Terminal, each with a keyboard-accessible handle and a remembered size (localStorage, restored before the first paint). History, Changes and Preview slide in from the right and out again (no animation under reduced motion); dragging the side panel past its minimum closes it, and reopening restores its last width. Frames ignore the pointer during a drag, so dragging over the Preview stays smooth. The Changes toggle's count is a corner badge, so the button is the same size as History and Preview. A pull request's Open environment opens in a new tab.
  • A preview frame says why it is empty. /api/env-auth answers a refusal with a small page — "This preview expired or was deleted", "You don't have access to this environment", "Preview environment unavailable" — and a signed-out visitor inside a frame gets a Sign in link that opens top-level instead of the login form in the frame. The panel reports a frame that hasn't loaded after 20 seconds (with Retry), an environment that failed or is stopped, and a build log that failed to read (with Retry) instead of "(loading build log…)" forever.
  • Read-only checkouts say so. A tag, a raw commit, or a checkout you lack push on shows a Read-only banner above the editor with the reason; the Changes panel explains it instead of "Edits to files on <branch> show up here", hides the commit form (noting that Terminal-made changes can't be pushed there), and Deploy preview is off. Right-clicking the tree says the checkout is read-only instead of doing nothing. Known push failures (read-only, protected branch, a moved tip, an invalid or taken branch name, a network error) read as what to do instead of the raw server message.
  • Code mode's Changes panel writes meaningful default commit messages — Add README.md, Delete old.ts, Upload logo.png, Update 3 files — instead of Web edit: <paths> on every commit; its diffs wrap so a change at the end of a long line is visible; Discard all is a quiet secondary control instead of the panel's most prominent button; and when another tab overwrites a file this tab still has unsaved edits to, the panel says <path> was changed in another tab.
  • Opening a commit or a compare from a branch and going back returns to that branch and the open file instead...
Read more

v0.1.0-alpha.4

v0.1.0-alpha.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 13 Oct 10:37

Code mode (editor, Preview and Terminal in the browser), branches and pull requests, a new app creator with GitHub, template and folder imports, push to create, and an MCP server for agents.

Operator action required

  • The runner SQLite upgrades itself to schema version 7 (the import source on app, plus the pull request and branch protection tables). As before, an older image refuses newer data, so back up before upgrading.
  • GitHub imports and templates need outbound HTTPS from the runner to github.com. Only https://github.com/<owner>/<repo> is ever fetched, redirects are not followed, and each import is capped at 500 MiB and 10 minutes. Instances without internet access can still create blank apps and push.
  • Runner REST API callers: POST /v1/apps/{id}/source/commit takes actor ({ userId, name }) instead of an author email, and GET /v1/apps/{id}/diff (RPC source.diff) is removed; see Changed and Removed.
  • Dev installs: the control UI is now http://noite.localhost:9080, not http://localhost:9080 (see Changed). .env/.env.example carry the new base, BETTER_AUTH_URL and GIT_PUBLIC_BASE, and a stored subdomain sitting on a local base is rewritten to {slug}.{BASE_DOMAIN} at boot, so existing apps keep their slugs. A passkey is bound to BETTER_AUTH_URL's hostname, so one enrolled on localhost is not offered on noite.localhost: sign in with an emailed code (noite-runner recover mints one) and enroll a passkey again.

Added

  • Code mode's workspace runs on Nodepod: @r1ck404/nodepod (MIT with the Commons Clause) is bundled into the pod host entry alone — a second entry of the control client build, served as /noite-pod-host.js next to the host document on the app's preview host — and its Vite plugin emits Nodepod's own runtime into the client build. The control page carries no Nodepod and talks to that document over the workspace protocol. The license is recorded in a new NOTICE file, copied into the image.
  • New app creator (/apps/new), with three ways to start:
    • Blank: as before.
    • Import from GitHub: a public repository URL and an optional branch. The repo is copied once, full history included, as main, and Noite's copy is the source of truth from then on. The app shows "Importing" while it clones, then deploys. A failed import shows the error, with Retry and Delete.
    • Template: "Oxide + Ilha" and "TanStack Start" start from a single "Initial commit from …" commit. Both deploy and serve on Noite (TanStack Start with SSR and hydration).
    • The slug is prefilled from the repo or template name.
  • Push to create: git push to a slug that doesn't exist yet creates the app, with you as admin and counted against your app limit, then deploys it. The push prints the app's URL. Reserved or invalid slugs answer "repository not found", and an account at its limit gets a clear refusal. Fetching an unknown slug is still a 404.
  • Branches and history on the Source page, which now has a single bar: the branch picker on the left; History, Changes and Preview on the right. Each opens a side panel that takes a third of the width (?panel=history|changes|preview).
    • Branch picker: a dropdown that lists every branch, with how far each is ahead of or behind main and a compare link. Admins can delete branches from it. A branch creator at the bottom branches off the browsed ref and switches to the new branch. Any branch, tag or commit can be browsed via ?ref=.
    • History panel: the commits of the browsed ref, or only those of the open file.
    • Commit page: metadata, changed files with line counts, and the diff.
    • Compare: two refs, with their commits, files, diff and whether they merge cleanly. Deploys link to their commit.
    • Browser edits work on an empty repository. Other branches are read-only in the browser. The Changes button counts the changed files, and its panel shows each edit as a diff, with a form to push them all as one commit: a message (default Web edit: <paths>), the target (main, or a new branch, after which the panel links to a pull request) and Push. When main is protected, only a new branch is offered (see below).
    • File tree: marks added, modified, moved and deleted files (and the folders holding them) from your unpushed changes. Right-click a file, a folder or the empty space below the files for New File, New Folder and Delete. New files are named in place and open in the editor. Delete asks first, for a file or a folder (which deletes every file in it); right-clicking inside a Ctrl/Cmd multi-selection offers Delete (N) for all of it. Drag files and folders (or a multi-selection) onto a folder to move them; drop on a top-level file to move back to the root. Moves and deletions show in the Changes panel, where Restore undoes them, and are pushed with the rest. A move never reads the file, so untouched, binary and large files move too. A new folder stays in the browser until it holds a file, since Git can't store an empty folder.
    • More tree actions: Rename from the menu or F2 (a rename is a move, so it pushes the same way); Delete and Cmd/Ctrl+Backspace delete the selected rows after the same confirmation; Upload files… in the menu, or drop files and folders from your computer onto the tree. Text uploads become editable drafts; anything else is pushed as a binary file (up to 10 MiB each, 25 MiB and 100 files per push), and an upload onto an existing path asks before replacing it.
    • Changes panel: each change has Discard (an edit goes back to the committed text, an added file or upload leaves the tree) and the panel has Discard all, both confirmed.
    • Unpushed changes are kept in the browser, per app and branch, in the virtual checkout that backs the file tree, editor and Changes panel. They survive a reload and a tab restart and last until you push or Discard all — nothing expires. Tabs open on the same branch share one workspace and can all edit it: each tab sends its edits, uploads, moves and discards to the others over a BroadcastChannel as they happen (last write wins on the same file). One tab saves the workspace to IndexedDB; a tab that opens later has it flush, restores the saved copy, and takes over saving when that tab closes, including the edits it made in between. node_modules and each tab's own Preview stay per tab. Nothing reaches the server until a push. If the branch moved since, the panel says the changes are based on an older commit.
  • Preview panel in Code mode (?panel=preview), beside History and Changes: the app runs in the browser on the workspace checkout and is shown in an iframe. It runs the dev script when the app has a usable one and no native-only dependency (@cloudflare/vite-plugin, wrangler, miniflare and workerd cannot run in the browser), otherwise a Worker entry bundled with Vite (no plugins) and served through srvx, a Vite build for a Cloudflare Vite app that builds without the plugin, or a static-asset server with its SPA fallback. A bar at the top shows the preview's state, an address bar that opens a path or URL in the frame (the preview's own host only; anything else is refused) and Restart; the plan's notes sit behind an info icon by the title, with errors verbatim below; the run's output is in the Terminal. Previews run from a separate per-app origin, never the control UI's origin — the whole pod, checkout and all, lives in a document there, and if that origin does not connect the preview stops with an error instead of falling back — so app code can't act as the signed-in viewer. D1, KV, R2 and Durable Objects run locally in the browser through Poder (SQLite-backed, persisted across reloads): a single location with no output gates, which the panel notes. Secrets never reach a preview, and WebSocket upgrades do not reach the preview host.
  • Terminal in Code mode (?terminal=1), opened from the button at the bottom of the file tree: a shell drawn by ghostty-web (MIT, in NOTICE) under the editor and the right panel (2:1), running in the same browser pod as the editor and Preview at the checkout's root. The tree, editor and Changes panel follow files the shell creates, edits, moves or removes. The running preview is the terminal's foreground job: its output streams into the shell and Ctrl+C stops it and returns the prompt. The shell has a preview command (preview, preview stop, preview restart) that drives the same session as the Preview panel. Hiding the panel keeps the shell and its scrollback.
  • Pull requests, for same-repo branches:
    • Pages: pull requests are called "pulls" in the UI and have their own pages, opened from the app's Pulls item in the sidebar, which shows the open count. The list (/apps/{id}/pulls) filters open, closed and merged pulls; its title repeats the open count and carries "New pull" (/apps/{id}/pulls/new). Each pull is at /apps/{id}/pulls/{number}. Open one from the Compare page or after pushing browser edits to a new branch. The pull page has a Conversation tab (description, comments, reviews) and a Files tab where you comment on a diff line. When later commits change that line, the comment is marked "Outdated" and stays in the conversation.
    • Reviews: anyone with push access, except the author, can approve or request changes. A new push to the branch dismisses approvals given on older commits.
    • Squash merge: one commit on the base branch, authored by the PR's author, which deploys when the base is main. You can delete the branch afterwards. A PR with conflicts can't be merged and lists the conflicting files; fix them locally and push.
    • Branch protection (Settings, admin only, off by default): "Require a pull" keeps the push role off main (git push, browser edits and branch deletes), and "Required approvals" (0–2) gates merging. Admins bypass bo...
Read more

v0.1.0-alpha.3

Choose a tag to compare

@github-actions github-actions released this 06 Oct 17:12

Opt-out instance telemetry, storage editors, the admin home, and a pre-release flow for releases.

Operator action required

  • RUNNER_BUILD_UID and RUNNER_BUILD_GID are removed and ignored if still set: delete them from your environment. Builds and release commands now run as one uid per app from a reserved range, RUNNER_BUILD_UID_BASE (default 10030) and RUNNER_BUILD_UID_RANGE (default 1024). The image pre-creates the default range in /etc/passwd; a custom range needs matching /etc/passwd and /etc/group entries, must stay below 65534 and must not contain RUNNER_FLEET_UID (10020).
  • Expect a one-time telemetry replay after the upgrade. The runner re-reads the stored telemetry for the whole retention window (RUNNER_TELEMETRY_RETENTION_DAYS, 30 by default) for every app, to recover the rows the pre-alpha.2 ingest bug skipped. It runs in the background with no downtime; the cost is extra bucket reads and DuckDB CPU in proportion to window × apps. The runner logs telemetry replay started and telemetry replay complete. Telemetry older than retention was already pruned and cannot be recovered.
  • Both databases upgrade themselves: the runner SQLite goes to schema version 5, the control D1 to 1.4.0. As before, an older image refuses newer data, so back up before upgrading.
  • This release adds anonymous instance telemetry, and it is on by default. Once a day the instance sends one count-only heartbeat: the version, platform, storage kind, numbers of apps, deploys and users (users bucketed), install age and uptime. It is keyed by a random install id; no domains, names, emails or IPs are sent, and nothing at all is sent from a local domain. To turn it off, untick it in the Admin section of /account, or set NOITE_TELEMETRY=0 (or DO_NOT_TRACK=1) in .env, which also locks the checkbox. The full list of fields is on Telemetry.
  • A malformed RUNNER_TELEMETRY_RETENTION_DAYS now stops the runner at boot with a config error. Before, it silently fell back to 14 days, though the documented default is 30. Unset still means 30.
  • Runner REST API callers: status codes and one response changed; see Changed. Scripts that matched on 409 for storage or source errors, sent extra fields to PATCH /v1/apps/{id}, or read remote from the git-remote response need updating.

Added

  • Anonymous instance telemetry. The runner sends PostHog EU one instance_heartbeat event a day: no SDK, GeoIP lookup disabled, no person profiles. An Admin section on /account, shown to instance admins only and never while impersonating, turns it on or off and shows the exact payload. The runner API has GET/PUT /v1/admin/telemetry and RPC telemetry.get/telemetry.set. The installer prints a notice and accepts NOITE_TELEMETRY. Dev and e2e stacks never report.
  • New login page: a product panel ("Push code. Get a URL.", three benefits, a deploy preview) next to a clean sign-in form, in light and dark, with a mobile layout. Sign in is the default tab; a fresh instance opens on "Set up Noite" and says the first account becomes the admin. Lost-passkey recovery is a separate view.
  • First-run onboarding: a three-step tour after sign-in (push to deploy, one API key for Git and the CLI, stay in the loop: GitHub releases, Discord, GitHub Sponsors). Story-style progress bars, Back from the second step, arrow keys to move between steps, and a bottom sheet on mobile. The tour never navigates away: steps 1 and 2 spotlight the sidebar's Apps item and your avatar instead (skipped on mobile, where the sidebar is hidden). "Get started", × or Esc ends it for good. Accounts created before this release see it once.
  • Admin home: /apps lists the control plane ("Noite · admin") for instance admins, and its page holds every admin tool: Overview, Metrics, Errors, Logs, Users, Apps and Invites tabs. Users, Apps and Invites are the panels god mode used, with the same actions and URL-kept searches. /god-mode and the account menu's "God Mode" item are removed. The whole page, and every admin action behind it, is refused while impersonating.
  • D1 table editor (Storage → D1, and the control database on the Noite admin page):
    • Layout: a table list with row counts, a toolbar with search, filters, sort, refresh and Insert, Data and Definition views, and a side panel for editing rows. Paging, sorting, filtering and search run in SQL on the server, so they cover the whole table, not just the first rows.
    • Row editor: shows each column's type, primary key, NOT NULL, default and foreign key. NULL and the empty string are separate values, and an insert leaves untouched columns to their defaults. Helpers fill in "Now" timestamps, format JSON and toggle 0/1 columns; a foreign key opens the referenced row.
    • Editing: changed fields are highlighted and only they are saved. Delete asks for confirmation inside the panel, rows can be deleted in bulk (1–100 at a time, in one transaction), and a toast confirms every write.
    • Control database: read in-process by the control worker, never through celld d1 execute.
      • Masked: secret columns never leave the worker (session tokens, account passwords and OAuth tokens, verification codes, passkey keys, API keys). They can't be filtered, sorted or searched, so a secret can't be guessed piece by piece.
      • Read-only: the auth tables, except that deleting a session row revokes it. user.role and the ban columns are managed in the Users tab. Tables not explicitly allowed are read-only too.
      • Your own row: you can edit it but not delete it.
      • Links: a user row links to "Manage in Users", an invite row to "Manage in Invites".
  • R2 browser (Storage → R2):
    • Browsing: breadcrumb navigation, folders, search within the current folder, list and column views, and pages of 100.
    • Details panel: an image or text preview, type, size, last modified and ETag, plus Download, Copy URL and Delete.
    • Uploads and folders: upload files (up to 64 MiB each) and create folders through celld r2 put, so an object carries the same record a Worker's env.BUCKET.put() writes, and the Worker reads uploads back as-is. Deletes go through celld r2 delete, including bulk deletes of up to 100 objects.
    • Safe previews: downloads use the object's own content type with nosniff, and SVG, HTML and XML are sandboxed.
  • Durable Object view in the same style: instance search and a read-only panel showing the state each instance's handler reports, as a key/value table.
  • Control-plane insights: the control fleet now writes celld telemetry under s3://<bucket>/control/telemetry/, and the runner compacts, ingests, prunes and error-groups it like a tenant app's. Rows are keyed on the reserved _control slug with no app row, so reconcile, Caddy, scale-to-zero, purge and GET /v1/apps never see it. The Noite app page gains Overview, Metrics, Errors and Logs tabs (Logs include the control node's own output); instance admins only, never while impersonating. The numbers include the dashboard's own polling. noite-runner telemetry reingest --slug _control replays its window. make dev serves the UI from vite, not celld, so the page says telemetry needs the release image instead of showing empty charts.
  • Per-app build uid: builds and release commands run as the app's own uid (app.build_uid, allocated on its first build), and its worktree and build cache are 0700. The hostile-tenant e2e suite now checks that a build cannot read a sibling app's worktree or cache.
  • noite-runner telemetry reingest [--since <RFC3339|duration>] [--slug <slug>] [--no-wait] [--timeout <seconds>]: replay a telemetry window into metrics.sqlite, bounded by retention. Safe to repeat.
  • install.sh warns before an upgrade that recreates the container, and asks once when it has a terminal. --yes, NOITE_YES=1 or NOITE_CONFIRM=1 skip the prompt; a fresh install, or a re-run whose image did not change, asks nothing.
  • Boot-phase log lines with elapsed milliseconds (tenant fleets booting; control UI deploys next, control bundle ready).
  • Docs link in the account menu.
  • GitHub Sponsors button on the repository (.github/FUNDING.yml).

Changed

  • Re-running the installer fills in any required .env setting that is missing or still at a dev default, instead of leaving the runner to refuse its config: the domain settings, ports, runner token, auth secret and RustFS keys. Values that are set are never changed, and the installer lists what it added. A missing BASE_DOMAIN is now asked for (or taken from NOITE_DOMAIN). Before, the installer stopped with "fix it or move it away".
  • Telemetry ingest is idempotent and resumable. It works in hour-aligned chunks and replaces each window's minute buckets, span stats, log lines and error buckets instead of adding to them, so a retry, a catch-up after downtime or a replay never counts twice. After downtime the gap is read hour by hour, oldest first, bounded by retention.
  • Telemetry ingest isolates a failing app: healthy apps keep aggregating while the broken one retries after a backoff. Apps that stopped (scale-to-zero, crash, runner shutdown) still get their remaining complete hours aggregated.
  • Error issue counts are derived from the retained hour buckets, and logged-error capture is capped per app per hour (200) instead of per ingest pass.
  • Boot order: signal handling, then Caddy, then every tenant fleet, then the REST listener, and only then the control UI deploy. A release that changes the UI bundle no longer delays tenant apps. Measured locally, 1, 5 and 15 apps all serve again within 5.1 s of the new container starting, and requests during that window are held at the edge, not refused.
  • The nft egress policy matches the whole build uid range instead of a single build uid. The image no longer has a shared `bui...
Read more