Config & Secrets Injection
mvm supports injecting custom files onto the guest’s config and secrets drives at boot time. Files are written to the drive images before the VM starts.
CLI Usage
Section titled “CLI Usage”mkdir -p /tmp/my-config /tmp/my-secrets
echo '{"gateway": {"port": 8080}}' > /tmp/my-config/app.jsonecho 'API_KEY=sk-...' > /tmp/my-secrets/app.env
mvmctl up --template my-app \ --volume /tmp/my-config:/mnt/config \ --volume /tmp/my-secrets:/mnt/secretsThe --volume (-v for short) flag uses the format host_dir:/guest/path:
| Guest path | Drive | Permissions | Purpose |
|---|---|---|---|
/mnt/config | /dev/vdb | Read-only (0444) | Application configuration |
/mnt/secrets | /dev/vdc | Read-only (0400) | API keys, tokens, credentials |
Every file in the host directory is written to the corresponding drive image. For persistent volumes with explicit size, use the 3-part format: --volume host:/guest/path:size.
Library API
Section titled “Library API”The same functionality is available programmatically for library consumers:
use mvm_runtime::vm::microvm::{DriveFile, FlakeRunConfig};
let config = FlakeRunConfig { config_files: vec![DriveFile { name: "app.json".into(), content: serde_json::to_string(&app_config)?, mode: 0o444, }], secret_files: vec![DriveFile { name: "app.env".into(), content: format!("API_KEY={}", api_key), mode: 0o400, }], ..base_config};Secret Bindings
Section titled “Secret Bindings”For AI agent workloads, use --secret to bind environment variable secrets to specific target domains. This provides domain-scoped secret injection — combine with --network-preset to prevent exfiltration:
mvmctl up --flake . \ --secret OPENAI_API_KEY:api.openai.com \ --secret ANTHROPIC_API_KEY:api.anthropic.com:x-api-key \ --network-preset devBinding syntax:
| Format | Meaning |
|---|---|
KEY:host | Read KEY from host env, bind to host (Authorization header) |
KEY:host:header | Custom HTTP header name |
KEY=value:host | Explicit value instead of env lookup |
KEY=value:host:header | Explicit value + custom header |
What happens at boot:
- Secret values are resolved (from host env or explicit) and written to the secrets drive (mode 0600)
- A
secrets-manifest.jsonis written to the config drive (metadata only, no values) - Placeholder env vars (
mvm-managed:KEY) are set in the guest environment so tools pass existence checks - Combined with network allowlists, the VM can only send traffic to the allowed domains
This is the “config-drive injection” approach. The secret values are on the guest’s secrets drive but are scoped to specific domains via network policy. A future upgrade will add MITM proxy-based injection where secrets never touch the guest filesystem.
Design
Section titled “Design”The DriveFile type is content-agnostic — it’s just {name, content, mode}. It knows nothing about specific file formats or keys. This means:
- Any file format works (JSON, TOML, YAML, env files, certificates, etc.)
- Adding support for new applications doesn’t require code changes
- NixOS
EnvironmentFilecan load.envfiles directly as systemd environment variables
Example: OpenClaw
Section titled “Example: OpenClaw”The OpenClaw example demonstrates all of these features. It ships with a default config baked into the image, but you can override it by mounting host directories.
Running with example config
Section titled “Running with example config”mvmctl template build openclawmvmctl up --template openclaw --name oc \ -v nix/examples/openclaw/config:/mnt/config \ -v nix/examples/openclaw/secrets:/mnt/secrets \ -p 3000:3000mvmctl forward oc 3000:3000The default config uses auth.mode: "none" — no token is required to access the Control UI. The gateway binds to loopback inside the VM with a TCP proxy forwarding external connections, so all connections appear local and are auto-approved by OpenClaw (no device pairing prompts). To enable token auth, set "auth": { "mode": "token" } in your config and OPENCLAW_GATEWAY_TOKEN in secrets/api-keys.env.
Running with custom config and API keys
Section titled “Running with custom config and API keys”# Create config directory with OpenClaw gateway settingsmkdir -p /tmp/oc-configcat > /tmp/oc-config/openclaw.json << 'EOF'{ "gateway": { "mode": "local", "channelHealthCheckMinutes": 0, "auth": { "mode": "none" }, "reload": { "mode": "off" }, "controlUi": { "dangerouslyAllowHostHeaderOriginFallback": true } }}EOF
# Create secrets directory with API keysmkdir -p /tmp/oc-secretscat > /tmp/oc-secrets/api-keys.env << 'EOF'ANTHROPIC_API_KEY=sk-ant-...EOF
mvmctl up --template openclaw --name oc \ -v /tmp/oc-config:/mnt/config \ -v /tmp/oc-secrets:/mnt/secrets \ -p 3000:3000The OpenClaw service’s preStart script checks for /mnt/config/openclaw.json and uses it (with envsubst expansion) instead of the built-in default. The command script sources /mnt/config/env.sh and /mnt/secrets/api-keys.env if they exist, making environment variables available to the gateway process.
Using snapshots for fast startup
Section titled “Using snapshots for fast startup”Build the template with --snapshot to capture a running VM state. Subsequent runs restore from the snapshot instead of cold-booting, reducing startup time from minutes to 1-2 seconds:
mvmctl template build openclaw --snapshotmvmctl up --template openclaw --name oc \ -v nix/examples/openclaw/config:/mnt/config \ -v nix/examples/openclaw/secrets:/mnt/secrets \ -p 3000:3000When restoring from a snapshot with -v mounts, the guest agent automatically remounts config/secrets drives and restarts services with the fresh data.
Snapshots + Dynamic Mounts = Instant Boots with Flexible Config
Section titled “Snapshots + Dynamic Mounts = Instant Boots with Flexible Config”Key insight: The snapshot stores the OS and application state (memory, running processes, compiled code caches), but config and secrets drives are created fresh at runtime from your host directories. This means:
- ✅ Same snapshot can serve multiple instances with different configs
- ✅ Update configs without rebuilding — just change the files in your host directory
- ✅ Instant boot + dynamic configuration — get both benefits simultaneously
Example: Run three OpenClaw instances from one snapshot with different API keys:
# Production gateway with prod Anthropic keymvmctl up --template openclaw --name oc-prod \ -v ./prod/config:/mnt/config \ -v ./prod/secrets:/mnt/secrets \ -p 3000:3000
# Staging gateway with test keymvmctl up --template openclaw --name oc-staging \ -v ./staging/config:/mnt/config \ -v ./staging/secrets:/mnt/secrets \ -p 3001:3000
# Dev gateway with no key (localhost-only testing)mvmctl up --template openclaw --name oc-dev \ -v ./dev/config:/mnt/config \ -p 3002:3000All three VMs restore from the same snapshot (1-2 second boot) but get different configs and secrets at runtime.
Monitoring the VM
Section titled “Monitoring the VM”Check the OpenClaw gateway logs via the guest console:
mvmctl logs oc # view console outputmvmctl logs oc -f # follow in real timeSee nix/examples/openclaw/ for the full example with sample config and secrets files.