SmolVM provides flexible port forwarding to expose services running inside VMs on your host machine. The expose_local() method automatically sets up localhost-only port forwarding using either nftables or SSH tunnels as a fallback.
from smolvm import SmolVMwith SmolVM() as vm: # Start a web server in the guest vm.run("apk add --no-cache python3") vm.run("python3 -m http.server 8000 > /dev/null 2>&1 &") # Expose guest port 8000 on host localhost:8000 host_port = vm.expose_local(guest_port=8000, host_port=8000) print(f"Server available at http://127.0.0.1:{host_port}") input("Press Enter to stop...")
with SmolVM() as vm: # Forward localhost:3000 -> guest:8080 host_port = vm.expose_local(guest_port=8080, host_port=3000) print(f"Access guest service at localhost:{host_port}")
with SmolVM() as vm: # SmolVM finds an available port automatically host_port = vm.expose_local(guest_port=8080) print(f"Assigned port: {host_port}") # Might be 43251, 38492, or any available port
When host_port is omitted, SmolVM automatically selects an available port on localhost.
SmolVM uses two transport methods, trying them in order:
1
nftables (Preferred)
Direct kernel-level port forwarding using nftables:
Fastest performance
No additional processes
Linux only
Requires appropriate permissions
2
SSH Tunnel (Fallback)
SSH-based port forwarding:
Works everywhere SSH is available
Slightly slower than nftables
Spawns background SSH process
Automatically cleaned up
with SmolVM() as vm: # SmolVM automatically chooses the best available method host_port = vm.expose_local(guest_port=8000) # Check logs to see which transport was used: # "transport=nftables" or "transport=ssh_tunnel"
with SmolVM() as vm: # Create forward host_port = vm.expose_local(guest_port=8000, host_port=8000) # Use the service... # Remove the forward vm.unexpose_local(host_port=8000, guest_port=8000) print("Port forward removed")
with SmolVM() as vm1: # VM1 gets port 8000 port1 = vm1.expose_local(guest_port=80, host_port=8000) print(f"VM1 on port: {port1}") # 8000 with SmolVM() as vm2: # VM2 can't use 8000, gets next available port2 = vm2.expose_local(guest_port=80, host_port=8000) print(f"VM2 on port: {port2}") # Different port (auto-assigned)
If the requested port is unavailable:
SmolVM tries the requested port
If busy, automatically allocates an available port
Returns the actual port used
with SmolVM() as vm: requested_port = 8000 actual_port = vm.expose_local(guest_port=80, host_port=requested_port) if actual_port != requested_port: print(f"Port {requested_port} was busy") print(f"Using {actual_port} instead")
from smolvm import SmolVMfrom smolvm.exceptions import SmolVMErrortry: with SmolVM() as vm: # VM must be running vm.stop() vm.expose_local(guest_port=8000)except SmolVMError as e: print(f"Error: {e}") # Error: Cannot expose port: VM is stopped
try: vm.expose_local(guest_port=99999) # Invalid portexcept ValueError as e: print(f"Invalid port: {e}") # guest_port must be 1-65535try: vm.expose_local(guest_port=80, host_port=0)except ValueError as e: print(f"Invalid port: {e}") # host_port must be 1-65535
import socketdef is_port_available(port: int) -> bool: """Check if a port is available on localhost.""" try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind(("127.0.0.1", port)) return True except OSError: return Falsewith SmolVM() as vm: desired_port = 8000 if is_port_available(desired_port): port = vm.expose_local(guest_port=80, host_port=desired_port) else: # Let SmolVM choose port = vm.expose_local(guest_port=80) print(f"Service on port: {port}")
from smolvm import SmolVMclass ServiceCluster: def __init__(self, num_instances: int): self.vms = [] self.ports = [] def start(self): for i in range(num_instances): vm = SmolVM() vm.start() # Start service in each VM vm.run("apk add --no-cache python3") vm.run(f"python3 -m http.server 8000 &") # Expose on different host ports port = vm.expose_local(guest_port=8000) self.vms.append(vm) self.ports.append(port) print(f"Instance {i+1}: http://localhost:{port}") def stop(self): for vm in self.vms: vm.stop() vm.delete()cluster = ServiceCluster(3)cluster.start()input("Press Enter to stop cluster...")cluster.stop()
with SmolVM() as vm: port = vm.expose_local(guest_port=8000) # Accessible from host: # ✓ http://127.0.0.1:{port} # ✓ http://localhost:{port} # NOT accessible from network: # ✗ http://192.168.1.100:{port} # ✗ http://host-ip:{port}
For external access, use proper network configuration or reverse proxies.