Sandbox and Test Flow Guide
This document describes the public sandbox API, the global ConfigManager under
AgentENV/src/cfg.rs, and what AgentENV/tests/integration/fc.rs,
AgentENV/tests/integration/process.rs, AgentENV/tests/integration/snapshot.rs, and
AgentENV/tests/integration/orchestrator.rs validate.
1) Sandbox Public Interface
Types and responsibilities
-
FirecrackerSandboxConfig(AgentENV/src/sandbox/firecracker/config.rs)- Purpose: describes a fresh VM boot (kernel + tools drive + user image).
- Key fields
firecracker_binary: path to thefirecrackerexecutable.kernel_image: path tovmlinux.bin.tools_drive_version: immutable tools drive identity. The node resolves it to<deps_path>/tools/<version>/tools.ext4before mounting it as/dev/vda.user_image_config: overlaybd config for the writable user image (/dev/vdb).boot_args: kernel boot args (e.g.console=ttyS0 ... init=/init).vcpu_count,mem_size_mib: VM size.runtime_policy: socket/envd timeouts and poll intervals.common.stdout_path,common.stderr_path: explicit capture directories. Otherwise stdout/stderr capture is enabled only whencommon.firecracker_log_levelis non-empty, using the serial directory orwork_dir/logsfallback.envd_version: expected envd version for the guest image.env_vars: optional default environment variables injected after envd init.ublk_config: optional ublk-backed rootfs configuration.
- Convenience
from_global_config()builds a config fromConfigManagerusing the internal default template rootfs image. The image must already be resolved under<image-cache-root>/configs.from_global_config_with_user_image(...)builds a config from an already resolved overlaybd image config.- Runtime launches from committed snapshots are built through
FirecrackerSandbox::from_snapshot(&RunnableSnapshot, &SandboxLaunchConfig). uid/gid: File ownership (default0).
-
ProcessOpts(AgentENV/src/sandbox/process.rs)- Purpose: Options for starting a process inside the sandbox.
- Key fields
envs:HashMap<String, String>for process environment variables.cwd: Optional working directory.timeout: Optional max time to wait for completion.
- Builder methods:
with_envs(),with_cwd(),with_timeout().
-
ProcessOutput(AgentENV/src/sandbox/process.rs)- Purpose: Result of a completed process execution.
- Fields:
stdout,stderr,exit_code.
-
ProcessHandle(AgentENV/src/sandbox/process.rs)- Purpose: Handle to a running process inside the sandbox.
- Key methods
pid(): Returns the PID inside the guest VM.wait().await: Waits for exit and collects all output.send_stdin(data).await: Sends bytes to stdin.send_signal(signal).await: Sends a signal (e.g. SIGTERM).kill().await: Kills the process withSIGKILL.
-
FirecrackerSnapshotConfig(AgentENV/src/sandbox/firecracker/config.rs)- Purpose: describes how to resume a VM from snapshot + memory + base disk.
- Key fields
vm_state_path: Firecracker VM state file.mem_overlaybd_config: runtime memory overlay image config.base_rootfs_path: writable disk image or overlaybd rootfs image config paired with that snapshot.firecracker_binary: FC binary to use for resuming.runtime_policy: socket/envd timeouts and poll intervals.envd_version: guest envd version metadata.env_vars: optional default environment variables restored on start.
- Notes
FirecrackerSnapshotConfigreturned bypause()owns a tempdir (keeps files alive).get_rootfs_path()resolves the snapshot rootfs path used by runtime tests.
-
SandboxBackendandSandboxExecutor(AgentENV/src/sandbox/backend.rs)- Purpose: public traits for sandbox lifecycle and process execution.
- Key methods
SandboxBackend:start,start_nowait,wait_for_ready,pause,resume,stop.SandboxExecutor:run_command,run_command_with_opts,start_process.
-
FirecrackerSandbox(AgentENV/src/sandbox/firecracker/sandbox.rs)- Purpose: lifecycle controller for a single Firecracker instance.
- Main methods (detailed)
FirecrackerSandbox::new(FirecrackerSandboxConfig)- Creates a sandbox handle for a fresh boot.
- Does not start Firecracker; it only prepares the object.
- Each sandbox gets its own temporary work directory.
FirecrackerSandbox::resume_from_snapshot_config(&FirecrackerSnapshotConfig)- Creates a new sandbox handle and immediately starts it from a snapshot config.
- Uses
vm_state_path, the memory overlay image config, andbase_rootfs_pathfrom the config. - Returns a running sandbox after Firecracker and envd are ready.
FirecrackerSandbox::from_snapshot(&RunnableSnapshot, &SandboxLaunchConfig)- Creates a sandbox handle from committed snapshot state resolved for a node.
- Uses snapshot
vm_state.binplus node-local materializedmemory/image.jsonandrootfs/image.json. - Does not start Firecracker; call
start().awaitafterward.
start().await- Spawns the Firecracker process, waits for the API socket, starts the VM, then waits for guest-level envd readiness.
pause().await- Sends
PATCH /vmto pause the VM. - Creates snapshot artifacts including
vm_state.bin,mem_image.json,mem_overlaybd/overlaybd.commit, and rootfs snapshot state. - Returns a
FirecrackerSnapshotConfigthat owns the tempdir holding these files.
- Sends
resume().await- Resumes a paused VM in-place by sending
PATCH /vmwithResumed. - Use this when you want to keep the same sandbox instance.
- Resumes a paused VM in-place by sending
stop().await- Stops Firecracker, releases network resources, and cleans up optional ublk state.
run_command(cmd, args).await- Runs a command inside the sandbox via envd gRPC and waits for it to
complete. Returns
ProcessOutputwith stdout, stderr, and exit code.
- Runs a command inside the sandbox via envd gRPC and waits for it to
complete. Returns
run_command_with_opts(cmd, args, opts).await- Same as
run_commandbut acceptsProcessOptsfor setting env vars, working directory, and execution timeout.
- Same as
start_process(cmd, args, opts).await- Starts a long-running process inside the sandbox. Returns a
ProcessHandlethat can stream output, send stdin, or kill the process.
- Starts a long-running process inside the sandbox. Returns a
firecracker_stdout_path()/firecracker_stderr_path()- Resolved log paths used by this sandbox (including defaults).
work_rootfs_path()- Returns the path to the per-instance writable rootfs inside the work directory. Useful for inspecting or copying disk state after a run.
What happens under the hood (important for correct usage)
- Each
FirecrackerSandboxuses its own temp work directory. - Kernel is used via the configured host path.
- When resuming, the writable rootfs and memory image are materialized into the work dir so each instance can mutate independently. For overlaybd-backed snapshot-backed template launches, this means recreating runtime image configs and fresh writable uppers from the committed layer stack.
- Rootfs copies use copy-on-write helpers where available.
- Firecracker runs with
current_dir = work_dir, so relative paths work. - Firecracker logging is disabled by default. Set
firecracker.log_levelto enable stdout/stderr capture andfirecracker.logunder the configured serial directory. Direct Rust callers can also opt individual streams in using explicit destinations. start()waits for both the API socket and envd readiness.- Although the Firecracker process will be killed on drop, explicit
stop()is recommended.
Minimal usage example
#![allow(unused)]
fn main() {
use agentenv::sandbox::{FirecrackerSandbox, FirecrackerSandboxConfig, SandboxExecutor};
let mut cfg = FirecrackerSandboxConfig::new(
"/path/to/firecracker".into(),
"/path/to/vmlinux.bin".into(),
"/path/to/rootfs.ext4".into(),
);
cfg.boot_args = Some("console=ttyS0 reboot=k panic=1 pci=off init=/init".into());
let mut sandbox = FirecrackerSandbox::new(cfg)?;
sandbox.start().await?;
// Run a command inside the VM
let output = sandbox.run_command("echo", &["hello"]).await?;
assert_eq!(output.exit_code, 0);
println!("{}", output.stdout);
let snapshot = sandbox.pause().await?;
sandbox.stop().await?;
let mut resumed_sandbox = FirecrackerSandbox::resume_from_snapshot_config(&snapshot).await?;
resumed_sandbox.stop().await?;
}
Runtime prerequisites
- Linux host
/dev/kvmaccessible by the current user, with the host modules matchingvirtualization_mode(KVM by default; PVM requires x86_64 andkvm_pvm)debugfs(e2fsprogs) if you use init injection or disk-inspection helpers
2) Global Config Manager (src/cfg.rs)
The tests and benchmarks use a shared global config manager based on
config/default.toml.
Config file format
AgentENV/config/default.toml (override via AENV_CONFIG_PATH or --config)
[firecracker]
boot_args = "your-kernel-boot-args"
binary_path = "/path/to/firecracker"
socket_timeout_secs = 3
socket_poll_ms = 20
# Optional override; defaults to $AENV_HOME/firecracker-work.
# work_dir = "/path/to/firecracker-work"
# Optional override; defaults to $AENV_HOME/logs/serial.
# serial_dir = "/path/to/serial"
# Optional; enables stdout/stderr capture and firecracker.log when non-empty.
# log_level = "Info"
[kernel]
image_path = "/path/to/vmlinux.bin"
[tools]
version = "0.1.0-custom.1"
drive_path = "/path/to/tools.ext4"
[machine]
mem_size_mib = 128
vcpu_count = 1
[envd]
version = "0.5.15"
init_timeout_secs = 30
poll_ms = 10
[orchestrator]
auto_evict_interval_ms = 1000
default_sandbox_timeout_secs = 15
[snapshot]
repository_backend = "posix_fs"
local_cache_path = "$AENV_HOME/snapshot-local-cache"
[backend.posix_fs]
snapshot_store = "$AENV_HOME/snapshot-store"
[ublk]
enabled = false
TOML parameter reference
-
[firecracker]boot_args: Kernel command line. The runtime appendsinit=/initif not already present (tools drive provides the init script).binary_path: Absolute or relative path tofirecracker.socket_timeout_secs: Max time to wait for the Firecracker API socket.socket_poll_ms: Poll interval for checking socket existence.work_dir: Optional override for the per-sandbox Firecracker working directories. These directories include runtime sockets, symlinks, local logs, and writable OverlayBD upper layer data (overlaybd/upper.dataandoverlaybd/upper.index). Defaults to$AENV_HOME/firecracker-work.serial_dir: Optional override for the persistent output directory. When logging is enabled, serial output is written under a per-sandbox subdirectory and not removed by the sandbox. Defaults to$AENV_HOME/logs/serial. Setting this path alone does not enable logging.log_level: Optional Firecracker log level (Error,Warning,Info,Debug,Trace, case-insensitive). When set to a non-empty value, stdout/stderr capture andfirecracker.logare enabled in the sandbox’s log directory. If omitted or empty, stdout/stderr are discarded and no log directories are created, unless explicit Rust capture destinations are set.
-
[kernel]image_path: Optional local kernel image path (vmlinux.bin). If omitted, AgentENV derives it fromdeps_pathand the bundled dependency manifest.
-
[tools]drive_path: Optional local tools drive source. When set, an explicitversionis required and setup imports it into the immutable version directory.
-
Template rootfs image
- User-visible rootfs images are selected when starting template builds through the optional
fromImagefield. - If
fromImageis omitted, AgentENV uses[image.resolver].default_image. - Standard Docker Hub shortnames such as
ubuntu:24.04andnode:20are normalized before image resolution. - Resolved image configs are cached under
<image-cache-root>/configs/<slug>-<hash>-image.json; local layer products are cached under<image-cache-root>/layers/; overlaybd-native remote block reads are cached under<image-cache-root>/remote-blocks/. - Registry authentication uses docker credentials from
~/.docker/config.jsonor$DOCKER_CONFIG/config.json.
- User-visible rootfs images are selected when starting template builds through the optional
-
[machine]mem_size_mib: Guest RAM size (default: 128).vcpu_count: Number of vCPUs (default: 1).
-
[envd]version: Expected envd version baked into the runtime image.init_timeout_secs: Max time to wait for the in-guest envd daemon to become ready after VM start (default: 30).poll_ms: Poll interval for envd health-check retries (default: 10).
-
[orchestrator]auto_evict_interval_ms: Poll interval for background timeout eviction.default_sandbox_timeout_secs: Default keep-alive timeout used by the orchestrator.auto_resume_min_sandbox_timeout_secs: When a data-plane request targets a non-running sandbox, automatically resume it (if auto-resume is enabled) and refresh its timeout for no-less than this duration.
-
[pool]low_watermark: Shared lower bound for warm resources. Defaults to2.high_watermark: Shared upper bound target for warm resources. Defaults to64.[pool.network].maintenance_enabled: Enables the background network-slot maintenance worker. Defaults totrue.[pool.block].enabled: Enables the ublk overlaybd warm-device pool.[pool.firecracker].enabled: Enables pre-spawned Firecracker processes for snapshot resume.
-
[snapshot]local_cache_path: Manager-owned node-local snapshot artifact/cache root. Defaults to$AENV_HOME/snapshot-local-cache.repository_backend: Snapshot repository backend. Defaults toposix_fs.
-
[backend.posix_fs]snapshot_store: Committed snapshot store root. Defaults to$AENV_HOME/snapshot-store.
-
[ublk]daemon_binary_path: Optional path touvm-ublk-daemon.daemon_socket_path: Optional unix socket path for daemon RPCs.daemon_log_path: Optional log file path for the daemon process.
-
[ublk.overlaybd]global_config_path: path to the generated overlaybd runtime config. Required for ublk. Per-image configs are derived from template buildfromImagevalues and live under<image.cache.root_dir>/configs.
Defaults and optional fields
[firecracker].binary_path: Optional. If omitted, AgentENV derives it fromdeps_pathand the bundled dependency manifest.[firecracker].boot_args: Optional. Defaults toconsole=ttyS0 reboot=k panic=1 pci=off.[firecracker].socket_timeout_secs: Optional. Defaults to 3 seconds.[firecracker].socket_poll_ms: Optional. Defaults to 20 ms.[firecracker].work_dir: Optional override. Defaults to$AENV_HOME/firecracker-work.[firecracker].serial_dir: Optional override. Defaults to$AENV_HOME/logs/serial.[kernel].image_path: Optional. If omitted, AgentENV derives it fromdeps_pathand the bundled dependency manifest.[tools].drive_path: Optional local source override; requires an explicit[tools].versionwhen set.[tools].url: Optional registry override; requires an explicit[tools].versionwhen set.[machine]: Optional. If omitted, defaults are 128 MiB RAM and 1 vCPU.[envd]: Optional, but template and sandbox flows expect a validversion.[envd].init_timeout_secs: Optional. Defaults to 30 seconds.[envd].poll_ms: Optional. Defaults to 10 ms.[orchestrator]: Optional. If omitted, orchestrator runtime defaults are used.[pool]: Optional. If omitted, network pool maintenance is enabled with low watermark 2 and high watermark 64; block and Firecracker process pools are disabled unless enabled in their component subsections.[snapshot]: Optional. If omitted,repository_backenddefaults toposix_fsand the local cache root derives fromAENV_HOME.[backend.posix_fs]: Optional. If omitted, the POSIX snapshot store defaults to$AENV_HOME/snapshot-store.[ublk]: Optional. If omitted, ublk is disabled.
Environment-based configuration
The config loader reads the full TOML file from the following sources:
AENV_CONFIG_PATH--config <path>config/default.toml
Runtime image and binary paths are configured in the TOML file itself.
Required fields
[kernel].image_path[tools].versionwhen[tools].drive_pathor[tools].urlis overridden[envd].version
If the [orchestrator] section is present, it must include
default_sandbox_timeout_secs.
ConfigManager (recommended entry point)
The helper is strict. If requirements are not met (missing files, invalid config), it returns an error and the test fails.
Path resolution order
AENV_CONFIG_PATHenvironment variable--configCLI flagconfig/default.toml(default)
Helper methods
ConfigManager::global()->Result<&'static ConfigManager>- Lazily initializes and returns a process-wide singleton.
ConfigManager::new()->Result<ConfigManager>- Loads config using the default resolution order.
ConfigManager::new_from_path(path)->Result<ConfigManager>- Loads a specific TOML file.
ConfigManager::config()->&AppConfig- Returns the parsed application config.
ConfigManager::global_config()->Result<&'static AppConfig>- Returns the global parsed config directly.
ConfigManager::get_orchestrator_config()->Option<OrchestratorConfig>- Returns orchestrator settings when configured.
AppConfig::resolved_snapshot_store()->PathBuf- Resolves the effective committed snapshot store root.
AppConfig::resolved_snapshot_local_cache_path()->PathBuf- Resolves the effective manager-owned node-local snapshot cache root.
Typical test setup flow
#![allow(unused)]
fn main() {
use agentenv::sandbox::{FirecrackerSandbox, FirecrackerSandboxConfig, SandboxExecutor};
let sandbox_config = FirecrackerSandboxConfig::from_global_config()?;
let mut sandbox = FirecrackerSandbox::new(sandbox_config)?;
sandbox.start().await?;
// Run commands via envd gRPC
let output = sandbox.run_command("echo", &["hello"]).await?;
assert_eq!(output.exit_code, 0);
let snapshot = sandbox.pause().await?;
sandbox.stop().await?;
let mut resumed_sandbox = FirecrackerSandbox::resume_from_snapshot_config(&snapshot).await?;
resumed_sandbox.stop().await?;
}
3) What the Integration Tests Validate
integration/fc.rs
-
microvm_startup_moves_to_runningBoots a fresh VM and verifies that:- Firecracker starts and accepts API calls.
- Kernel + rootfs can be configured without error.
- The VM can be cleanly shut down afterward.
-
microvm_pause_transitions_to_pausedValidates pause + snapshot creation by:- Pausing a running VM via FC API.
- Creating
vm_state.bin,mem_image.json, andmem_overlaybd/overlaybd.commit. - Ensuring those files exist on disk.
-
microvm_resume_from_pause_transitions_to_runningValidates in-place resume by:- Pausing a VM.
- Resuming it in the same sandbox instance.
- Confirming the control flow completes without error.
-
microvm_resume_transitions_to_runningValidates resume into a new sandbox by:- Pausing and snapshotting a VM.
- Shutting down the original sandbox.
- Resuming from the snapshot in a new sandbox instance.
-
snapshot_preserves_disk_state_on_resumeConfirms disk state is preserved by:- Writing a marker file via
run_command. - Pausing and resuming from the snapshot.
- Reading the marker file after resume and asserting it persists.
- Writing a marker file via
-
snapshot_chain_survives_after_parent_snapshot_handle_is_droppedValidates multi-level snapshot chains by:- Creating a snapshot, resuming, creating a second snapshot.
- Dropping the first snapshot handle.
- Resuming from the second snapshot and verifying disk state.
- Also tests
pause_to_dirwith snapshot-owned inherited layer adoption.
-
microvm_can_access_internetConfirms guest networking works before and after snapshot resume. -
multiple_resumes_have_independent_disk_stateConfirms multiple resumptions are isolated by:- Resuming multiple VMs from a single snapshot sequentially.
- Each resumed VM verifies the shared marker and writes its own file.
- Verifying each snapshot has independent state.
integration/process.rs
-
run_command_captures_stdoutRunsecho hello worldand verifies stdout contains the expected string. -
run_command_captures_stderrRuns a shell command that writes to stderr and verifies stderr capture. -
run_command_reports_exit_codeRunsexit 42and verifies the exit code is reported correctly. -
run_command_with_opts_sets_env_and_cwdVerifies that environment variables and working directory options are honoured. -
run_command_handles_timeoutVerifies long-running commands fail when a timeout is configured. -
run_command_with_unbounded_outputVerifies excessive output is rejected instead of buffering forever. -
start_process_interactive_stdinStartscat, sends data via stdin, kills the process, and verifies the echoed output. -
start_process_send_signalStartssleep 300, sends SIGTERM, and verifies the process terminates with a non-zero exit code. -
run_command_after_snapshot_resumePauses a sandbox, resumes from a snapshot config, and verifiesrun_commandworks on the resumed instance.
integration/snapshot.rs
-
built_snapshot_can_be_loaded_by_alias_and_launchedBuilds a template-backed snapshot, loads it by alias, launches a sandbox from the resolved runnable snapshot, and verifies captured runtime state. -
listed_committed_snapshot_can_be_loaded_and_resolvedVerifiesSnapshotManager::list_committed()returns usable committed snapshots that can be loaded and resolved into runnable paths. -
loaded_runnable_snapshot_by_alias_can_be_resolvedVerifiesSnapshotManager::load_runnable()resolves alias-based lookups into runnable snapshot state in one step. -
delete_committed_snapshot_by_alias_removes_it_from_repositoryVerifies deleting a committed snapshot by alias removes the durable record. -
snapshot_rebuild_from_committed_snapshot_preserves_base_stateVerifies rebuilding from a committed snapshot preserves the base filesystem state while adding new changes. -
rebuild_merges_env_metadata_and_list_filter_can_select_by_idVerifies rebuilds merge environment metadata correctly and that committed snapshot listing can filter by snapshot id.
integration/orchestrator.rs
orchestrator_lifecycleVerifies the orchestrator can create, fetch, list, filter, keep alive, and delete template-backed sandboxes while keeping proxy lookup state in sync.
4) End-to-End Execution Checklist
- Provide Firecracker binary, kernel, tools drive, overlaybd runtime, and ublk daemon. These are automatically downloaded when the server starts, or you can run
cargo run --bin server -- --setup-onlyto provision runtime dependencies independently. - Provision host access once as root with
server --setup-host --runtime-user <user> --runtime-group <group>. The group is the runtime service group: it owns AgentENV state and receives ublk device access. Normal server startup performs validation only and never invokessudo. - Host setup installs a udev rule for
/dev/ublk-control,/dev/ublkc*, and/dev/ublkb*, so the runtime group can access the control and dynamic device nodes. - Update
config/default.tomlpaths, or pointAENV_CONFIG_PATHto a custom config file. - Ensure
/dev/kvmis accessible by the runtime user and the configured virtualization mode matches the host modules. - If you run template tests, ensure the host can run
regctl(server setup installs it automatically from the[regclient]manifest entry) and access the registry for templatefromImageresolution. - Run
scripts/tests/e2e/run_e2e.shfor API-level E2E coverage. The runner exportsE2E_TEMPLATE_USER_IMAGE. Suite05_template_lifecycle.shalso creates a template build withE2E_SHORT_USER_IMAGEto verify short-name image resolution. - Run
make test-agent-integrationto run theagentenvintegration test modules intests/integration/as a non-root user with the required capabilities, plus the Docker/MinIO-backed OSS snapshot repository test (crates/e2e-tests/tests/snapshot_oss_e2e_test.rs). - Run
make test-ublkto run theuvm-ublkandoverlaybdstorage tests, including the Docker/MinIO-backed OSS backend test (storage/overlaybd/tests/oss_backend_minio.rs).
4.1) Firecracker Client and Runtime Upgrade Checklist
When bumping AgentENV to a patched Firecracker runtime, update both the generated client API and the binary that setup downloads:
-
Replace
thirdparty/firecracker-client/firecracker.yamlwith the target Firecracker OpenAPI spec. -
Run
make firecracker-clientfrom the repository root and review generated changes underthirdparty/firecracker-client/. -
Build the patched Firecracker release binary on a Linux host. Package it as a gzip tar archive named
firecracker-{version}-{arch}.tgzcontaining afirecrackerexecutable. -
Update the
[firecracker]entry inconfig/deps_manifest.tomlwith the new version and a download URL template that supports{version}and{arch}. -
Before relying on the URL in tests, verify it is a direct download:
curl -L "<url>" -o /tmp/firecracker.tgz tar -tzf /tmp/firecracker.tgzThe archive listing should include
firecracker. -
Run setup against a clean or temporary Firecracker dependency directory so it really downloads the archive:
cargo run --bin server -- --config /path/to/config.toml --setup-only -
Validate the Rust side after code generation:
cargo fmt --check -p firecracker_client -p agentenv cargo check -p firecracker_client cargo check -p agentenv cargo test -p agentenv --lib
5) Running Custom Scripts
Run commands via envd
Once the sandbox is started (with a rootfs that includes the envd daemon), you can execute commands inside the VM using the process API:
#![allow(unused)]
fn main() {
// Simple command
let output = sandbox.run_command("ls", &["-la", "/tmp"]).await?;
println!("exit={} stdout={}", output.exit_code, output.stdout);
// Command with options
use agentenv::sandbox::ProcessOpts;
let opts = ProcessOpts::new()
.with_cwd("/tmp")
.with_envs([("KEY".into(), "value".into())].into());
let output = sandbox.run_command_with_opts("env", &[], opts).await?;
// Interactive / long-running process
let mut handle = sandbox.start_process("cat", &[], ProcessOpts::default()).await?;
handle.send_stdin(b"hello\n").await?;
handle.kill().await?;
let output = handle.wait().await?;
}
The process API uses envd’s gRPC ProcessClient under the hood. It requires
the envd daemon to be running inside the guest VM.
Run different scripts for multiple snapshot resumes
/init is not re-executed after resume, so per-instance behavior must come
from per-instance disk contents.
A typical pattern:
- Start a base VM and
pause()to create a snapshot. - For each instance, copy the snapshot rootfs and write a per-instance file
into it (for example using
debugfs). - Create a
FirecrackerSnapshotConfigthat points to that per-instance rootfs and resume.
The guest must keep a long-running dispatcher (started during the initial boot) that watches for these per-instance files and executes them after resume.