v0.4.43
Signedpublished September 21, 2026What changed
-
A snapshot a workspace was restored from is now marked, and held. An exact restore does not copy a snapshot: the workspace boots the snapshot's own saved image and keeps running from it. The snapshot is the floor that workspace stands on, so deleting it was never really possible — Docker refused it with a conflict naming an image ID, and only after you had asked for the deletion.
Both Snapshots lists now say so first. A snapshot a workspace is running from is marked In use, names the workspace holding it — as a link, on the account-wide list — and its delete button is disabled rather than offering something that cannot happen. The API refuses the same deletion with the same reason, and with the two ways out: restore that workspace onto its current image, which rebuilds it on a real base image and releases the snapshot, or delete the workspace. Deleting a workspace and its snapshots is refused in the same way when one of those snapshots is holding up a different workspace, and says to delete the workspace without its snapshots instead.
Restoring is unchanged. The restore dialog simply says what an exact restore leaves behind before you pick it.
-
Give an account a disk quota, and snapshots have to fit inside it. A snapshot saves a whole workspace, so it is the one thing on a DevGrail box that consumes disk on a tenant's say-so — and until now the only thing standing in front of that was a cap on how many an account could keep, which says nothing about ten snapshots of a 40 GB workspace.
Administration now has a Disk per account setting beside the snapshot count, and the Users page can give one account a quota of its own — larger, smaller, or
0for no quota at all. Both take the same sizes as a memory limit (50g). Leave them blank and nothing changes: a deployment with no quota set behaves exactly as it did before.Your dashboard shows what you are holding against yours, and the Snapshots tab will not offer to take one you have no room for. If a snapshot turns out to be bigger than the room left — which is only knowable once it has started measuring — it stops there and says so, before it has written anything. Nothing you have already saved is ever deleted to make space; a full quota is cleared by deleting a snapshot you no longer want, and the account-wide Snapshots tab is where you can see them all at once.
-
Save a whole workspace with a snapshot. A workspace page now has a Snapshots tab, and taking one saves the parts of a workspace nothing else ever has: everything under
/home/dev— the sign-ins your agents hold,~/.gitconfig, shell history, anything you installed withsudo apt— along with a copy of the files on the volume behind/workspace. Give it a label like "before the Node 22 upgrade" and it stays in the list until you delete it.Taking one stops the workspace for as long as it takes and starts it again afterwards, which is what makes the two halves a snapshot saves describe the same moment. If stopping is not acceptable there is a checkbox to snapshot the workspace while it runs; the list marks those, because a file being written while the copy runs can be saved half-updated. Either way the snapshot is taken in the background, so you can leave the page.
While one is being taken the list says what it is actually doing — stopping the workspace, measuring it, saving its image, copying
/workspace, starting it again — with the time so far beside it. Saving the image is nearly all of the wait, so it is nearly all of the bar, and it is the step that can report nothing at all about itself: its share fills on elapsed time, slowing as it goes and never running past the step it belongs to. Copying/workspaceis the short step at the end, and the one with a real measurement behind it — the bar moves on bytes actually copied, out of a total measured before the copy began. Once a workspace has one finished snapshot, the estimate stops being a guess: how long the bar takes to fill is paced by how long that workspace took last time. A snapshot that stopped your workspace keeps saying so for as long as it is down, and the list tells you how big the last snapshot of that workspace was and how long it took, which is usually the answer to "should this be taking this long".A snapshot of a workspace whose image the host no longer has is now refused up front, with a message that says so. Rebuilding the workspace image and pruning the old build leaves long-running workspaces on an image Docker has forgotten: they keep running, but they cannot be committed, and Docker's own answer to that is a missing content digest that names nothing you can act on. The refusal names the image, says the workspace itself is fine, and says what recreating it would cost.
A saved snapshot can now create a new workspace in either of two ways. An exact restore brings the whole saved machine back, including agent sign-ins, packages installed with
sudo apt, shell history and every other writable-layer change. A current-image restore starts on the image tag as it resolves now, keeps/workspace, and lets you choose which saved agent and shell settings to carry forward.~/.local/binis deliberately left on the current image so old copies cannot replace the agent binaries the new image shipped.The restored workspace is a copy: the snapshot and its source stay untouched, and a name or URL-path collision is resolved with a numeric suffix. Project membership and public routes return only when they are still available; API-key grants never copy to a new workspace. SSH and browser-terminal credentials are always issued fresh, and an SSH key revoked after the snapshot stays revoked after the restore.
A snapshot can also be restored over the workspace it came from, in either mode. The workspace keeps everything that identifies it — its name, URL path, SSH port, published routes, project and API-key grants — and everything inside it is replaced. That makes a current-image restore the way to move a workspace onto a newer build without losing your home directory, which is what rebuilding on the current image never could.
It is built so it cannot take your working files with it if it goes wrong: the saved copy of
/workspaceis poured into a brand new volume while the workspace is still running, and only once that has succeeded is the container replaced. If the rebuild fails, the workspace is put back on the volume and image it had. The restored copy then takes over the name of the volume it replaced, so the workspace goes on using a volume called what you called it, and the one it was using before the restore is kept, unattached, in the Volumes tab as “… (pre-restore <date>)” — so restoring the wrong snapshot is undoable, at the cost of holding both copies until you delete one. Tick “delete the previous volume” in the restore dialog to reclaim that disk instead; it is applied only after the restore has succeeded, so a restore that fails never deletes anything. Restoring over a workspace is refused while a script is running in it and for a delete-locked workspace, and an open browser terminal reconnects as a new session because its credential is reissued.A snapshot outlives the workspace it came from on purpose: delete the workspace and its snapshots stay, because "I deleted that by accident" is the case they most need to answer. The delete dialog says how many there are and roughly what they cost, with a checkbox to take them along — off by default, like the one for the attached volume. The disk a snapshot holds is freed when you delete it.
Those survivors now have a home: Containers → Snapshots lists every snapshot on your account, grouped under the workspace each came from, with the groups whose workspace is gone marked as such and one button to show only those. Restore one from there and the deleted workspace comes back as a new one; delete it and the disk is freed. Until now a snapshot whose workspace had been deleted was only reachable if you had kept its id, because the only list of snapshots was on the workspace page that went with it — so an account with no workspaces left still opens on this tab rather than the first-run panel.
Any snapshot in either list can now be inspected: what it holds, and where those bytes are. A snapshot is two ordinary Docker objects with names derived from its id — an image holding the container's writable layer and a volume holding the copy of
/workspace— and the panel names both, says whether the daemon still has them, and gives the path on the host each sits at. It also shows what the snapshot recorded about the workspace it came from (its URL path, CPU and memory limits, restart policy and published routes) and which workspaces are currently running from its image, which is what stops a delete until they are gone. A snapshot whose image someone removed outside DevGrail now reads as unrestorable here rather than at the moment you reach for it.For whoever runs the server: the System page now counts the snapshots on the deployment and what they add up to, since a snapshot's storage deliberately sits outside the volume figures beside it. Accounts are limited to ten snapshots by default, and Administration → Workspace defaults now has the field to change that (0 removes the cap). Snapshot images no longer appear on the images page as unpermitted images — the server made them, and no allow-list entry will ever name one.
uninstall.shaccounts for them in the report it prints before it asks anything, and removes them with--purge. -
"Update image" is gone, and so is the data loss it came with. Rebuilding a workspace on the current image kept the volume behind
/workspaceand threw away everything else — every agent sign-in,~/.gitconfig, shell history, every package installed withsudo apt. That is why the button needed a table explaining what survived, and why the note beside it ended by talking you out of pressing it.A snapshot restored onto the current image does the same job and keeps all of it, so the button and the
POST /containers/{id}/recreateendpoint behind it have both been removed. The Image row still says when a newer build of the tag exists, and now points at Snapshots below it; the Containers list still marks those workspaces. Nothing else about a workspace changes, and workspaces already running are untouched. -
Publish a workspace at your own domain. Until now the only public address a workspace could have was a subdomain of a suffix your administrator had set up —
shop.apps.example.com. A workspace page now has a Custom domains section beside Subdomains, where you can point a domain you own at a port inside that workspace: putwww.myrestaurant.com's DNS A record on this server, add it with port 3000, and the site your workspace serves on port 3000 answers there. The Containers list shows each workspace's custom domains beside its subdomains, so a published address is one click away from the overview.The HTTPS certificate is arranged for you. Traefik obtains one from Let's Encrypt the first time somebody visits, so that first load can take a few seconds and every one after it is immediate — there is nothing to upload and nothing to renew. Visitors who type the bare address without
https://are now redirected to it rather than meeting a 404, which is also true from now on for app subdomains and for the routes an administrator adds under Traefik routes.Adding or removing a domain leaves the workspace alone: nothing restarts, nothing is rebuilt, and everything running inside it keeps running. The route is live about a second after you add it. As with a subdomain, the process inside the workspace has to listen on all interfaces rather than only on localhost, or the address will answer with a gateway error.
Two things are refused, both with a reason. A domain can be published only once on a server, so a name another workspace already holds is turned down — and a name that falls under one of this server's own base domains is turned down too, because that is what the Subdomains section above it is for. Wildcards are not accepted: the way this server proves it owns a domain works for one exact name at a time, so a wildcard could never be given a certificate browsers trust.
Administrators can see every custom domain on the server, with the workspace it serves and who owns it, at the bottom of the Traefik routes page, and withdraw one from there. That is deliberately the only thing an operator can do to somebody else's domain: it takes the route off the shared proxy without touching the workspace, which keeps running, and its owner can publish the domain again.
-
Your open terminals are still there when you come back. The terminal tab used to forget its layout the moment you looked at anything else: switching to another tab or reloading the page closed every pane and dropped you back on the session list, even though the sessions themselves had never stopped running. The workspace now remembers which terminals you had open, in what order and at what size, and puts that grid back — reattached to the same shells — the next time you open the terminal tab in that browser. Terminals you close stay closed, and a session that has since ended is simply left out.
-
A terminal you open is ready to type in. Opening a session from the terminal list, from a chip beside the toolbar, or with "New terminal" now puts the cursor in that terminal, instead of leaving you to click into the pane first. Terminals that come back on their own — restored from your last visit, or opened by a link — leave the keyboard where it was.
-
Agent commands now get ten minutes by default. MCP's
exec_commandno longer cuts ordinary builds and test suites off after two minutes, and when a command does reach its deadline the timeout result returns promptly instead of waiting for that command to finish. Commands that need longer can run in a persistent terminal session.
Container images
Installer files
Signature
One signature covers every artifact above. It is made off this host, and neither this site nor the registry behind it can check it — that is the point, and it is why verifying it catches things a checksum from the same server cannot. How to verify it.
curl -fsSL https://web-dev.trixibot.com/api/registry/releases/v0.4.43 -o release.json
# -j, not -r: -r appends a newline the signer never saw, and the check fails.
jq -j '.signature.payload' release.json > payload
jq -r '.signature.signature' release.json | base64 -d > payload.sig
openssl dgst -sha256 -verify devgrail-release.pem -signature payload.sig payload
# -> Verified OK
# The payload must name this release, so an older signed one cannot be replayed.
head -2 payload
# ...and it must name this exact file. -qxF: whole line, literal.
grep -qxF 'image devgrail-container 33 amd64 d428e5a83022c8da67b23a9b689349c23594a7b420f1e5621c9036ea7aa85695' payload && echo "digest is covered by the signature"The signed payload — shown for reading, not for verifying
Copy this from the manifest with jq -j, never from this page: the signature covers exact bytes, and rendering here drops the trailing newline the signer included.
devgrail-release-signature-v1
release v0.4.43
asset docker-compose.yml 51 3d2782acc8a2d405964feb3ef7ea3d71ac85285445e20ead88368dea1420c1ba
asset install.sh 51 ee5052c1552dd5c34c2d33716aa92f6384ca7464b59ff224785ab986a102f466
asset traefik-dynamic-http.yml 50 e344c8efd13c7d84996e2ebfbc75dcee2d995d8538c8a245c89c0e44fad0089b
asset traefik-dynamic.yml 51 d18fa561b8eb7d53b015284e0246b2d05953bf281b65269c1fae3d6883530db6
asset uninstall.sh 51 ce0fc4bf3d7e6cd0a00489ca0ac40b94ff000595f88e07d47c72f6261ebb4e02
image devgrail-container 33 amd64 d428e5a83022c8da67b23a9b689349c23594a7b420f1e5621c9036ea7aa85695
image devgrail-container 33 arm64 47ff37339c4dd3d23643e033192dd905c93e777035f26b3456430920df4769f4
image devgrail-server 64 amd64 b3699a53404b98ea240a3c3e4caf33ebdb0c6c35f2e9286dfd080090f9502347
image devgrail-server 64 arm64 aabc6ff36f1384e9b9b9a204adf1255de60101479662c8180292fc5ad6efa7d0