Virtual Machine
The vm resource defines a virtual machine that runs as part of your lab sandbox environment. VMs provide full operating system compute for labs that need system services, OS-level configuration, extra disks, or workloads that do not fit a container-only design.
Use Cases
Section titled “Use Cases”As a lab author, you can use VMs to create richer sandbox environments:
- Full Operating Systems: Run workloads that need a complete Linux environment instead of a single container process
- System Configuration Labs: Teach package installation, service management, networking, or disk setup
- Nested Runtime Labs: Provide VM images that include Docker, Kubernetes, or other platform tooling
- Stateful Services: Attach additional disks for databases, data directories, or other storage-heavy exercises
Use a VM when the lab experience depends on OS behavior. Use a container when a lighter process-isolated runtime is enough.
HCL Syntax
Section titled “HCL Syntax”Basic Syntax
Section titled “Basic Syntax”resource "vm" "ubuntu" { image { name = "ubuntu:24.04" }}Full Syntax
Section titled “Full Syntax”resource "network" "main" { subnet = "10.50.0.0/24"}
resource "vm" "ubuntu" { config { arch = "x86_64" }
image { name = "ubuntu:24.04" username = "registry-user" password = variable.registry_token }
resources { cpu = 2 memory = 2048 }
environment = { GREETING = "hello" }
dns = ["1.1.1.1"] startup_script = <<-EOF #!/bin/sh apt-get update apt-get install -y nginx EOF
network { id = resource.network.main.meta.id ip_address = "10.50.0.10" aliases = ["web"] }
disk { destination = "/data" size = 20 }
volume { source = "./shared" destination = "/mnt/shared" read_only = true }
port { local = "8080" host = "8080" protocol = "tcp" open_in_browser = "/" }
port_range { range = "3000-3010" enable_host = true protocol = "tcp" }
health_check { timeout = "30s"
tcp { address = "localhost:8080" } }}Resource Structure
Section titled “Resource Structure”vm├─ config│ └─ arch├─ image (required)│ └─ name, username, password├─ resources│ └─ cpu, memory├─ environment├─ dns[]├─ startup_script├─ networking│ └─ network[] (repeatable)│ └─ id, ip_address, aliases├─ storage│ ├─ disk[] (repeatable)│ │ └─ destination, size│ └─ volume[] (repeatable)│ └─ source, destination, type, read_only├─ port exposure│ ├─ port[] (repeatable)│ │ └─ local, host, protocol, open_in_browser│ └─ port_range[] (repeatable)│ └─ range, enable_host, protocol└─ monitoring └─ health_check ├─ timeout ├─ http[] (address, method, body, headers, success_codes) ├─ tcp[] (address) └─ exec[] (command, script, exit_code)Fields
| Field | Type | Required | Description |
|---|---|---|---|
config | block | — | VM configuration |
image | block | ✓ | VM image configuration |
resources | block | — | Resource allocation for the VM |
disk | block | — | Disk images attached to the VMrepeatable |
volume | block | — | Volume mounts for the VMrepeatable |
network | block | — | Connect your container to a specific network to enable communication between containers or services.repeatable |
port | block | — | Expose specific ports so learners can access web apps, APIs, or services running in the container.repeatable |
port_ | block | — | Range of ports to exposerepeatable |
environment | map(string) | — | Environment variables for the VM |
startup_ | string | — | Script to run on VM boot |
dns | list(string) | — | Configure DNS servers or custom hostnames for your VM to resolve network addresses. ConstraintsMust be a valid IP address |
health_ | block | — | Define checks to confirm that your VM is running and responsive. |
Config
VM configuration
| Field | Type | Required | Description |
|---|---|---|---|
arch | string | — | CPU architecture (e.g. x86_64, aarch64) |
Image
VM image configuration
| Field | Type | Required | Description |
|---|---|---|---|
name | string | ✓ | Docker image name with optional tag ConstraintsMust match `^[a-z0-9]([a-z0-9-]*[a-z0-9])*(\.[a-z0-9]([a-z0-9-]*[a-z0-9])*)*(/[a-z0-9]+([a-z0-9._-]*[a-z0-9])*)*(:[a-z0-9]+([a-z0-9._-]*[a-z0-9])*)?$` |
username | string | — | Docker registry user to use for private repositories |
password | string | — | Docker registry password to use for private repositories |
Resources
Resource allocation for the VM
| Field | Type | Required | Description |
|---|---|---|---|
cpu | number | — | Number of CPU cores |
memory | number | — | Memory in MB |
Disk
Disk images attached to the VM
| Field | Type | Required | Description |
|---|---|---|---|
type | string | — | Disk type (e.g. qcow2, raw) |
source | string | — | Source image URL or path |
size | string | — | Disk size (e.g. 20G) |
destination | string | — | Mount path inside the VM |
readonly | bool | — | Mount as read-only |
Volume
Volume mounts for the VM
| Field | Type | Required | Description |
|---|---|---|---|
source | string | ✓ | Source path on the host |
destination | string | ✓ | Mount path inside the VM |
read_ | bool | — | Mount as read-only |
Network
Network interfaces for the VM
| Field | Type | Required | Description |
|---|---|---|---|
id | string | ✓ | ID of the network to attach the container |
ip_ | string | — | Static IP address to assign container for the network, the ip address must be within range defined by the network subnet. If this parameter is omitted an IP address will be automatically assigned. |
aliases | list(string) | — | Aliases allow alternate names to specified for the container |
Port
Port mappings for the VM
| Field | Type | Required | Description |
|---|---|---|---|
local | number | ✓ | The local port in the container ConstraintsMinimum 1; Maximum 65535 |
remote | number | — | Remote port of the service ConstraintsMinimum 1; Maximum 65535 |
host | number | — | The host port to map the local port to ConstraintsMinimum 1; Maximum 65535 |
protocol | string | — | The protocol to use when exposing the port Allowed tcp udpDefault tcp |
open_ | string | — | When a host port is defined open this port with the given path in a browser |
Port Range
Range of ports to expose
| Field | Type | Required | Description |
|---|---|---|---|
range | string | ✓ | Local port in the container ConstraintsRange format is "<number>-<number>" |
enable_ | bool | — | Host port |
protocol | string | — | Protocol tcp, udp Allowed tcp udpDefault tcp |
Health Check
Health checks for the VM
| Field | Type | Required | Description |
|---|---|---|---|
timeout | string | ✓ | The maximum duration to wait before marking the health check as failed. Expressed as a Go duration, e.g. 1s = 1 second, 100ms = 100 milliseconds. Default 30sConstraintsMust be a valid duration (e.g. 30s, 5m) |
exec | block | — | Runs a command or script inside the VM. The check passes when the command exits with code 0.repeatable |
http | block | — | Sends an HTTP request to the address and passes when the response matches the expected success codes.repeatable |
tcp | block | — | Attempts to open a TCP connection to the address. If the connection opens, the check passes.repeatable |
Exec Health Check
Execute command health check
| Field | Type | Required | Description |
|---|---|---|---|
command | list(string) | — | Command to execute |
script | string | — | Script to execute |
exit_ | number | — | Expected exit code |
HTTP Health Check
HTTP health check
| Field | Type | Required | Description |
|---|---|---|---|
address | string | ✓ | The URL to check, health check expects a HTTP status code to be returned by the URL in order to pass the health check. |
method | string | — | HTTP method to use when executing the check. |
body | string | — | HTTP body to send with the request. |
headers | map(list(string)) | — | HTTP headers to send with the check |
success_ | list(number) | — | HTTP status codes returned from the endpoint when called. If the returned status code matches any in the array then the health check will pass. |
TCP Health Check
TCP health check
| Field | Type | Required | Description |
|---|---|---|---|
address | string | ✓ | TCP address to check |
Computed Attributes
These attributes are set by the system and are read-only.
| Attribute | Type | Description |
|---|---|---|
image.id | string | Unique identifier for the image, this is independent of tag and changes each time the image is built. An image that has been tagged multiple times also shares the same ID. |
network[].name | string | Name will equal the name of the network as created by jumppad |
network[].assigned_address | string | AssignedAddress will equal if IPAddress is set, else it will be the value automatically assigned from the network |
Examples
Section titled “Examples”Basic VM
Section titled “Basic VM”resource "vm" "ubuntu" { image { name = "ubuntu:24.04" }}VM with Network and Terminal Access
Section titled “VM with Network and Terminal Access”resource "network" "main" { subnet = "10.50.0.0/24"}
resource "vm" "devbox" { image { name = "ubuntu:24.04" }
resources { cpu = 2 memory = 2048 }
network { id = resource.network.main.meta.id ip_address = "10.50.0.10" }
startup_script = <<-EOF #!/bin/sh apt-get update apt-get install -y curl jq EOF}
resource "terminal" "devbox" { target = resource.vm.devbox shell = "/bin/bash"}VM with Disk and Service Tab
Section titled “VM with Disk and Service Tab”resource "network" "main" { subnet = "10.50.0.0/24"}
resource "vm" "web" { image { name = "ubuntu:24.04" }
disk { destination = "/var/lib/app" size = 20 }
port { local = "8080" host = "8080" }
network { id = resource.network.main.meta.id }
health_check { http { address = "http://localhost:8080/health" } }}
resource "service" "web" { target = resource.vm.web port = 8080}Best Practices
Section titled “Best Practices”- Choose the right compute type: Use VMs for full OS behavior and containers for lightweight application runtimes
- Attach a network for interactive labs: Terminal access, service tabs, startup scripts, and exec health checks depend on VM reachability
- Size resources deliberately: VMs need enough CPU and memory for the guest operating system and your lab workload
- Use explicit image tags: Pin images to stable versions for repeatable lab runs
- Make startup scripts idempotent: Startup scripts should handle reruns and partially completed setup safely
- Use disks for durable paths: Mount additional disks at application data paths and avoid reserved system paths
- Expose only needed ports: Map the smallest set of ports required for the learner experience
