Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.celesto.ai/llms.txt

Use this file to discover all available pages before exploring further.

You can pass configuration values, API keys, and other settings into a sandbox using environment variables. SmolVM supports two approaches: setting variables when you create the sandbox, or adding them at runtime while the sandbox is running. The same Python API (env_vars=, vm.set_env_vars(...), vm.unset_env_vars(...), vm.list_env_vars(...)) works for both Linux and Windows guests. On Linux the variables are written to /etc/profile.d/smolvm_env.sh and picked up by new login shells; on Windows they’re written to HKCU\Environment and picked up by new processes. See Windows guests below for the details specific to Windows.

Setting variables at boot

Set environment variables when creating a VM:
from smolvm import SmolVM, VMConfig
from smolvm.build import ImageBuilder, SSH_BOOT_ARGS

builder = ImageBuilder()
kernel, rootfs = builder.build_alpine_ssh()

config = VMConfig(
    vm_id="env-vm",
    vcpu_count=1,
    memory=512,
    kernel_path=kernel,
    rootfs_path=rootfs,
    boot_args=SSH_BOOT_ARGS,
    env_vars={  # Variables injected after boot
        "APP_MODE": "production",
        "DATABASE_URL": "postgres://localhost:5432/mydb",
        "API_KEY": "secret-key-value",
    },
)

with SmolVM(config) as vm:
    # Variables are automatically injected during start()
    result = vm.run("echo $APP_MODE")
    print(result.output)  # "production"

How it works

When env_vars is set in VMConfig:
  1. VM boots normally
  2. SmolVM waits for SSH to become available
  3. Variables are written to /etc/profile.d/smolvm_env.sh
  4. All subsequent login shells source these variables
Environment variable injection requires an SSH-capable image. Use ImageBuilder or auto-config mode to ensure your image supports this feature.

Setting variables at runtime

You can also add, update, and remove variables while a sandbox is running:
from smolvm import SmolVM

with SmolVM() as vm:
    # Set environment variables
    vm.set_env_vars({"APP_MODE": "dev", "DEBUG": "1"})

    # Use them in commands
    result = vm.run("echo APP_MODE=$APP_MODE DEBUG=$DEBUG")
    print(result.output)  # "APP_MODE=dev DEBUG=1"

    # List current variables
    env_vars = vm.list_env_vars()
    print(env_vars)  # {"APP_MODE": "dev", "DEBUG": "1"}

    # Remove a variable
    removed = vm.unset_env_vars(["DEBUG"])
    print(removed)  # {"DEBUG": "1"}

    # Verify it's gone
    result = vm.run("echo DEBUG=$DEBUG")
    print(result.output)  # "DEBUG="

Windows sandboxes

Environment variables work for Windows sandboxes too, using the same API. The only difference is where the values are stored.
from smolvm import SmolVM

with SmolVM(
    os="windows",
    image="~/.smolvm/images/win11.qcow2",
    ssh_user="smolvm",
    ssh_password="smolvm",
    env_vars={"OPENAI_API_KEY": "sk-..."},
) as vm:
    vm.wait_for_ssh()

    # Fresh SSH session, so the new value is visible.
    print(vm.run("$env:OPENAI_API_KEY").stdout.strip())

    vm.set_env_vars({"DEBUG": "1"})
    print(vm.list_env_vars())
    vm.unset_env_vars(["DEBUG"])
How it works on Windows:
  • SmolVM runs [Environment]::SetEnvironmentVariable(name, value, 'User') over SSH, which writes the value into the HKCU\Environment registry hive — the standard per-user environment store on Windows.
  • New processes inherit the updated environment automatically. The SSH session that issued the change does notvm.run(...) opens a fresh session every call, so the next vm.run(...) sees the new value.
  • SmolVM tracks which keys it owns via a SMOLVM_ENV_MANAGED_KEYS sentinel value. list_env_vars() and unset_env_vars() only ever touch variables SmolVM set; anything you configured inside Windows yourself is left untouched.
  • Values are passed through verbatim — spaces, embedded quotes, and special characters are escaped safely by SmolVM before the PowerShell call.
Variable changes are visible to new processes, not the current one. Inside a single SSH session you can verify the change by spawning a fresh process — for example start-process powershell -wait, or simply rely on the fact that the next vm.run(...) call will see the new value.

