Isolated OTP application management with hot-reload capabilities for Elixir.
Sandbox enables you to create, manage, and hot-reload isolated OTP applications (sandboxes) within your Elixir system. Each sandbox runs with its own supervision tree and namespace-transformed modules, providing process-level separation suitable for plugin systems, development workflows, and learning environments.
- Process Isolation: Each sandbox has its own supervision tree and process hierarchy
- Hot Reload: Update running sandboxes without restarting
- Version Management: Track and rollback module versions
- Fault Tolerance: Sandbox crashes don't affect the host application
- Resource Control: Compile-time limits and process monitoring
- Isolated Compilation: Compile code in a separate process with configurable timeouts
Comprehensive guides are available to help you get the most out of Sandbox:
- Getting Started - Installation, setup, and your first sandbox
- Hot Reload - Live code updates, version management, and state preservation
- Module Transformation - Namespace isolation and virtual code tables
- Compilation - Compilation backends, caching, and security
- Configuration - All configuration options and security profiles
- Architecture - System design, components, and extension points
- Evolution Substrate - Vision for genetic programming and self-modifying systems
Note: For API documentation, run
mix docsto generate HTML documentation locally, or visit the HexDocs.
Add sandbox to your list of dependencies in mix.exs:
def deps do
[
{:sandbox, "~> 0.1.0"}
]
endAdd Sandbox to your supervision tree:
children = [
Sandbox,
# ... other children
]
Supervisor.start_link(children, strategy: :one_for_one)# Create a sandbox with a supervisor module
{:ok, sandbox} = Sandbox.create_sandbox("my-sandbox", MyApp.Supervisor)
# Or with custom options
{:ok, sandbox} = Sandbox.create_sandbox("plugin-1", :my_plugin,
supervisor_module: MyPlugin.Supervisor,
sandbox_path: "/path/to/plugin/code"
)# Compile new code
{:ok, compile_info} = Sandbox.compile_file("updated_module.ex")
# Hot reload the module
{:ok, :hot_reloaded} = Sandbox.hot_reload("my-sandbox",
compile_info.beam_files |> hd() |> File.read!())# List all sandboxes
sandboxes = Sandbox.list_sandboxes()
# Get sandbox info
{:ok, info} = Sandbox.get_sandbox_info("my-sandbox")
# Restart a sandbox
{:ok, _} = Sandbox.restart_sandbox("my-sandbox")
# Destroy a sandbox
:ok = Sandbox.destroy_sandbox("my-sandbox")Sandbox tracks all module versions and supports rollback:
# Check current version
{:ok, version} = Sandbox.get_module_version("my-sandbox", MyModule)
# List all versions
versions = Sandbox.list_module_versions("my-sandbox", MyModule)
# Rollback to a previous version
{:ok, :rolled_back} = Sandbox.rollback_module("my-sandbox", MyModule, 2)
# Get version history with statistics
history = Sandbox.get_version_history("my-sandbox", MyModule)
# => %{current_version: 3, total_versions: 3, versions: [...]}When hot-reloading GenServers, you can provide custom state migration logic:
{:ok, :hot_reloaded} = Sandbox.hot_reload("my-sandbox", beam_data,
state_handler: fn old_state, old_version, new_version ->
# Migrate state from old version to new version
%{old_state | version: new_version, upgraded_at: DateTime.utc_now()}
end
)Compile code in a separate process with configurable timeouts:
# Compile a directory
{:ok, compile_info} = Sandbox.compile_sandbox("/path/to/sandbox",
timeout: 60_000,
validate_beams: true
)
# Compile a single file
{:ok, compile_info} = Sandbox.compile_file("my_module.ex")
# Access compilation results
compile_info.beam_files # List of compiled BEAM files
compile_info.output # Compiler output
compile_info.compilation_time # Time taken in millisecondsCreate a plugin system where each plugin runs in its own supervision tree:
defmodule MyApp.PluginManager do
def load_plugin(plugin_path) do
plugin_id = Path.basename(plugin_path)
# Create sandbox for the plugin
{:ok, _} = Sandbox.create_sandbox(plugin_id, :plugin,
supervisor_module: Plugin.Supervisor,
sandbox_path: plugin_path
)
# Plugin is now running in its own supervision tree
end
def update_plugin(plugin_id, new_code_path) do
# Compile new version
{:ok, compile_info} = Sandbox.compile_sandbox(new_code_path)
# Hot reload each module
Enum.each(compile_info.beam_files, fn beam_file ->
beam_data = File.read!(beam_file)
Sandbox.hot_reload(plugin_id, beam_data)
end)
end
endCreate separate environments for experimenting with OTP patterns:
defmodule LearningPlatform do
def create_exercise_sandbox(user_id, exercise_code) do
sandbox_id = "exercise_#{user_id}"
# Write exercise code to temporary directory
temp_dir = Path.join(System.tmp_dir!(), sandbox_id)
File.mkdir_p!(temp_dir)
File.write!(Path.join(temp_dir, "exercise.ex"), exercise_code)
# Compile and run in sandbox
{:ok, compile_info} = Sandbox.compile_sandbox(temp_dir)
{:ok, _} = Sandbox.create_sandbox(sandbox_id, ExerciseSupervisor,
sandbox_path: temp_dir
)
# Student's code now runs in its own supervision tree
end
endSandbox is built on a modular architecture with clearly separated concerns:
┌─────────────────────────────────────────────────────────────┐
│ Sandbox.Application │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Manager │ │ ModuleVersion │ │ Resource │ │
│ │ (lifecycle) │ │ Manager │ │ Monitor │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Security │ │ Process │ │ Isolated │ │
│ │ Controller │ │ Isolator │ │ Compiler │ │
│ └─────────────┘ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Each sandbox has its own supervision tree and can be hot-reloaded independently without affecting others. For detailed architecture documentation, see the Architecture Guide.
- Set appropriate timeouts for compilation and execution
- Monitor sandbox resource usage in production
- Use version management for safe rollbacks
- Test state migration handlers thoroughly
- Clean up sandboxes when no longer needed
This is not a security sandbox. Sandboxes share the same Erlang VM and are not suitable for running untrusted or potentially malicious code. All sandboxes share:
- The global code server and atom table
- Memory address space
- Scheduler infrastructure
- Network and file system access
Other limitations:
- Memory and resource limits are advisory, not enforced
- Security profiles audit operations but do not prevent them
- Hot-reload requires proper GenServer
code_change/3implementation - Some BEAM instructions can affect the entire VM
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -am 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Create a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.