Overview
SmolVM provides multiple ways to inject environment variables into guest VMs:
At VM creation - via VMConfig.env_vars (injected during boot)
At runtime - via set_env_vars() method (dynamic injection)
Manual management - read, update, and remove variables on running VMs
Variables are persisted to /etc/profile.d/smolvm_env.sh inside the guest and automatically sourced by new login shells.
Injecting 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 ,
mem_size_mib = 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:
VM boots normally
SmolVM waits for SSH to become available
Variables are written to /etc/profile.d/smolvm_env.sh
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.
Runtime Variable Management
Dynamically set, update, and remove variables on a running VM:
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="
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 ( " \n 1) Set environment variables" )
vm.set_env_vars({ "APP_MODE" : "dev" , "DEBUG" : "1" })
print (vm.list_env_vars())
print ( " \n 2) 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 ( " \n 3) Remove one variable" )
removed = vm.unset_env_vars([ "DEBUG" ])
print ( f "Removed: { removed } " )
print (vm.list_env_vars())
print ( " \n Done." )
return 0
if __name__ == "__main__" :
raise SystemExit (main())
Advanced: Injecting Host Environment
Pass environment variables from your host into the VM:
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 ,
mem_size_mib = 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.
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
1. 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" ,
},
)
2. 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" )
3. 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:
Verify SSH support
if not vm.can_run_commands():
print ( "VM does not support SSH - cannot inject variables" )
Check the file was created
result = vm.run( "cat /etc/profile.d/smolvm_env.sh" )
print (result.output)
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