Method reference

set_env_vars()

def set_env_vars(
    self,
    env_vars: dict[str, str],
    *,
    merge: bool = True,
) -> list[str]:
    """Set environment variables on a running VM.

    Variables are persisted in /etc/profile.d/smolvm_env.sh and
    affect new SSH sessions/login shells.

    Args:
        env_vars: Key/value pairs to set.
        merge: If True (default), merge with existing variables.

    Returns:
        Sorted variable names present after update.
    """
Example:
# Merge with existing variables (default)
vm.set_env_vars({"NEW_VAR": "value"})

# Replace all variables
vm.set_env_vars({"ONLY_VAR": "value"}, merge=False)

list_env_vars()

def list_env_vars(self) -> dict[str, str]:
    """Return SmolVM-managed environment variables for a running VM."""
Example:
env_vars = vm.list_env_vars()
for key, value in env_vars.items():
    print(f"{key}={value}")

unset_env_vars()

def unset_env_vars(self, keys: list[str]) -> dict[str, str]:
    """Remove environment variables from a running VM.

    Args:
        keys: Variable names to remove.

    Returns:
        Mapping of removed keys to their previous values.
    """
Example:
# Remove multiple variables
removed = vm.unset_env_vars(["VAR1", "VAR2"])
print(f"Removed: {removed}")

Complete example

From examples/env_injection.py:
from smolvm import SmolVM

def main() -> int:
    with SmolVM() as vm:
        print(f"VM started: {vm.vm_id}")

        print("\n1) Set environment variables")
        vm.set_env_vars({"APP_MODE": "dev", "DEBUG": "1"})
        print(vm.list_env_vars())

        print("\n2) Use env vars in a command")
        # vm.run() opens a fresh SSH session, so new values are available
        print(vm.run("echo APP_MODE=$APP_MODE DEBUG=$DEBUG").output)

        print("\n3) Remove one variable")
        removed = vm.unset_env_vars(["DEBUG"])
        print(f"Removed: {removed}")
        print(vm.list_env_vars())

    print("\nDone.")
    return 0

if __name__ == "__main__":
    raise SystemExit(main())

Passing host environment into the sandbox

You can forward environment variables from your host machine into the sandbox. This is useful for sharing API keys without hardcoding them:
import os
from smolvm import SmolVM, VMConfig
from smolvm.build import ImageBuilder, SSH_BOOT_ARGS

def collect_host_env() -> dict[str, str]:
    """Collect API keys from host environment."""
    env_vars = {}

    if api_key := os.getenv("OPENAI_API_KEY"):
        env_vars["OPENAI_API_KEY"] = api_key

    if api_key := os.getenv("ANTHROPIC_API_KEY"):
        env_vars["ANTHROPIC_API_KEY"] = api_key

    return env_vars

builder = ImageBuilder()
kernel, rootfs = builder.build_alpine_ssh()

config = VMConfig(
    vm_id="host-env-vm",
    vcpu_count=1,
    memory=512,
    kernel_path=kernel,
    rootfs_path=rootfs,
    boot_args=SSH_BOOT_ARGS,
    env_vars=collect_host_env(),  # Inject host environment
)

with SmolVM(config) as vm:
    result = vm.run("env | grep API_KEY")
    print(result.output)
This pattern is used in examples/openclaw.py to inject OPENROUTER_API_KEY and OPENAI_API_KEY from the host.

Host-side variables SmolVM reads

A few environment variables on your host machine change how SmolVM itself behaves. Set these in your shell, not inside the sandbox.

SmolVM behavior

SMOLVM_BACKEND
string
Override the virtual machine monitor SmolVM uses. Accepts firecracker, qemu, or auto (the default). On macOS the default is QEMU; on Linux it is Firecracker.
SMOLVM_USE_PUBLISHED
string
default:"1"
Controls the published-image fast path. On by default. Set to 0, false, or no to force SmolVM to build images locally instead of downloading pre-built ones. Useful when you’re testing changes to a preset’s install script.
SMOLVM_VERBOSE_BOOT
string
Show the guest kernel’s full boot log on the serial console. Off by default — SmolVM appends quiet to the kernel command line so boots stay fast and clean. Set to 1, true, yes, or on to drop quiet and surface every kernel message, which is the first thing to try when a sandbox hangs during start or panics before the agent answers.
SMOLVM_VERBOSE_BOOT=1 smolvm run "echo hello"
This only affects sandboxes that use the default low-latency boot profile (MICROVM_DIRECT). It does not change behaviour after the guest is up.

