In progressFailed#5Load product images through the new loader7c41e09now31sVersion #4 kept serving
Live#4Tune image loading on the case studies page9b3e0d61d ago24s
Succeeded#3Add the contact form's spam check2f7a8c1Oct 2224s
Succeeded#2Bump Next.js to 15.2 and fix the sitemap route5e9d0b3Oct 1524s
Succeeded#1Rework the homepage hero sectiona1c4f92Oct 824s
#5 starts beside #4#5 starts beside #4Noust asks it for the home page every 2 s
#5 answers 500 for 30 s#5 answers 500 for 30 sThe check fails and no visitor is sent to it
#4 is still live#4 is still liveEvery request was served by #4
Three measured facts
52s
from apt install to a first app answering over HTTPS, in our test
apt install 22.1 s
setup 19.4 s
the first app and its certificate 10.4 s
Noust 3.3.1, before 4.0, in a Debian 13 container, on a 14-core laptop, 20 Oct 2026. The package came from a local file and Pebble, Let’s Encrypt’s test server, issued the certificate, so a real server takes longer.
On 28 and 29 Oct 2026 we started each recipe on a clean server, with the development code that followed Noust 4.0.0, and every one passed its health check. A recipe's whole test, image download included, takes 54 s at the median.
From packages: Debian 12 and 13; Ubuntu 22.04, 24.04 and 26.04; Fedora 43; openSUSE Leap 16.0 and Tumbleweed. With pip: Rocky Linux, AlmaLinux, RHEL, CentOS Stream and Oracle Linux, versions 9 and 10.
From a repository to HTTPS, a database for it, a broken release that never takes traffic, and every server in one console. Recorded as it happened in the console, with no sound.
What the video shows
On the central, Marta starts a new application on fra-1 and types the repository kelmoor-labs/tidewick-site. Noust recognises a Next.js site; she types tidewick.io and the console checks that it points at the server.
She deploys it and follows it live: fetch, install, build, start, and a check that it answers. tidewick.io is running, over HTTPS.
She creates a PostgreSQL database for it. Noust links it as DATABASE_URL.
She finds the shop with the search. Iker pushes a change to shop.brumaria.es: the release never answers its startup check, so the app goes back to version #21 and the shop keeps running.
From the server selector, the whole fleet: fra-1, ams-3 and lon-2 answering, what needs attention on each, and their CPU, memory and disk side by side.
Every deploy is health-checked, and a failed one is undone
Each release is built beside the live one and only replaces it once it passes its health check. That holds for Node.js, Next.js, Python and Vite apps created since 4.0; the others are below.
An animation of two deploys of wrenfield.io in a row. Visitors keep arriving through nginx at the live release, #3. Release #4 is built beside it on its own port, and Noust asks it for the home page every 2 seconds; it answers 200 three times, so nginx sends new visitors to #4, and #3 finishes the requests it had and stops. Then release #5 is built beside #4 and checked the same way; it answers 500 three times, so it is marked failed and stopped. No visitor ever reached it, and #4 kept serving without a restart.
1Built beside the live release2Health-checked on its own port3If it passes, it gets the traffic4If it fails, #4 keeps serving
Visitors
nginxwrenfield.io
#3:3001LiveFinishingStopped
Add the contact form's spam check
#5:3001FailedBuildingChecking
No visitor reached itLoad product images through the new loader
#4:3000LiveBuildingChecking
Never restartedTune image loading on the case studies page
Health checkevery 2 s
500200500200500200
GET /GET /GET /500200500200500200
1
1. Built beside the live release
The new release gets a directory of its own and is built by an unprivileged account, noust-build. The live one keeps serving.
2
2. Health-checked on its own port
Noust requests the home page every 2 s for up to 30 s, and any HTTP status below 500 passes. You can change the path, the statuses and the timeout.
3
3. If it passes, it gets the traffic
nginx sends new requests to the new release, and the old one finishes the ones in flight, then stops.
4
4. If it fails, #4 keeps serving
#5 is stopped and #4 never restarted. The deployment keeps the check’s output and the journal.
What a failed deploy leaves
The health check’s output as Noust writes it, and the last lines of the app’s journal, as the page of deployment #5 shows them.
The application did not answer the health check.
Health check attempts 1-15 failed: HTTP 500 is not an expected status
Last lines of the journal of wrenfield-io.service:
Oct 29 10:16:02 fra-1 wrenfield-io[52114]: Error: Cannot find module './lib/image-loader'
Oct 29 10:16:02 fra-1 wrenfield-io[52114]: Require stack: .next/server/app/page.js
Other apps are health-checked too
A static site switches one link and a PHP app reloads PHP-FPM, with nothing restarted. Other apps that keep their earlier releases, and Compose stacks, are restarted and checked; if the check fails, the previous release comes back. Apps deployed with Noust 1.x, which keep no earlier releases, are checked and only report a failure.
Going back to #3 means pointing current at its directory again: a few seconds, nothing rebuilt.
The directory of wrenfield.io on the server: three releases, #4, #3 and #2, and current, a link that points at the live one, #4. noust releases rollback wrenfield.io moves current to #3, and #3 is live: nothing is rebuilt. Beside them, shared/.env, one file with mode 0600 for every release, and repo/, the git cache.
$noust releases rollback wrenfield.io
Rolled back to release 20261022-141133-2f7a8c1 of wrenfield.io
/var/www/apps/wrenfield-io/
releases/
20261028-031502-9b3e0d6/#4
20261022-141133-2f7a8c1/#3
20261015-094410-5e9d0b3/#2
currentlive
shared/.env# mode 0600, one file shared by every release
repo/
Noust puts code back, never a database
If a release ran a database migration, Noust asks you to confirm before going back past it, because the schema stays as the migration left it: the code goes back, the database does not.
Set a limit on disk, memory, errors or response time. When a server stays past it, Noust tells you on Slack, by email or on 11 other channels, and again when it clears.
Read the guide
Disk on ams-3Last hour
NormalFiring
disk-almost-full: above 85% for 10 min
100%85%50%0%
10 minFired at 91%Resolved at 63%
10:0010:1510:3010:4511:00
#opson Slack
NNoust App
disk-almost-full is firing on ams-3Disk at 91% for 10 min; the limit is 85%.10:40
MMarta acknowledged the alertReminders stop while she works on it.10:43
Cleanup on ams-3 freed 11.4 GBRun by Marta: 4 old releases and 6 database dumps removed.10:47
disk-almost-full resolved on ams-3Disk at 63%, back under the limit.10:49
Let’s Encrypt for every domain, renewed with 30 days to go. If one gets within 14 days of expiring, you get an alert.
Read the guide
Certificates on fra-1
Today 26 Oct27 Oct28 Oct29 Oct
alertrenews0143090 days
wrenfield.ioValidRenewingRenewedExpires 26 Nov, in 31 daysExpires 26 Nov, in 30 daysExpires 25 Jan 2027, in 90 daysExpires 25 Jan 2027, in 89 daysExpires 25 Jan 2027, in 88 days
api.kestrelworks.ioValidExpires 23 Dec, in 58 daysExpires 23 Dec, in 57 daysExpires 23 Dec, in 56 daysExpires 23 Dec, in 55 days
blog.nortewave.comRenewal failedExpires 11 Nov, in 16 days; the name points to another serverExpires 11 Nov, in 15 days; the name points to another serverExpires 11 Nov, in 14 days; the name points to another serverExpires 11 Nov, in 13 days; the name points to another server
Sent to #ops on Slack: blog.nortewave.com expires in 13 days and could not renew.
With your DNS provider connected, adding a name creates its record. Noust checks it from public resolvers, sets up the web server and adds the name to the certificate, without restarting the app.
Read the guide
www.tidewick.ioAlias of tidewick.io on fra-1
DNS recordWaitingCreating it at CloudflareA 203.0.113.10, made at Cloudflare
Resolves hereWaitingAsking 1.1.1.1 and 8.8.8.81.1.1.1 and 8.8.8.8 answer 203.0.113.10
SiteWaitingTesting nginxnginx -t passed; reloaded
CertificateWaitingAdding the nameCovers tidewick.io and www.tidewick.io
The app, its .env and its databases, encrypted and sent to S3, R2, Backblaze or SFTP. It keeps the newest seven, and every 30 days it restores one as a test.
Read the guide
wrenfield.io on fra-1
Every night at 03:00
Tonight’s backup
release #4
.env
wrenfield_prod, 2.1 GB
Next at 03:0003:00:02 Dumping the wrenfield_prod database03:00:29 Sending to vault-r2Sent at 03:00:41, 2.3 GB
Keep your server’s PostgreSQL 16 and run 17 beside it, from the official image pinned by digest, listening only on 127.0.0.1.
Read the guide
Versions Noust can run beside the server’s own
PostgreSQL
12131415161718
MySQL
5.78.08.49.7
MariaDB
10.610.1111.411.8
Redis
7.27.48.08.4
Valkey
7.289
KeyDB
6.3
ClickHouse
25.826.326.8
MongoDB
6.07.08.0
PostgreSQL also with pgvector and PostGIS. Official images, listed from each registry on 6 Oct 2026.
Engines on fra-1
PostgreSQL 16.4RunningFrom the server’s packages, port 5432
MySQL 8.0.39RunningFrom the server’s packages, port 3306
Redis 7.2RunningFrom the server’s packages, port 6379
PostgreSQL 17postgresql@pg17, from the official image pinned by digest, on 127.0.0.1:5433 with 512 MBPulling the imageStartingRunning
17
206 apps that install with one command
Nextcloud, Immich, Grafana, Vaultwarden and 202 more. Each recipe pins the exact version of its images. 199 of them were started on a clean server on 28 and 29 Oct 2026 and passed their health check; 7 are still waiting for that test.
One Noust, the central, drives the others over SSH tunnels it opens itself. It never gets a shell on them, and each server sets how much the central may do.
The central never gets a shell
Its key can forward one port and nothing else: the forced command is /usr/bin/false, set both in sshd and on the key.
Each server sets a ceiling for the central
Read, deploy or admin, chosen on the server with noust fleet authorize and enforced there.
A changed host key stops the tunnel
Each server’s key is recorded when you add it. If it ever changes, the tunnel closes and says why.
An animation of the sample fleet. fra-1, the central, opens an SSH tunnel to each of ams-3, lon-2 and mad-1; ams-3 lets it administer, lon-2 and mad-1 only deploy. The central sends an update to all three. palenbrook.io on ams-3 and quillfield.io on mad-1 pass their health checks and go live; on lon-2, release #14 of staging.wrenfield.io fails its check and #13 keeps serving. The renewed certificate of quillfield.io on mad-1, a backup sent from lon-2 and a disk alert at 91% on ams-3 all reach the console on fra-1. Then mad-1 stops answering and its tunnel closes; the central opens it again and mad-1 answers.
fra-1Central, Frankfurt
Every server answeredOpening tunnels1 not answering
fra-13 tunnels open
fra-1Updating 3 servers2 of 3 servers updated
ams-3palenbrook.io #10 is live
mad-1quillfield.io #8 is live
lon-2#14 failed; #13 kept serving
mad-1quillfield.io certificate renewed
lon-2Backup sent to vault-r2
ams-3Disk at 91%
mad-1Not answering
mad-1Tunnel reopened; it answered
ams-3Amsterdam
Not answering
palenbrook.io
#10#9LiveStopped#10CheckingLiveFailed
Disk91%57%
Ceiling: admin
lon-2London
Not answering
staging.wrenfield.io
#13LiveStopped#14CheckingLiveFailedBackup sent
Disk18%
Ceiling: deploy
mad-1Madrid
Not answering
quillfield.io
#8#7LiveStopped#8CheckingLiveFailedRenewed
Disk42%
Ceiling: deploy
SSH tunnel, no shell
Two people for anything root, and a record nobody can quietly rewrite
Roles decide who can do what. Turn on approvals and anything that amounts to root waits for a second person. Every change goes into an audit log chained line by line and copied off the server.
An animation of an approval and the audit log on fra-1. Marta, an admin, asks for a root shell on fra-1, and the request waits for a second person. Sara, from security, approves it, and the shell opens. Each step is written to the audit log of fra-1, every line chained to the one before by its HMAC, and each line is copied to the SIEM, off the server, as it is written. Then, in that root shell, Marta rewrites line 62 of the log with sed so that it names sara instead of her. Sara runs noust audit verify: it checks the chain from the top, passes the lines before 62 and stops at line 62, which was changed after it was written. The copy of line 62 in the SIEM, off the server, still says marta.
M
MartaAdministrator
S
SaraSecurity officer
noust audit verify
fra-1Frankfurt
Approvals
Nothing waiting
Root shell on fra-1
Asked by MartaApproved by SaraNeeds a second person
A root shell, a hand-written unit or nginx file, a deploy hook or a new role waits until someone from security approves it, and that someone is never the person who asked.
Root on the server holds the key and could rebuild a consistent chain. The copy Noust sends to syslog or your SIEM as each line is written is out of its reach, and still says what happened.
Installing and building run as noust-build in a systemd sandbox. If Noust cannot prove the sandbox holds, the build stops instead of falling back to root. Building an app as root is a decision on record and, with approvals on, takes a second person.
noust-buildBuild of wrenfield.io #5
Reaches
releases/#5
its build cache
the variables of its .env
Cannot see
/root
/etc/noust
other apps
Noust’s database
Reaches: releases/#5, its build cache, the variables of its .env. Cannot see: /root, /etc/noust, other apps, Noust’s database.
Every release has a signed git tag, and the installer publishes a signed SHA256SUMS. The Python packages of any commit rebuild identical, byte for byte. Verify a release
Report a vulnerability privately
Open a private advisory on GitHub, or email support@noust.dev with [noust security] in the subject. We confirm receipt within 3 working days and agree on a severity within 10. Open a private advisory
Anything the console does, you can script
The console is a client of the same JSON API your scripts call, and each page shows the equivalent command.
# build #5 beside #4, check it, switchnoust update wrenfield.io# every release on disk, the live one markednoust releases list wrenfield.io# point current at the previous release, in secondsnoust releases rollback wrenfield.io# the app’s journal, credentials maskednoust logs wrenfield.io --follow# a recipe, health-checked like any appnoust recipe install immich -d photos.kelmoor.dev
One command per action, with the names the console uses.Every command
# Queue an update. The answer is 202, with the job to follow.curl -X POST https://fra-1.kelmoor.dev/api/jobs/update \
-H "Authorization: Bearer $NOUST_TOKEN" \
-H "Content-Type: application/json" \
-d '{"domain": "wrenfield.io"}'# Every route the console uses, in one schema.curl -H "Authorization: Bearer $NOUST_TOKEN" \
https://fra-1.kelmoor.dev/api/openapi.json
Tokens with a scope and an owner, and an OpenAPI schema at /api/openapi.json.The API
# noust.yaml, at the root of the repositoryhooks:pre_deploy:
- run: ./node_modules/.bin/prisma migrate deploy
timeout: 600
# rolling back past this release asks you to confirmmigrates: true
post_deploy:
- run: ./scripts/purge-cache.sh
You can also keep it in the repository, in noust.yaml. The schema is closed, so an unknown key is rejected with the name of the field.Deploy hooks
import os
from noust_sdk import Client
client = Client("https://fra-1.kelmoor.dev", token=os.environ["NOUST_TOKEN"])
for app in client.applications.list_apps()["apps"]:
print(app["domain"], app["status"])
# waits for the health check to finish; raises JobFailed if it fails
queued = client.applications.create_app({
"domain": "shop.brumaria.es",
"source": "https://github.com/kelmoor/brumaria-shop.git",
})
job = client.wait_for_job(queued)
Applications, domains and databases as code, for Terraform and OpenTofu.The provider
name: Deploy
on:push:branches: [main]
jobs:deploy:runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
# waits for the health check and fails if the deploy does
- uses: Perkybeet/noust/packaging/github-action@v4.0.0
with:url: https://fra-1.kelmoor.dev
token: ${{ secrets.NOUST_TOKEN }}
app: wrenfield.io
It queues the deployment, prints each step and, if the deploy fails, ends with the server’s error message.Deploying from CI
How Noust compares with the platforms you know
Seven platforms, the same questions. Every answer has its source.
Yes
Partly, or on a paid plan
No
Doesn’t apply, or not documented
Select an answer to see where it comes from.
Noust and seven other platforms, question by question
We read each platform’s own documentation, pricing and code on 29 Oct 2026, and Noust’s answers come from its documentation for 4.1. If something has changed, tell us in an issue.
Free and open source, with every feature
Everything Noust does is in the free version, single sign-on and the audit log included.
Community
€0
AGPL-3.0, and every release stays under it
Every feature: the fleet, SSO, SCIM, roles, the audit log and the ENS profile
As many servers and apps as you like
Signed packages, and updates through apt, dnf or zypper
Short answers. Each one links to the full explanation.
Can I use it on a server that already runs my apps?
Yes, without rebuilding them. noust app discover shows the nginx and Apache sites Noust did not write, and adopting one puts it in the console with its state, logs and restarts. A Compose stack is adopted as it runs, with noust app adopt. An adopted site has no deployments yet, so there is nothing to roll back until you deploy it from its source.
The service does: it installs packages and writes nginx, systemd and certificate files. For new apps, builds, installs and deploy hooks run as noust-build, an account without privileges, in a systemd sandbox, and each app runs as an account of its own.
Not for your own apps: Noust runs Node.js, Python, PHP, Go and static sites directly, as systemd services behind nginx or Apache. Docker is needed only for Compose stacks, container apps and most recipes.
How big a server does it need, and what does it install?
To start, 1 GB of memory, 1 processor and 10 GB of free disk; 2 GB and 2 processors is comfortable, and below 512 MB of memory or 3 GB of disk the installer stops. It adds nginx, certbot, git and Node.js. Idle, Noust itself uses about 290 MB of memory: the console and the monitor, measured on Ubuntu 24.04.
They keep running. Each one is a systemd unit, an nginx or Apache site, a directory and a .env file, and systemctl, journalctl and git are enough to run them without Noust.
No. There is no telemetry. The only request Noust makes on its own is the update check. The other side sees your server’s IP address and nothing else. noust config set updates.check false turns it off.