Skip to content

feat(computer): boot Windows computers on modal_vm - #534

Merged
earakely-scale merged 15 commits into
mainfrom
edgararakelyan/windows-on-modal-vm
Nov 8, 2026
Merged

earakely-scale merged 15 commits into
mainfrom
edgararakelyan/windows-on-modal-vm

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

What this does

Part of FD-5321. Windows computers now boot on modal_vm, as Linux disk computers do. It builds on #531, which keeps Windows disks in the image store.

  • Carries a thin disk whole. A Windows disk is a layer (disk.img, about 750 MB) on its base (base.img, 11.6 GB).
    • exported_disk now returns an Exported, not a Path. Only modal_vm calls it.
    • An Exported holds up to three files, each exported from the image store and compressed into this machine's cache:
      • the disk;
      • for a thin disk, its base, exported once per base. Bases are told apart by their first layer, which every disk composed on that base shares.
      • the guest file, which names the disk's OS.
  • Mounts them where a disk's own image holds them. modal_vm mounts the three files at /disk/disk.img.zst, /disk/base.img.zst and /disk/guest, the same paths as in the disk's own image, and passes the base's path as VM_BASE. Modal caches a mounted file by its content, so a base goes up once and each computer on it adds only its layer.
  • Expands the base beside the disk. The launcher expands VM_BASE beside the expanded disk, as the base.img the disk is backed by. It reads the guest file from beside the disk, as it already did.
  • Accepts Windows. modal_vm lists windows/amd64 among the disk platforms it boots.
  • Recovers from killed exports.
    • An export's work folder and Docker container are named for the disk. Both are cleared, under the disk's lock, before it's exported again, so an export killed outright doesn't leave a copy of the base behind.
    • The container's name hashes the cache path, so two users' exports through one Docker never clear each other's.
    • Only Docker's missing-file error counts as "no guest file". Any other copy error fails the export, rather than caching a Windows disk that would boot as Linux.
  • Uses one rule for thin disks. A disk of more than one layer carries its base. The local machine image and the export share that rule (_layout).
  • Updates the docs. The computers README and the sandbox provider guide now say Windows boots on modal_vm and how its base is carried. The README adds a note: on modal_vm, Windows runs in your own Modal workspace, under the evaluation's terms or a licence of your own.

A Linux disk is one layer and is exported and mounted as before.

Windows keeps the default ready timeout (180 s). On modal_vm, a whole deploy step, ready wait included, took 40–100 s across runs; the export and upload happen before the wait starts. On local Docker it took about 26 s.

Tested

  • Unit tests:

    • exporting a Linux disk;
    • exporting a thin disk (its base once for two disks on it, plus the guest file);
    • an export killed outright;
    • two users' exports;
    • a guest-file copy that fails;
    • the modal_vm mounts and VM_BASE;
    • the launcher expanding a base.
  • Full unit tier: run on an EC2 box.

  • E2E on Modal, from an EC2 box with the composed disk in its image store:

    • probe, reset, and a Notepad trial with Claude Code;
    • chaos: SIGTERM and SIGKILL mid-export;
    • the Linux-disk-on-Modal integration test.

    Details are in the comments below.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new actionable issues were found.

Summary

Windows disks can now boot on modal_vm.

  • Modal VM boots Windows disks with their backing base.
  • Disk exports clear interrupted leftovers and stop on unexpected guest-copy errors.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Disk image] --> B[Local export cache]
  B --> C[Compressed disk]
  B --> D[Shared compressed base]
  B --> E[Guest file]
  C --> F[Modal sandbox]
  D --> F
  E --> F
  F --> G[Launcher expands disk and base]
  G --> H[Windows boots]
Loading

Reviews (10) · Last reviewed commit: "revert(computer): Windows keeps the defa..." · Reviewed by Greptile

earakely-scale and others added 6 commits November 7, 2026 09:25
…, as Linux disks do

A composed Windows disk is pushed to the env's image store under its key and recorded pinned;
a compose of the same inputs on any machine reuses it, and a deploy pulls it. A base is
pushed under a tag of its recipe, with the key to its admin, so a machine without it pulls it
to compose a layer instead of installing Windows, and fetches no ISO to find it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A thin disk is carried whole: its base, exported once for each base, its layer and its guest file, compressed and
mounted beside each other; the launcher expands the base beside the disk. Windows gets a longer ready timeout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…llback for a layer count docker always gives

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…local base the image store lacks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 7, 2026 18:17
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