Coding-agent presets

When you launch a coding-agent preset, SmolVM forwards a small list of host environment variables into the sandbox so the agent can authenticate without an extra login step.
OPENAI_API_KEY
string
Forwarded to presets that talk to OpenAI APIs (for example codex).
OPENROUTER_API_KEY
string
Forwarded to the openclaw preset and any preset that supports OpenRouter.
OPENCLAW_GATEWAY_TOKEN
string
Forwarded to the openclaw preset. OpenClaw rejects boot with a clear error message if neither this nor OPENCLAW_GATEWAY_PASSWORD is set, so populate one of the two before running smolvm openclaw start.
OPENCLAW_GATEWAY_PASSWORD
string
Alternative to OPENCLAW_GATEWAY_TOKEN for the openclaw preset. Use whichever credential type matches your OpenClaw deployment.
Set these in your shell profile (~/.zshrc, ~/.bashrc) so every smolvm openclaw start picks them up automatically. They never get baked into a published image — they only travel into a sandbox you boot.

Variable validation

SmolVM validates environment variable keys:
from smolvm.env import validate_env_key

# Valid keys
validate_env_key("MY_VAR")      # OK
validate_env_key("_PRIVATE")    # OK
validate_env_key("VAR_123")     # OK

# Invalid keys
validate_env_key("")            # ValueError: cannot be empty
validate_env_key("123VAR")      # ValueError: must start with letter or _
validate_env_key("MY-VAR")      # ValueError: only [A-Za-z0-9_] allowed
Keys must match the pattern: [A-Za-z_][A-Za-z0-9_]*

Persistence details

File location

Variables are stored in /etc/profile.d/smolvm_env.sh inside the guest:
# Inside the VM
$ cat /etc/profile.d/smolvm_env.sh
#!/bin/sh
# SmolVM managed environment variables

export APP_MODE='production'
export DATABASE_URL='postgres://localhost:5432/mydb'

Atomic updates

All writes are atomic (write to temp file → mv into place) to prevent partial updates on failure.

Quoting and escaping

SmolVM uses shlex.quote() to safely handle special characters:
vm.set_env_vars({
    "MESSAGE": "Hello, World!",
    "PATH_VAR": "/usr/local/bin:/usr/bin",
    "COMPLEX": "value with 'quotes' and spaces",
})

# All values are safely quoted in the generated shell script

Use cases

Configuration management

config = VMConfig(
    vm_id="app-vm",
    # ... other config ...
    env_vars={
        "DATABASE_URL": "postgres://db:5432/app",
        "REDIS_URL": "redis://cache:6379",
        "LOG_LEVEL": "info",
    },
)

Secret injection

import os

with SmolVM() as vm:
    # Inject secrets from host environment or secrets manager
    vm.set_env_vars({
        "API_KEY": os.getenv("API_KEY", "default-key"),
        "DB_PASSWORD": get_secret("db-password"),
    })

    vm.run("my-application")

Dynamic configuration updates

with SmolVM() as vm:
    # Start in dev mode
    vm.set_env_vars({"APP_MODE": "dev"})
    vm.run("start-app")

    # Switch to production mode
    vm.set_env_vars({"APP_MODE": "production"})
    vm.run("restart-app")

Troubleshooting

Variables not available

If variables don’t appear in your commands:
1

Verify SSH support

if not vm.can_run_commands():
    print("VM does not support SSH - cannot inject variables")
2

Check the file was created

result = vm.run("cat /etc/profile.d/smolvm_env.sh")
print(result.output)
3

Ensure you're using a login shell

Variables are only available in login shells:
# This works - uses login shell (default)
vm.run("echo $MY_VAR", shell="login")

# This may not work - raw execution
vm.run("echo $MY_VAR", shell="raw")

Next steps

Custom images

Build images with pre-baked environment configuration

AI agent integration

Use environment variables for agent configuration

Port forwarding

Expose services configured via environment variables
Last modified on June 2, 2026