Noust 3.2 changelog
What changed in 3.2.
Changes since Noust 3.1.17. Upgrade notes are in UPGRADING-3.2.md: read them before upgrading a server that deploys Docker Compose projects or builds as root, and before upgrading a fleet.
3.2 makes Docker Compose projects first-class (deploy hooks, updates without a cut, adopting a stack that already runs, a copy of its database before every update) and shows the web server as a structure and a diagram instead of only as text. It does not change an application until its operator asks, with the exceptions listed under What changes on its own.
Deploy hooks
Until 3.1 a project could not say what to run around its own deploy, and Prisma's automatic migration, the one thing that did run, treated a failure as a warning: the new code went live against the schema it was not written for.
noust.yaml. A file at the root of the repository (or.noust.yaml) declareshooks.pre_deployandhooks.post_deploy. Each hook hasrun, and optionallyservice,workdir,timeout(1 to 3600 seconds, 600 by default) andmigrates. The schema is closed: a key Noust does not know, a path that climbs with..and a link instead of a file are refused, and the error names the field. The same file can saybackup.databases(see below). compose.md has the whole schema.- Never a shell.
runis split into an argument list and run through the command runner like every other process; a pipe or a redirection goes in a script you commit. - Where a hook runs. In a Compose stack, in a one-off container of the image the deploy just
built, with the service's own environment and network (
docker compose run --rm --no-deps), before anything is recreated. In every other application that serves, in the release phase of the build tree, with the application's identity and its.envwhen it builds in the sandbox, and as Noust itself while it still builds as root, as its migrations always did. A monorepo runs them at the root of the repository, after the build and before any unit restarts. A static site has nothing to run them in, and declaring hooks for one is refused with the reason. pre_deployaborts. The first hook that fails, by exiting non-zero or running out of time, stops the deployment before it moves any traffic: what served before keeps serving, and the error carries the hook's own output.post_deploywarns. It runs once the new version has passed its health gate. A failure leaves the deployment deployed with warnings (recorded as such in the history, with the hook's output) and sends the newdeploy_hook_failednotification, because undoing a version that already serves is worse than saying so.- The operator's hooks.
noust app hooks set DOMAIN --file hooks.yaml,PUT /api/apps/{domain}/hooksand the application's Settings store a document for one application that replaces the repository's whole: they are never merged, so what runs is always one document someone wrote.noust app hooks showsays where the hooks in force come from. Writing them needsroot_equivalent, sudo mode and, when approvals are on, a second person: a hook is code that runs with the application's secrets. - Prisma's automatic migration aborts on failure. When a project declares no hooks and
uses Prisma,
prisma migrate deploystill runs for Node and monorepo applications, and its failure now stops the update exactly as a failingpre_deploydoes. When hooks are declared, the automatic migration does not run: what is written down wins, and the log says so.
Going back past a migration
- Each deployment records whether it changed the database's schema. A hook marked
migratesthat succeeded, or Prisma applying at least one migration, marks it. A hook markedmigrateswhose output says nothing was applied (in Prisma's, Django's, Laravel's, Knex's, Sequelize's, TypeORM's, Doctrine's, Flyway's or golang-migrate's own words) does not. - Going back asks first.
noust rollback,noust releases rollback,noust update --commitand a restore of only an application's files refuse to go back past a deployment that changed the schema, name the deployments, and say what to do: restore the database from the copy taken before them, or pass--schema-changed-okto say the older code runs against the schema as it is. The API answers409 schema_changedwith the deployments and takesschema_changed_ok, and the console shows a dialog that names them. Noust puts code back, never a database. - The automatic go-back after a failed health gate still happens, because the alternative is an outage. When the failed attempt had changed the schema, the first line of the error says so and the history marks the deployment.
- Deployments in the API and the console carry
schema_changedandschema_changed_between, so the warning shows before the button is pressed.
Docker Compose without a cut
Until 3.1, docker compose up -d recreated each web service by stopping its container and
starting a new one: for as long as the new one took to listen, nginx had nobody on the port.
- A relay.
noust app zero-downtime DOMAIN onnow accepts a Compose stack. An update starts a twin of each web service from the new image on a free loopback port, runs the health gate against it, points nginx at it, recreates the real container, runs the gate against that, points nginx back, and removes the twin. Services go independs_onorder, so a new front end never talks to an old back end. In the project's harness, a probe every 200 ms saw 0 failed requests of 110 through a relayed update, against 72 of 94 through a plain recreate. - A twin that does not start touches nothing. The image failing to start is the proof, and
the container that serves is never recreated. A recreated container that does not answer
leaves the twin serving and the error says which one serves. A twin left by an update that was
interrupted is resolved first, and
noust setup doctorreports one it finds. - Requirements, checked when it is turned on, each with its fix: a fixed host port on each
web service, and a servers file the site reaches it through,
/etc/nginx/noust-upstreams/<app>/<service>.servers, which the site Noust writes includes by itself. A site you wrote yourself must include it, and turning the mode on says the exact line. See compose.md.
Adopting a stack that already runs
noust app adopt DOMAIN --path DIR (and POST /api/apps/adopt, and the new-application page)
registers a stack someone brought up by hand, such as one run by its own deploy.sh under
/opt/<name>, without touching it.
- Nothing is cloned, cleaned, rebuilt or restarted. The Compose project name is read from the
running containers' labels and kept; every later command and the unit pass it with
-p. - Noust proves it would change nothing:
docker compose up -d --remove-orphans --dry-run --no-buildmust say every container is already as it would be. Otherwise the adoption is refused with Compose's own output, unless you read it and pass--accept-recreate. - The site that serves the domain is found by the names it answers on and left as it is. When
its file is not named after the domain (
shopforshop.example.com), the file name is recorded, and the one place that turns a domain into a site file resolves it. - The unit is created and enabled, not started. The application is registered in place, at its real directory, which can be outside the apps directory.
- Deleting an adopted application never removes its directory unless you name it
(
--remove-adopted-directory PATH); its own site and the servers files it includes are kept and reported as kept.
The stack's database, copied before an update
The pre-update backup of a Compose stack held, at best, a copy of a running database's data files, which nobody can restore. Going back to the previous containers puts the previous code back and leaves a migration in the data.
- Detected and dumped. Services running the official
postgres,mysql,mariadbormongoimage (any tag) are dumped through the engine's own client inside their container, into the pre-update backup, compressed and0600. The user and database come from the service's environment as Compose resolves it; a password reaches the client from the container's own environment and is in no command line. Other images (bitnami/postgresql,postgis/postgis) are declared innoust.yamlunderbackup.databases. - On by default, and a failure stops the update. Without the copy there is no way back from
a migration. The error carries the engine's output and the command that turns it off:
noust app backup-before-update DOMAIN off, orbackup.databases: offinnoust.yaml. - Restore.
noust backup restore BACKUP_ID --databases-onlyputs a stack's databases back with the application stopped (pg_restore --clean --if-exists, and the other engines' equivalents); a full restore does it too when the backup holds them. noust backup createand the backup manager use the application's real directory instead of assuming<apps directory>/<name>, so an adopted stack can be backed up, and a stack's Docker volumes are found under their real names (project prefix andname:).
Your own site
3.1.17 respected a site you had written for applications deployed through releases. 3.2 does it for every path that writes or deletes a site.
- A site without Noust's marker is never rewritten and never deleted. Compose deploys and
updates, certificates, rollbacks, monorepos and
noust deleteleave it alone and say so. Obtaining a certificate for a Compose application used to delete the site and write a new one. - What Noust would need it to contain (its names, the servers file include, a certificate) is checked and reported as warnings with the exact fix.
- The list of sites (
GET /api/sitesand the console) shows every file in the web server's directory, not only the store's, each marked as written by Noust or by you, and with the application it belongs to. A hand-written site no longer disappears from the list as soon as the store has one. - See domains.md.
Aliases, redirects and certificates for Compose and monorepo
noust domain add refused Compose and monorepo applications. What a deployer does to render its
site, hold a certificate and cover every name now lives in one place, and Docker Compose and
monorepo applications use it: aliases, redirects and one certificate for all the names work for
them too (monorepo.conf.j2 gained the server names and the redirects). See
domains.md.
Ports and routes
- One reader of a Compose file's ports. The short and long forms, with or without an address
(IPv4 and
[::1]), ranges,/tcpand/udp, and values already resolved. The first-port reader failed on127.0.0.1:3000:3000and fell back to 3000. - The root route. With several web services and no
noust.nginx.yaml,/goes to the service in front (the one that depends on another web service and that no web service depends on; the first if there is none). Noust no longer invents a/<service>route for the others, which never worked for a Next.js front end; they are listed in a warning with how to route them. noust.nginx.yamlgains per-route and server-widemax_body_size,buffering: false,read_timeoutandsend_timeout,staticdirectories served straight from the application (withcache), andreturnfor redirects. The server-widerate_limitnow applies to every route without its own limit; it used to declare a zone no location used. The schema is closed and each value is checked against what its directive takes. See compose.md.
Workers without a web port
A Compose stack that publishes no port is not a web application that is down. There is one
definition of it (no service publishes a TCP port on the host), and the deploy, the store,
noust diagnose, the monitor and the summaries all use it.
- Such a stack is judged by its containers: well when every one runs or exited with 0 and none
restarts in a loop, broken when one exits with an error or restarts.
noust diagnoseshows the containers and the last lines ofdocker compose logs, not an HTTP probe of a port nothing listens on, and does not call the unit'sactive (exited)degraded: that is what a Compose unit looks like. - A stack that an earlier version registered with a port (WASM 1.x recorded the default) is
pointed out by
noust diagnoseandnoust healthwith the fix,noust app headless DOMAIN, which clears the port and, when Noust wrote its site and no alias uses it, offers to remove the site. Nothing does it by itself.GET|POST /api/apps/{domain}/headlessis the same.
Deploying by tag
noust create --follow-tags 'v*'andnoust app follow-tags DOMAIN 'v*'make an application follow the tags that match a glob instead of a branch. They are ordered by version, and never a tag older than the one deployed (it is ignored and recorded).- The webhook accepts, for those applications, a published GitHub
release(not a prerelease or a draft) and a push of a tag; a push to a branch deploys nothing. The deployment is the exact commit the tag points to. noust update DOMAIN --tag v1.2.3deploys one tag.noust app follow-tags DOMAIN --offgoes back to a branch.
The web server, at a glance
- A parser that loses nothing.
noust.managers.siteconfreads an nginx or Apache site into a tree that renders back byte for byte: comments, spacing and order included. Text it cannot read is an error with its line and column, never half a tree. It builds the model the console shows (servers with their listeners and certificates, locations with their targets, upstreams, includes, comments) and keeps every directive it does not model as raw, visible and editable. Edit operations change only the bytes of the element they name and keep the surrounding indentation. A routing function applies the web server's own algorithm to say which server and location answer a request, step by step. - The API.
GETandPOST /api/sites/{domain}/structure,POST .../config/edit,POST .../routeandGET .../topology(who owns each port, whether it answers, when the certificate expires). They write nothing: saving a site is still the onePUT .../config(root-equivalent, sudo mode, four-eyes, tested before it is written). - The site page has three views, Text, Structure and Diagram, sharing one draft and one save
bar. Structure edits a site visually (locations in the order the web server evaluates them,
inline settings, adding, duplicating, moving and removing locations from templates). Diagram
draws how a request travels, from the ports through the names and locations to the backends
and who holds each backend's port and whether it answers, with the flow animated by CSS and
still under
prefers-reduced-motion. Try a URL animates the path one request takes and explains each step of the choice. See console.md. - A site's configuration is tested inside the live one. The test used to wrap the site in an
empty
httpblock, so a site that used alimit_req_zone, amapor alog_formatdeclared innginx.conffailed while valid, and one that declared anupstreamanother site already declares passed and then broke the reload. Now the test copies the real main configuration with the enabled sites listed, this one replaced by the candidate. Apache is tested the same way.
Builds and accounts
- The build sandbox becomes the default for applications from before 3.1. On the next update
of an application that still builds as root, Noust first builds that exact commit in the
sandbox, as
noust app sandbox testdoes. If it passes, the sandbox is turned on and the update builds in it. If it fails, the update builds as root as it always did, the reason is recorded, and the application page,noust health,noust ens checkand a notification (underdeploy_failed) say so. Noust tries once;noust app sandbox testretries. An application withnoust app sandbox disable --reasonis left alone, and so is one on a server where the sandbox does not hold. An update that worked never breaks because of it. - Each application its own account. An application created from 3.2 runs as
noust-app-<name>, a system account with its own group, no home and no shell, that owns its tree,.envand caches; a PHP application's pool runs as it. One compromised application can no longer read or write another's tree. Existing applications keep the shared account until you move them:noust app identity status|migrate DOMAINtests the change behind the health gate and puts everything back exactly if the application does not answer. Docker Compose stacks, monorepos and static sites do not get one.
Monitor, notifications and the timeline
- Notifications when an application stops answering. The monitor asks every application
that runs a process what its deploy's health gate asks and sends
app_unreachableafter three failures in a row over at least a minute, once per outage, andapp_recoveredat the first success. A restart or a deploy never reports; nothing is probed while a deploy, update, rollback or job of that application runs, and an application stopped on purpose does not report. A stack with no published port is judged by its containers. Both are on by default. Until now onlyunit_failedexisted, which fires when systemd gives a unit up, so a web with its unit running that no longer answered notified nobody. deploy_hook_failednotifies a deployment that went live with warnings.- Process samples. Every minute the monitor keeps the five processes using most CPU and the five using most memory (pid, name, who owns it: a Noust application, a Compose service or a unit, the user, CPU, memory and a truncated command line with its secrets redacted) in the metrics database, for 26 hours, the retention of the per-minute metrics. History starts one minute after the monitor starts.
- One timeline for a stretch of time.
GET /api/timelinemerges the journal (warnings and worse), the audit trail, deployments, jobs, what the monitor saw and the process samples for a start and an end, optionally narrowed to one application. Each source keeps the permission it already had, and one that you may not read is named as withheld, not silently dropped. - Investigate this stretch. On an enlarged chart, a selection (or the visible range) opens that timeline in a panel, which answers "why this peak" with the processes that were running. Through a central it works on any server.
Jobs and the fleet
- A job that runs in a transient systemd unit survives a console restart. An operating
system update and Noust updating itself run in a unit of their own and record it; when the
console restarts, it reads how the unit ended instead of marking the job interrupted. Ended well: the job completes with the unit's output. Failed: it fails with the
unit's words. Still running: it keeps following it. A job with no recorded unit is marked
interrupted as before. This is why an
apt upgradethat included thenoustpackage, whose restart of the console used to mark the job failed, now reads as it ended. - A fleet's operating system update that includes Noust says so in the plan, installs Noust last (on apt and dnf, which can leave a package for a second command), and waits for the node's console to come back and for the job to say how it ended, instead of counting the node as failed and skipping the others. A node older than 3.2 marks the job interrupted; the central then waits for the node's own record of the update.
- The server detail of a fleet job shows what happened. The drawer for a server in a fleet job shows the job's log on the node, live while it runs, with the result in words (packages installed, versions, certificates renewed), a link to the job on that server, and "each application" only for the actions that are per application.
- Measuring storage is a read.
POST /api/server/storage/analyzeneedsserver.read(it wasserver.manage), so a central with thereadceiling can ask what takes the space. A refusal by a ceiling now names the lowest level that would allow the call.
The console
- A time zone selector. Server > System > Change time zone is a searchable list: by name,
city, abbreviation or offset (
madrid,+2,utc+1), grouped by region, each zone with its offset now and abbreviation, and the time the server will have with it. The list comes from the managed server (GET /api/server/clock/timezones; on a fleet, from the node) without the legacy aliases (posix/,right/,SystemV/,EST5EDT). - Activated or not, at a glance. Each page whose feature can be on or off (notifications, instant rollback, zero-downtime, automatic updates, builds, previews, scheduled backups, two- factor, approvals) shows the state as one header with a colour, a shape and a word: on, off with what that means and the action to turn it on beside it, or on with a problem (amber).
- Processes. "Show 50" works (the query was cached without its limit), the order is chosen by clicking a column header, ordered on the server, and the button says how many there are.
- Charts in a group. Pointing at one chart shows the moment on the others as a crosshair only; a click no longer freezes them all until the next one; the selection you drag is a translucent tint that leaves the readings visible.
- Application settings gain the hooks, the tags an application follows, zero-downtime for a Compose stack, the database copy before an update, the account it runs as and, in the new application page, adopting a stack.
- Spanish
.envplaceholders.genera-clave-hex-64-caracteres,cambiar,tu-andpon-in an.env.exampleare placeholders like their English cousins: never written verbatim, and a secret that a name says is hexadecimal is generated as hexadecimal of the length the name says.
Fixes
- Deleting a Docker image from Storage answered 500 and left the image deleted without an
audit record, with the console saying nothing had changed. The image id travelled as
targetnext to the audit record's own, which raised aTypeErrorafter the job was queued. The audit is recorded first, asimage. - A hook marked
migratesmarked every deployment as changing the schema when it succeeded, so each update of a project runningprisma migrate deploycounted, and a failed update said the database had been changed when nothing was applied. Found by running a production NestJS stack in the harness; the output is now read in the migration tools' words. noust --dry-run app adoptrefused every checkout: reading its origin was not a declared read-only probe.noust create --force -t docker-composeno longer empties a directory that holds data: it brings a git checkout that is already there up to date in place and says what it keeps. Without-t, automatic type detection still fetches before the Compose deployer is chosen, so name the type when the directory holds data.- A Compose stack's volumes are found under the names Docker gave them (project prefix and
name:); a backup used to look for one that did not exist and mount an empty one. - Docker Compose in the integration harness. The Compose scenario had never run: with no Docker in the container it printed a note and counted as a pass. It now skips explicitly, the harness image carries Docker, and the scenarios above, and a clone of a production stack, run for real.
Packaging
- The store moves to schema v13, one way. A copy of the store as it was is written beside it
first (
noust.db.v12-<time>-<pid>-<id>.bak); 3.1 refuses to open a store written by a newer version, and going back to 3.1 means restoring that copy. See UPGRADING-3.2.md. v13 adds the operator's hooks (app_hooks), what each deployment ran and whether it changed the schema, the Compose project, site name, tag pattern, database-copy setting and account of an application, when the sandbox was tried by itself, and the unit a job runs in. - No runtime dependency is new, in Python or in the console.
- New paths:
/etc/nginx/noust-upstreams/<app>/<service>.servers(the servers files; WASM's/etc/nginx/wasm-upstreamsis still read where it exists) and thenoust-app-<name>accounts. - The man page and the completions cover the new commands.
- The package documentation adds compose.md and UPGRADING-3.2.md.
3.2.1
- A Compose stack with the zero-downtime relay is no longer read as blue/green. Its list row
said Stopped while the stack served, and a start or restart from the console named
@blue/@greenunits that do not exist. The relay keeps working as before. - The Compose unit never pulls at start.
ExecStartPre=... pullis gone from the unit: a boot or asystemctl restartrecreated the database container with whatever its image tag had become. Images change only on a deploy. - On/off labels are badges. The informative enabled/disabled labels (notification channels, integrations, services, cron jobs, tokens, accounts, firewall, automatic updates, approvals, start at boot) are small status badges with their shape and text.
- Spinners keep turning under reduced motion, slower: a frozen one read as a hang.
- The hook flag reads "May change the database schema" instead of a bare verb.