git log.
v0.1.0 (2026-09-30): first public release
The first release: everything below this entry, plus these changes. Two earlier tags (v0.1.0 on 2026-09-29 and v0.1.1) were withdrawn and folded into this one.- Container deploys are faster (#540). The control plane asks Podman for its settings once instead of on every deploy, skips stopping and removing on a first deploy, and waits 75 ms instead of 200 ms to confirm the app is up. Spawn-to-ready in the bench dropped by 150 to 420 ms per app, to within about 100 ms of a plain
podman run. - Stopping a container reaches the app (#540). Containers run with
--init, so Podman’s init is PID 1 and passes SIGTERM on. Before, an app with no SIGTERM handler of its own (meilisearch) ignored it as PID 1, so every stop and replace waited 10 s and then killed it. On a host without catatonit (it is only recommended by Debian’s podman package), the control plane logs one warning and runs containers without--init. - An update keeps the service’s
[ingress].portpin (#538). Before, the firstrussel updateof a pinned container or passt microVM moved it to an allocated port. Pinned services now update cold; onerussel updateputs a lost pin back. - Containers start under
install.sh hostand the NixOS module (#524). Before, every container failed: Podman asked therusselaccount’s user manager for a systemd scope, which systemd refuses for a process in a system unit. The control plane now runs Podman with--cgroup-manager=cgroupfswhen it runs unprivileged in a delegated system unit.--cpusand--memorystill apply. - The unit drops
PrivateTmp,ProtectSystem, andProtectHome(#524). Podman’s pause process outlives the unit and kept the first control plane’s private/tmp, which systemd deleted on restart. Re-runninginstall.sh hostreplaces a pre-release unit even without--force-unit, keeps your drop-ins, and ends the stale pause process.[[volumes]].hostroots no longer needReadWritePaths=. See systemd and NixOS. - The build runs in the Russelfile’s folder joined with
service.source(#525). Before, it always ran at the repo root andsourcewas only validated.russel deploy https://github.com/daschinmoy21/russel.git --config examples/basic-http/Russelfile.tomlnow builds that example, so the quickstart needs no local checkout orRUSSEL_ALLOW_LOCAL_PATH_DEPLOY. A Russelfile in a subfolder that relied on building the repo root must now setsourceto reach it, or move to the root. russel psno longer lists.config,.local, or.cachein/var/lib/russelas services (#526). Every dot-directory in the data root is skipped.- No cleartext-token warning for a loopback control plane (#527). Plain HTTP to a non-loopback host is still refused without
--insecure. install.sh checkfindsuseraddandusermodin/usr/sbinwhen run withoutsudo, andhostwaits up to 30 s for a freshly started control plane instead of reporting a good install as failed.
2026-09-28: an update can’t replace a working version with one that crashes on start-up (#493)
- Updates and rollbacks (dual-live) switch traffic to the new version as soon as it answers, then keep the previous version running for 2 s (
switch · New version is live; keeping the previous one running for 2s in case it crashes). If the new version dies in that window, traffic goes back to the previous one, the new one is torn down, andapplyfails withnew version crashed within 2s of answeringand the app’s last output. Before, one accepted connection was enough: an app that listened and then crashed (postgres without/dev/shm) reporteddeployed, and the previous version was already drained. Both runtimes. - Cold updates (services with
[[ports]]) require the new version to stay up for 2 s after it answers; a crash restores the previous version from its backup (rolled_back). - First deploys are unchanged in speed:
deployedat the first answer. A crash right after is reported within about a second by the supervisor (microVMs) or the exit watcher (containers). - A microVM whose guest exits before the app answers fails at once with
app exited during startup (exit code N)and the guest console tail, instead of after the 30 s readiness timeout. - Fixed: a failed container cold update never rolled back. The rollback looked for the previous rootfs at its live path while it sat under
.bak, and failed withprevious rootfs_path no longer exists. - Details and trade-offs: When a deploy counts as ready.
2026-09-28: a dedicated russel account, and host checks (#515, breaking for existing host installs)
install.sh hostnow runs as root (sudo ./contrib/install.sh host, orcurl … | sudo RUSSEL_VERSION=… bash -s -- host). It creates an unprivilegedrusselsystem account with its own subuid/subgid range and linger, and installs/etc/systemd/system/russel-ctrl.servicerunning asUser=russel. This is the same model as the NixOS module. Before, russel-ctrl and every container ran as the operator’s own login account, so an app that escaped its container landed next to that account’s SSH keys and sudo.- The token moves to
/etc/russel/env(root:russel,0640). The operator who ransudojoins therusselgroup and canrussel login --token-file /etc/russel/envafter logging in again. On a host that had the old per-user install, the token in~/.config/russel/envis carried over. - The unit puts the Nix daemon profile on
PATHand points rootless Podman at the account’s runtime dir and user bus, so builds findnixandpodman run --memoryworks under a system unit. - New
install.sh checklists what the host is missing (Podman 4+, uidmap, a systemd user bus, a multi-user Nix with flakes, git, cgroup v2), with the Debian/Ubuntu command that fixes each.hostruns it first and stops on failures;--skip-checksoverrides that. hostrefuses to run next to the old per-user install and prints the steps to move over: destroy the services, disable the user unit, thensudo … --take-state-ownership host, which now hands the whole/var/lib/russeltree torussel.- Connection hints say
systemctl status russel-ctrl(installer) andsystemctl status russel(NixOS). The old hint namedrussel-ctrlfor NixOS, whose unit isrussel.service.
2026-09-26: Nix GC roots for deployed generations (#411)
- Ctrl roots every store path a service may re-exec under
_pool/gcroots/<id>/: the current metadata’s app, kernel, initramfs, and rootfs paths, plus the journal’sactiveandpreviousgenerations. It syncs after deploys and automatic rollbacks, and for all services at startup, so hosts upgraded from earlier builds get roots on the next ctrl start. Destroy removes them. - Container rollback fails before renaming anything when the previous
rootfs_pathno longer exists. MicroVM rollback already checkedapp_pathandkernel_path.
2026-09-26: one port model (#385)
service.portis the guest listen port andPORTin every case. A host-side port pin is configured with[ingress].port;service.portremains the guest listen port andPORT.[ingress].portpins the host side.[ingress].portand[[ports]]hosts share one rule (validate_publish_host_port: not 0,>= 1024, not 7878/7946).- An
[ingress].portthat equals a[[ports]]host is rejected instead of publishing the port twice.[[ports]]stays additional listeners only (microVM support is #386).
2026-09-26: GPL source for the kernel, dashboard third-party licenses
- New flake package
packages.x86_64-linux.microvm-kernel-source: one tar with the upstreamlinux-<version>.tar.xzthe kernel is built from, the nixpkgs patches in apply order, the generated.config, Russel’sflake.nix/flake.lock/nix/microvm-kernel.nix, nixpkgs’pkgs/os-specific/linux/kernelbuild expressions, and aREADMEwith the nixpkgs rev and rebuild steps. contrib/release-kernel.shbuilds it next to the bzImage and uploads it asrussel-kernel-<tag>-source.tarwith aSHA256SUMSline.--publishrefuses to publish without it, so the GPL-2.0 kernel binary never ships without its corresponding source. Arelease.ymlre-run keeps its checksum line like the kernel’s.bun run buildwritesdist/THIRD-PARTY-LICENSES.txt(license and license text of every package in the dashboard’s production npm closure), sorussel-dashboard-<tag>.tar.gzcarries it. The release workflow fails if it is missing.NOTICEand the release notes point at both.
2026-09-26: Russelfile contract for v0.1 (breaking)
[database.postgres]and[database.redis]are gone from the schema. They were placeholders (enabled = truealready failed load) and skippeddeny_unknown_fields, so[database.mysql]loaded silently. Any[database]table now fails load withunknown field `database`. Delete it; run Postgres or Redis as its ownservice.packageservice with[[volumes]](examples/postgres,examples/redis).russel initno longer emits the commented stub.- Load enforces the name, bin, and env rules the reference states.
service.nameuses the service id rule (ASCII[A-Za-z0-9_-], 1–128, notsecrets/traefik/_pool), the same rulerussel init --namenow reuses fromrussel_core::ids;my.app,my app, and non-ASCII names fail load.service.binkeeps the wider[A-Za-z0-9._+-], max 256 rule and is checked at load too.[service.env]goes throughvalidate_env_mapat load, soPORT = "1"fails before any build. The reference, env guide (its example was missingsource), and the init template (1gbis not a valid memory value;LD_AUDITwas missing from the reserved list) are corrected, and a test loads everyexamples/**/Russelfile.toml. service.argsand the CLI’s trailing tokens are no longer both called args.service.argsis process argv after the entrypoint and stays in the Russelfile; it is container-only for now, and a microVM file with non-emptyargsfails load instead of having them silently dropped by the guest init. Extrapodman runflags are configured in Russelfileservice.podman_args(one token per entry; API field ispodman_args).
2026-09-26: kernel uploaded from the maintainer’s build
release.ymlno longer builds the microVM kernel (linux from source ran past 2h on hosted runners and the first release run was cancelled). It builds the binaries, dashboard,LICENSE, andNOTICE, and leaves a draft release. A re-run keeps an uploaded kernel’sSHA256SUMSline and fails instead of dropping it.contrib/release-kernel.sh <tag> [--repo OWNER/NAME] [--publish]checks that the local tag matches the release repo’s tag, buildspackages.x86_64-linux.microvm-kernelat that commit, uploadsrussel-kernel-<tag>-x86_64.bzImage, adds it toSHA256SUMS, and reads the sums back to confirm the line landed.--publishtakes the release out of draft only once every asset is present with a checksum line.--repodefaults todaschinmoy21/russel, wherecontrib/install.shdownloads from.- The publish job sets
GH_REPO: it has no checkout, sogh releasecould not resolve the repository before.
2026-09-25: tests stay off the host state root
cargo testno longer reads or writes/var/lib/russelor/var/lib/microvms(#412). ctrl unit tests pin both roots to a per-process temp dir (russel-test-<pid>-…under$TMPDIR, removed by the next run once the process is gone). The API integration tests do the same, clear the roots before each test, and point Podman at a missing binary soGET /vmsdoes not list the host’s real containers.- Before this, a test run on a host with a writable
/var/lib/russeloverwrotectrl-catalog.jsonwith fixture services, andvms_list_empty_when_no_servicesfailed on any host with a live Russel container. The catalog is informational and ctrl rewrites it on the next state change. - ctrl code resolves host paths through
russel_ctrl::paths;crates/ctrl/clippy.tomlbans the directrussel_core::pathscalls./var/lib/microvmsliterals go throughpaths::microvms_root()(still/var/lib/microvmsin production; making it configurable is #413). - The port-hold tests use OS-assigned ports instead of fixed 4010–4012, which failed when another process on the host held them.
2026-09-24: release workflow and --version
- Release workflow builds
russel+russel-ctrlon Ubuntu 22.04 (glibc 2.35), the dashboard dist, andnix build .#microvm-kernel, then refuses to publish ifrussel-kernel-<tag>-x86_64.bzImageis missing or under 1MB. russel --versionprints the crate version.install.sh hoston a KVM machine fails closed when the kernel asset is absent. OptionalRUSSEL_GITHUB_TOKEN/GH_TOKEN/GITHUB_TOKENfor private GitHub release URLs.
2026-09-20: release artifacts, download mode, KVM-gated kernel
- New
.github/workflows/release.ymlonv*tags: buildsrussel+russel-ctrl, tars the dashboarddist, runsnix build .#microvm-kernel, writesSHA256SUMS, and uploads everything withgh release upload. - Assets:
russel-<tag>-x86_64,russel-ctrl-<tag>-x86_64,russel-ctrl-<tag>.service,russel-dashboard-<tag>.tar.gz,russel-kernel-<tag>-x86_64.bzImage,SHA256SUMS. contrib/install.shdownload mode:RUSSEL_VERSION(or a missingtarget/release) fetches the assets and verifies each one againstSHA256SUMS.RUSSEL_RELEASE_BASEoverrides the artifact base URL.- The installer is safe to pipe on stdin (
curl … | RUSSEL_VERSION=… bash -s -- host): it no longer assumesBASH_SOURCEpoints at a checkout root. hostfetches the microVM kernel only when/dev/kvmexists, and installs it at/var/lib/russel/_pool/kernel/bzImagewith operator ownership. Container-only hosts print a skip.allnever fetches the kernel.- Docs: release one-liner, glibc vs NixOS binary compatibility, and the KVM kernel gate.
2026-09-11: install-scripts stack (#350–#353) + CLI/host packaging
Onmain:
- CLI ships as
russel(craterussel-cli);russel-clibinary name retired.psreplacesvms(list,vmskept as aliases). - New:
russel init(scaffoldRusselfile.toml+--with-flake),russel login/logout/origin(~/.config/russel/config.toml,0600, env wins). - Improved loopback/remote connect diagnostics (anchored SSH hint, systemd +
ssin the lock message); contention tests use tempfiles. - Installer:
install.sh host(user-systemd, refuses NixOS/root),connect user@host(anchored forward),status(endpoint/listener/unit/origin probe). Restart failure restores the prior binary; tokens never printed. - Dashboard: default API base
/apieverywhere;401(re-enter token) distinguished from offline (tunnel/proxy); Settings presets same-origin/tunneled/custom-HTTPS; no CORS. - Docs: topologies A (same host) / B (split + SSH) / C (split + HTTPS) without pet hostnames; TLS examples strip
/apiand keep NDJSON unbuffered; Fish guidance vialogin --token-file. - Control plane:
repo_urluserinfo redacted in logs/metadata; microVMdeploy.envmounted read-only with separatescratch/share for.agent_ready;service.guest(busyboxdefault,linuxparsed-then-rejected); filebrowser example requires auth + loopback. - NixOS module
services.russel(nixosModules.russel/.default): loopback,REQUIRE_AUTH=1,0700state, rootless-Podman integration.
2026-08-15: audit remediation (deslop/audit-2026-08-15)
- P0 fixes: state-lock I/O outside the mutex, atomic metadata writes, shared constant-time tokens, bounded podman calls,
rm -rf→ fs, multibyte truncation, 400/500 split, loud init, GC outside lock. - Dead-code purge (12 items), dep hygiene (
core -tokio,agent -serde,cli -serde/tracing), docs de-sprawl (vendored CH docs deleted), CI hardening (concurrency/cache/timeouts/dashboard/shellcheck). - Follow-ups:
ServiceStatus/VmStateenums, scary-module tests (warm_pool, ch_api parser, rollback, CLI NDJSON),bench-common.sh+flock, dashboard dedupe.