E2E on Modal

Set-up:

Task Result Time
probe passed 373 s in all. The deploy step took 333 s: export (about 5 min), upload, expand and boot. The probe itself took 40 s.
reset passed (before, reset in place, after) 138.5 s. The deploy took 39.7 s, because the export and Modal's mount cache were warm. The reset took 23.9 s.
  • The compose was a reuse. A new bundle path means a new env, but its compose reused the disk the image store holds under its key ("reusing the disk of the same key, composed earlier"). Then gate 3 probed the disk locally.
  • Export sizes.
    • The base: base.img is 11.6 GB, base-<layer>.img.zst is 9.1 GB. It's exported once per base and shared by every disk composed on it.
    • The layer: disk.img.zst is 314 MB, exported once per composed disk.
  • Every later deploy uses the cache. With the base already uploaded, a deploy took about 40 s, with the base expanded in the sandbox.

The base compresses poorly at zstd -3 (11.6 → 9.1 GB), because NTFS data is mostly already dense. The first upload of a base is the slow part, once per base per Modal workspace. FD-5328 (W8) tracks making the base smaller.

…her than cache the disk without it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its work folder and container are named for the disk and cleared under its lock before it's exported again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

More e2e and chaos on Modal

Run What happened Result
Notepad trial The env was on modal_vm and the agent (Claude Code, Sonnet 5.5) on the EC2 box. The prompt: open Notepad, type a line, save hello.txt to Documents. passed (the check found the file). 177 s in all: deploy 113 s, agent 57 s.
SIGTERM mid-export The export cache was emptied and the run killed with 3.2 GB of the base copied. The CLI cancelled the run, then the process finished the export thread (a thread can't be cancelled) and exited. The cache was whole: no temp folder, no container. The rerun's probe passed (129 s).
SIGKILL mid-export, before 36e6bac The orphaned docker cp went on writing into the export's temp folder. The rerun passed, but left the whole 11.6 GB base copy and the docker created container behind for good.
SIGKILL mid-export, with 36e6bac The kill left the work folder (3.3 GB) and the container agentenv-export-<disk>. The rerun cleared both under the disk's lock before exporting. The probe passed (378 s). Afterwards the cache held only the four export files (8.8 GB), and no export container remained.

Fixed here: 36e6bac. An export's work folder (<disk>.img.zst.work) and its container (agentenv-export-<disk>) are now named for the disk, and both are cleared under the disk's lock before it's exported again. Until then, a hard-killed export of a Windows disk leaked a copy of the base.

earakely-scale and others added 3 commits November 7, 2026 11:01
…path, so two users' exports never clear each other's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dows names derived from a key in one place

- pin_if_served beside pin_digest replaces the composer's _served and Registry's own copy of it; _image_store gets
  the env's store with its repository for _stored and the Windows compose alike.
- base_names and disk_names name a base or disk from its key, here and in the store; compose takes the key rather
  than both names.
- labels is imported as image_labels, no longer shadowed by compose's parameter and Registry's method.
- The ISO label nothing read, and the sha256 helper only it used, are gone; Registry's store is an ImageStore.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from edgararakelyan/windows-images-in-the-image-store to main November 8, 2026 00:44
earakely-scale and others added 4 commits November 7, 2026 16:45
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d beside the disk, under your own licence

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne place, and a disk form's own ready timeout

- _layout gives a disk's id and its base's first layer, for the machine image and the export alike.
- _Cached names an export's files: the disk, its guest file, the file naming its base's export, its work folder.
- modal_vm mounts the disk, base and guest file where a disk's own image holds them (DISK_FILE and its siblings).
- A disk form says how long a boot of it may take to be ready (ready_timeout_s); windows-11 says 600 s, which
  replaces the os_type branch in boot.ready_timeout, now given the env and the form it boots.
- The export tests share one fake Docker; _compressed no longer takes the image for its message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y boot measured stays well within

Its ready wait takes about 40 s on modal_vm, whose export and upload come before it, and about 30 s on local Docker.
Drops the disk form's ready_timeout_s, the windows-11 value and the ready_timeout signature change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit b0900a7 into main Nov 8, 2026
59 of 62 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/windows-on-modal-vm branch November 8, 2026 03:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant