virt.heropods #
HeroPods
HeroPods is a lightweight container management system built on crun (OCI runtime), providing Docker-like functionality with bridge networking, automatic IP allocation, and image management via Podman.
Requirements
Platform: Linux only
HeroPods requires Linux-specific tools and will not work on macOS or Windows:
crun(OCI runtime)ip(iproute2 package)iptables(for NAT)nsenter(for network namespace management)podman(optional, for image management)
On macOS/Windows, please use Docker or Podman directly instead of HeroPods.
Quick Start
Basic Usage
import incubaid.herolib.virt.heropods
// Initialize HeroPods
mut hp := heropods.new(
reset: false
use_podman: true
)!
// Create a container (definition only, not yet created in backend)
mut container := hp.container_new(
name: 'my_alpine'
image: .custom
custom_image_name: 'alpine_3_20'
docker_url: 'docker.io/library/alpine:3.20'
)!
// Start the container (creates and starts it)
// Use keep_alive for containers with short-lived entrypoints
container.start(keep_alive: true)!
// Execute commands
result := container.exec(cmd: 'ls -la /')!
println(result)
// Stop and delete
container.stop()!
container.delete()!
Custom Network Configuration
Configure bridge name, subnet, gateway, and DNS servers:
import incubaid.herolib.virt.heropods
// Initialize with custom network settings
mut hp := heropods.new(
reset: false
use_podman: true
bridge_name: 'mybr0'
subnet: '192.168.100.0/24'
gateway_ip: '192.168.100.1'
dns_servers: ['1.1.1.1', '1.0.0.1']
)!
// Containers will use the custom network configuration
mut container := hp.container_new(
name: 'custom_net_container'
image: .alpine_3_20
)!
container.start(keep_alive: true)!
Using HeroScript
!!heropods.configure
name:'my_heropods'
reset:false
use_podman:true
!!heropods.container_new
name:'my_container'
image:'custom'
custom_image_name:'alpine_3_20'
docker_url:'docker.io/library/alpine:3.20'
!!heropods.container_start
name:'my_container'
keep_alive:true
!!heropods.container_exec
name:'my_container'
cmd:'echo "Hello from HeroPods!"'
stdout:true
!!heropods.container_stop
name:'my_container'
!!heropods.container_delete
name:'my_container'
Mycelium IPv6 Overlay Network
HeroPods supports Mycelium for end-to-end encrypted IPv6 connectivity:
!!heropods.configure
name:'mycelium_demo'
reset:false
use_podman:true
!!heropods.enable_mycelium
heropods:'mycelium_demo'
version:'v0.5.6'
ipv6_range:'400::/7'
key_path:'~/hero/cfg/priv_key.bin'
peers:'tcp://185.69.166.8:9651,quic://[2a02:1802:5e:0:ec4:7aff:fe51:e36b]:9651'
!!heropods.container_new
name:'ipv6_container'
image:'alpine_3_20'
!!heropods.container_start
name:'ipv6_container'
keep_alive:true
// Container now has both IPv4 and IPv6 (Mycelium) connectivity
See MYCELIUM.md for detailed Mycelium configuration.
Keep-Alive Feature
The keep_alive parameter keeps containers running after their entrypoint exits successfully. This is useful for:
- Short-lived entrypoints: Containers whose entrypoint performs initialization then exits (e.g., Alpine's
/bin/sh) - Interactive containers: Containers you want to exec into after startup
- Service containers: Containers that need to stay alive for background tasks
How it works:1. Container starts with its original ENTRYPOINT and CMD (OCI-compliant)2. HeroPods waits for the entrypoint to complete3. If entrypoint exits with code 0 (success), a keep-alive process is injected4. If entrypoint fails (non-zero exit), container stops and error is returned
Example:
// Alpine's default CMD is /bin/sh which exits immediately
mut container := hp.container_new(
name: 'my_alpine'
image: .custom
custom_image_name: 'alpine_3_20'
docker_url: 'docker.io/library/alpine:3.20'
)!
// Without keep_alive: container would exit immediately
// With keep_alive: container stays running for exec commands
container.start(keep_alive: true)!
// Now you can exec into the container
result := container.exec(cmd: 'echo "Hello!"')!
Note: If you see a warning about "bare shell CMD", use keep_alive: true when starting the container.
Features
- Container Lifecycle: create, start, stop, delete, exec
- Keep-Alive Support: Keep containers running after entrypoint exits
- IPv4 Bridge Networking: Automatic IP allocation with NAT
- IPv6 Mycelium Overlay: End-to-end encrypted peer-to-peer networking
- Image Management: Pull Docker images via Podman or use built-in images
- Resource Monitoring: CPU and memory usage tracking
- Thread-Safe: Concurrent container operations supported
- Configurable: Custom network settings, DNS, resource limits
Examples
See examples/virt/heropods/ for complete working examples:
HeroScript Examples
- simple_container.heroscript - Basic container lifecycle management
- ipv4_connection.heroscript - IPv4 networking and internet connectivity
- container_mycelium.heroscript - Mycelium IPv6 overlay networking
V Language Examples
- heropods.vsh - Complete API demonstration
- runcommands.vsh - Simple command execution
Each example is fully documented and can be run independently. See examples/virt/heropods/README.md for details.
Documentation
- MYCELIUM.md - Mycelium IPv6 overlay network integration guide
- PRODUCTION_READINESS_REVIEW.md - Production readiness assessment
- ACTIONABLE_RECOMMENDATIONS.md - Code quality recommendations
Constants #
const version = '0.0.0'
fn delete #
fn delete(args ArgsGet) !
Delete a HeroPods instance from Redis (does not affect memory cache)
fn exists #
fn exists(args ArgsGet) !bool
Check if a HeroPods instance exists in Redis
fn get #
fn get(args ArgsGet) !&HeroPods
Get a HeroPods instance by name If fromdb is true, loads from Redis; otherwise returns from memory cache
fn heroscript_loads #
fn heroscript_loads(heroscript string) !HeroPods
///////////NORMALLY NO NEED TO TOUCH
fn list #
fn list(args ArgsList) ![]&HeroPods
List all HeroPods instances If fromdb is true, loads from Redis and resets memory cache If fromdb is false, returns from memory cache
fn list_available_docker_images #
fn list_available_docker_images() []string
List available Docker images that can be downloaded
Returns a curated list of commonly used Docker images
fn new #
fn new(args ArgsGet) !&HeroPods
fn play #
fn play(mut plbook PlayBook) !
fn set #
fn set(o HeroPods) !
Register a HeroPods instance (saves to both memory and Redis)
fn switch #
fn switch(name string)
Switch the default HeroPods instance
Thread Safety Note: String assignment is atomic on most platforms, so no explicit locking is needed. If strict thread safety is required in the future, this could be wrapped in a lock.
enum ContainerImageType #
enum ContainerImageType {
alpine_3_20 // Alpine Linux 3.20
ubuntu_24_04 // Ubuntu 24.04 LTS
ubuntu_25_04 // Ubuntu 25.04
custom // Custom image downloaded via podman
}
ContainerImageType defines the available container base images
enum ContainerStatus #
enum ContainerStatus {
running // Container is running
stopped // Container is stopped or doesn't exist
paused // Container is paused
unknown // Unknown status (error case)
}
ContainerStatus represents the current state of a container
struct ArgsGet #
struct ArgsGet {
pub mut:
name string = 'default' // name of the heropods
fromdb bool // will load from filesystem
create bool // default will not create if not exist
reset bool // will reset the heropods
use_podman bool = true // will use podman for image management
// Network configuration
bridge_name string = 'heropods0'
subnet string = '10.10.0.0/24'
gateway_ip string = '10.10.0.1'
dns_servers []string = ['8.8.8.8', '8.8.4.4']
// Mycelium IPv6 overlay network configuration
enable_mycelium bool // Enable Mycelium IPv6 overlay network
mycelium_version string // Mycelium version to install (default: 'v0.5.6')
mycelium_ipv6_range string // Mycelium IPv6 address range (default: '400::/7')
mycelium_peers []string // Mycelium peer addresses (default: use public nodes)
mycelium_key_path string = '~/hero/cfg/priv_key.bin' // Path to Mycelium private key
}
///////FACTORY
struct ArgsList #
struct ArgsList {
pub mut:
fromdb bool // will load from filesystem
}
struct Container #
struct Container {
pub mut:
name string // Unique container name
node ?&builder.Node // Builder node for executing commands inside container
tmux_pane ?&tmux.Pane // Optional tmux pane for interactive access
crun_config ?&crun.CrunConfig // OCI runtime configuration
factory &HeroPods // Reference to parent HeroPods instance
}
Container represents a running or stopped OCI container managed by crun
Thread Safety: Container operations that interact with network configuration (start, stop, delete) are thread-safe because they delegate to HeroPods.network_* methods which use the network_mutex for protection.
fn (Container) start #
fn (mut self Container) start(args ContainerStartArgs) !
Start the container
This method handles the complete container startup lifecycle:1. Creates the container in crun if it doesn't exist2. Handles leftover state cleanup if creation fails3. Starts the container process4. Sets up networking (thread-safe via network_mutex)5. If keep_alive=true, waits for entrypoint to exit and injects keep-alive process
Parameters:- args.keep_alive: If true, the container will be kept alive after its entrypoint exits successfully.The entrypoint runs first, and if it exits with code 0, a keep-alive process (tail -f /dev/null) is injected to prevent the container from stopping. If the entrypoint fails (non-zero exit), the container is allowed to stop. Default: false
Thread Safety: Network setup is thread-safe via HeroPods.network_setup_container()
fn (Container) stop #
fn (mut self Container) stop() !
Stop the container gracefully (SIGTERM) or forcefully (SIGKILL)
This method:1. Sends SIGTERM for graceful shutdown2. Waits up to sigterm_timeout_ms for graceful stop3. Sends SIGKILL if still running after timeout4. Cleans up network resources (thread-safe)
Thread Safety: Network cleanup is thread-safe via HeroPods.network_cleanup_container()
fn (Container) delete #
fn (mut self Container) delete() !
Delete the container
This method:1. Checks if container exists in crun2. Stops the container (which cleans up network)3. Deletes the container from crun4. Removes from factory's container cache
Thread Safety: Network cleanup is thread-safe via stop() -> cleanup_network()
fn (Container) exec #
fn (mut self Container) exec(cmd_ osal.Command) !string
Execute command inside the container
fn (Container) status #
fn (self Container) status() !ContainerStatus
fn (Container) pid #
fn (self Container) pid() !int
Get the PID of the container's init process
fn (Container) cpu_usage #
fn (self Container) cpu_usage() !f64
Get CPU usage in percentage
fn (Container) mem_usage #
fn (self Container) mem_usage() !f64
Get memory usage in MB
fn (Container) tmux_pane #
fn (mut self Container) tmux_pane(args TmuxPaneArgs) !&tmux.Pane
fn (Container) node #
fn (mut self Container) node() !&builder.Node
fn (Container) config #
fn (self Container) config() !&crun.CrunConfig
Get the crun configuration for this container
fn (Container) set_memory_limit #
fn (mut self Container) set_memory_limit(limit_mb u64) !&Container
Container configuration customization methods
fn (Container) set_cpu_limits #
fn (mut self Container) set_cpu_limits(period u64, quota i64, shares u64) !&Container
fn (Container) add_mount #
fn (mut self Container) add_mount(source string, destination string, mount_type crun.MountType, options []crun.MountOption) !&Container
fn (Container) add_capability #
fn (mut self Container) add_capability(cap crun.Capability) !&Container
fn (Container) remove_capability #
fn (mut self Container) remove_capability(cap crun.Capability) !&Container
fn (Container) add_env #
fn (mut self Container) add_env(key string, value string) !&Container
fn (Container) set_working_dir #
fn (mut self Container) set_working_dir(dir string) !&Container
fn (Container) save_config #
fn (self Container) save_config() !
Save the current configuration to disk
struct ContainerImage #
struct ContainerImage {
pub mut:
image_name string @[required] // Image name (located in ${self.factory.base_dir}/images//rootfs)
docker_url string // Optional Docker registry URL
rootfs_path string // Path to the extracted rootfs
size_mb f64 // Size in MB
created_at string // Creation timestamp
factory &HeroPods @[skip; str: skip] // Reference to parent HeroPods instance
}
ContainerImage represents a container base image with its rootfs
Thread Safety: Image operations are filesystem-based and don't interact with network_config, so no special thread safety considerations are needed.
fn (ContainerImage) export #
fn (mut self ContainerImage) export(args ImageExportArgs) !
Export image to .tgz file
Creates a compressed tarball of the image rootfs
fn (ContainerImage) delete #
fn (mut self ContainerImage) delete() !
Delete image
Removes the image directory and removes from factory cache
fn (ContainerImage) info #
fn (self ContainerImage) info() map[string]string
Get image info as map
Returns image metadata as a string map for display/serialization
struct ContainerImageArgs #
struct ContainerImageArgs {
pub mut:
image_name string @[required] // Unique image name (located in ${self.factory.base_dir}/images//rootfs)
docker_url string // Docker image URL like "alpine:3.20" or "ubuntu:24.04"
reset bool // Reset if image already exists
}
ContainerImageArgs defines parameters for creating/managing container images
struct ContainerNewArgs #
struct ContainerNewArgs {
pub:
name string @[required] // Unique container name
image ContainerImageType = .alpine_3_20 // Base image type
custom_image_name string // Used when image = .custom
docker_url string // Docker image URL for new images
reset bool // Reset if container already exists
}
ContainerNewArgs defines parameters for creating a new container
struct ContainerStartArgs #
struct ContainerStartArgs {
pub:
keep_alive bool // If true, keep container alive after entrypoint exits successfully
}
ContainerStartArgs defines parameters for starting a container
struct CrunConfigArgs #
struct CrunConfigArgs {
pub:
container_name string @[required] // Container name
rootfs_path string @[required] // Path to container rootfs
}
CrunConfigArgs defines parameters for creating crun configuration
struct HeroPods #
struct HeroPods {
pub mut:
tmux_session string // tmux session name
containers map[string]&Container // name -> container mapping
images map[string]&ContainerImage // name -> image mapping
crun_configs map[string]&crun.CrunConfig // name -> crun config mapping
base_dir string // base directory for all container data
reset bool // will reset the heropods
use_podman bool = true // will use podman for image management
name string // name of the heropods
network_config NetworkConfig @[skip; str: skip] // network configuration (automatically initialized, not serialized)
network_mutex sync.Mutex @[skip; str: skip] // protects network_config for thread-safe concurrent access
// Mycelium IPv6 overlay network configuration (flattened fields)
mycelium_enabled bool // Whether Mycelium is enabled
mycelium_version string // Mycelium version to install (e.g., 'v0.5.6')
mycelium_ipv6_range string // Mycelium IPv6 address range (e.g., '400::/7')
mycelium_peers []string // Mycelium peer addresses
mycelium_key_path string // Path to Mycelium private key
mycelium_ip6 string // Host's Mycelium IPv6 address (cached)
mycelium_interface_name string // Mycelium TUN interface name (e.g., "mycelium0")
logger logger.Logger @[skip; str: skip] // logger instance for debugging (not serialized)
}
HeroPods factory for managing containers
Thread Safety: The network_config field is protected by network_mutex for thread-safe concurrent access. We use a separate mutex instead of marking network_config as shared because V's compile-time reflection (used by paramsparser) cannot handle shared fields.
fn (HeroPods) container_new #
fn (mut self HeroPods) container_new(args ContainerNewArgs) !&Container
Create a new container
This method:1. Validates the container name2. Determines the image to use (built-in or custom)3. Creates crun configuration4. Configures DNS in rootfs
Note: The actual container creation in crun happens when start() is called.This method only prepares the configuration and rootfs.
Thread Safety: This method doesn't interact with network_config, so no mutex is needed. Network setup happens later in container.start().
fn (HeroPods) get #
fn (mut self HeroPods) get(args ContainerNewArgs) !&Container
fn (HeroPods) image_get #
fn (mut self HeroPods) image_get(name string) !&ContainerImage
Get image by name
fn (HeroPods) image_import #
fn (mut self HeroPods) image_import(args ImageImportArgs) !&ContainerImage
Import image from .tgz file
Extracts a compressed tarball into the images directory and creates image metadata
fn (HeroPods) image_new #
fn (mut self HeroPods) image_new(args ContainerImageArgs) !&ContainerImage
Create a new image or get existing image
This method:1. Normalizes the image name2. Returns existing image if found (unless reset=true)3. Downloads image from Docker registry if docker_url provided4. Creates image metadata and stores in cache
Thread Safety: Image operations are filesystem-based and don't interact with network_config.
fn (HeroPods) images_list #
fn (mut self HeroPods) images_list() ![]&ContainerImage
List all available images
Scans the images directory and returns all found images with metadata
fn (HeroPods) list #
fn (self HeroPods) list() ![]Container
List all containers currently managed by crun
fn (HeroPods) mycelium_inspect #
fn (mut self HeroPods) mycelium_inspect() !mycelium.MyceliumInspectResult
Inspect Mycelium status and return information
Returns the public key and IPv6 address of the Mycelium node
struct ImageExportArgs #
struct ImageExportArgs {
pub mut:
dest_path string @[required] // Destination .tgz file path
compress_level int = 6 // Compression level 1-9
}
ImageExportArgs defines parameters for exporting an image
struct ImageImportArgs #
struct ImageImportArgs {
pub mut:
source_path string @[required] // Source .tgz file path
reset bool // Overwrite if exists
}
ImageImportArgs defines parameters for importing an image
struct TmuxPaneArgs #
struct TmuxPaneArgs {
pub mut:
window_name string
pane_nr int
pane_name string // optional
cmd string // optional, will execute this cmd
reset bool // if true will reset everything and restart a cmd
env map[string]string // optional, will set these env vars in the pane
}
- README
- Constants
- fn delete
- fn exists
- fn get
- fn heroscript_loads
- fn list
- fn list_available_docker_images
- fn new
- fn play
- fn set
- fn switch
- enum ContainerImageType
- enum ContainerStatus
- struct ArgsGet
- struct ArgsList
- struct Container
- struct ContainerImage
- struct ContainerImageArgs
- struct ContainerNewArgs
- struct ContainerStartArgs
- struct CrunConfigArgs
- struct HeroPods
- struct ImageExportArgs
- struct ImageImportArgs
- struct TmuxPaneArgs