Skip to content

You are viewing documentation for Instruqt 2.0 Labs. For Tracks documentation, please visit docs.instruqt.com.

Virtual Machine


Defined insandbox.hcl

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.

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.

resource "vm" "ubuntu" {
image {
name = "ubuntu:24.04"
}
}
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 = "20G"
}
volume {
source = "./shared"
destination = "/mnt/shared"
read_only = true
}
port {
local = 8080
host = 8080
protocol = "tcp"
}
port_range {
range = "3000-3010"
enable_host = true
protocol = "tcp"
}
health_check {
timeout = "30s"
tcp {
address = "localhost:8080"
}
}
}

Port numbers (local, host) are integers between 1 and 65535 - quoting them as strings fails validation. Only port_range.range is a string (for example "8000-8010").

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)
│ │ └─ type, source, size, destination, readonly
│ └─ volume[] (repeatable)
│ └─ source, destination, type, read_only
├─ port exposure
│ ├─ port[] (repeatable)
│ │ └─ local, host, protocol
│ └─ 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

FieldTypeRequiredDescription
configblock—Low-level machine settings for the VM, such as which CPU architecture it emulates
imageblock✓The base image the virtual machine boots from. This defines the operating system and preinstalled software available to learners. It must be an Instruqt VM image; a container image will not boot.
resourcesblock—Set how much CPU and memory the virtual machine can use
diskblock—Attach additional disk images to the VM, for example to ship a prepared dataset or a second filesystemrepeatable
volumeblock—Share files from the lab repository into the VM, or persist data written by the VMrepeatable
networkblock—Connect this resource to a specific network so it can communicate with other containers or servicesrepeatable
portblock—Expose specific ports so learners can access web apps, APIs, or services running in this resourcerepeatable
port_rangeblock—Specify a range of ports to open for applications that require multiple consecutive ports, such as clusters or debugging toolsrepeatable
environmentmap(string)—Set environment variables to pass configuration values, API keys, or secrets to the VM
startup_scriptstring—Shell script that runs automatically when the VM boots. Use it to install packages, configure the OS, or set up your lab environment.
dnslist(string)—Configure DNS servers or custom hostnames for your VM to resolve network addresses
ConstraintsMust be a valid IP address
health_checkblock—Define checks to confirm that your VM is running and responsive

Config

vm→Config

VM configuration

FieldTypeRequiredDescription
archstring—CPU architecture of the VM. Use x86_64 for standard Intel/AMD hardware, or aarch64 for ARM-based environments such as AWS Graviton.

Image

vm→Image

VM image configuration

FieldTypeRequiredDescription
namestring✓Full image reference, including the registry and an optional tag. Include a specific tag to ensure consistent lab environments.
ConstraintsMust be a valid image reference (e.g. ubuntu:22.04, europe-west1-docker.pkg.dev/instruqt/instruqt-sandbox/ubuntu:22.04)
usernamestring—Only required for private registries. Leave empty for public images.
passwordstring—Only required for private registries. Leave empty for public images.

Resources

vm→Resources

Resource allocation for the VM

FieldTypeRequiredDescription
cpunumber—Set the number of CPU cores for this VM
memorynumber—Specify how much memory to allocate to this VM

Disk

vm→Disk

Disk images attached to the VM

FieldTypeRequiredDescription
typestring—Disk image format. qcow2 is the most common for QEMU and supports snapshots; raw is a simple byte-for-byte image
Allowedqcow2 raw
sourcestring—URL or local path to the disk image file
sizestring—Set the maximum capacity of this disk. Applied when expanding a base image.
destinationstring—Absolute path where the disk is mounted inside the VM
readonlybool—Prevent the VM from writing to this disk. Use it to keep a shared base image unchanged.

Volume

vm→Volume

Volume mounts for the VM

FieldTypeRequiredDescription
sourcestring✓The path to mount into the VM. Can be relative to the file declaring the VM (`./`) or absolute (`/usr/local/bin`).
destinationstring✓Absolute path inside the VM where the source is mounted
read_onlybool—Prevent the VM from writing to this mount

Network

vm→Network

Network interfaces for the VM

FieldTypeRequiredDescription
idstring—ID of the network to attach the container
targetreferencenetwork—The network to attach the container to
ip_addressstring—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.
ConstraintsMust be a valid IPv4 address (e.g., 192.168.1.10)
aliaseslist(string)—Aliases allow alternate names to specified for the container

Port

vm→Port

Port mappings for the VM

FieldTypeRequiredDescription
localnumber✓The local port in the container
ConstraintsMinimum 1; Maximum 65535
hostnumber—The host port to map the local port to
ConstraintsMinimum 1; Maximum 65535
protocolstring—The protocol to use when exposing the port
Allowedtcp udp
Defaulttcp

Port Range

vm→Port Range

Range of ports to expose

FieldTypeRequiredDescription
rangestring✓The port range to expose, e.g., `8080-8082` would expose the ports `8080`, `8081`, `8082`
ConstraintsMust be a valid port range (e.g., 8080-8082) with ports between 1 and 65535
enable_hostbool—Expose the port range on the host
protocolstring—The protocol to use when exposing the port
Allowedtcp udp
Defaulttcp

Health Check

vm→Health Check

Health checks for the VM

FieldTypeRequiredDescription
timeoutstring✓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.
Default30s
ConstraintsMust be a valid duration (e.g. 30s, 5m)
execblock—Runs a command or script inside the VM. The check passes when the command exits with code 0.repeatable
httpblock—Sends an HTTP request to the address and passes when the response matches the expected success codesrepeatable
tcpblock—Attempts to open a TCP connection to the address. If the connection opens, the check passes.repeatable

Exec Health Check

vm→Health Check→Exec Health Check

Execute command health check

FieldTypeRequiredDescription
commandlist(string)—The command to run, given as the executable followed by its arguments. Use either a command or a script, not both.
scriptstring—An inline shell script to run instead of a single command. Must start with a shebang, e.g. #!/bin/bash.
exit_codenumber—The exit code that counts as healthy. Defaults to 0.

HTTP Health Check

vm→Health Check→HTTP Health Check

HTTP health check

FieldTypeRequiredDescription
addressstring—The URL to check. Leave empty to check this resource on the given port; set it to check a specific endpoint, such as another resource by its sandbox.internal name. The check passes when the endpoint returns one of the success codes.
schemestring—http or https; used when address is empty
Allowedhttp https
Defaulthttp
portnumber—The port the app listens on inside this resource
ConstraintsMinimum 1; Maximum 65535
pathstring—The HTTP path to request when checking this resource via port
Default/
methodstring—HTTP method to use when executing the check
bodystring—HTTP body to send with the request
headersmap(list(string))—HTTP headers to send with the check
success_codeslist(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

vm→Health Check→TCP Health Check

TCP health check

FieldTypeRequiredDescription
addressstring—Host and port to connect to, e.g. db.sandbox.internal:5432. Leave empty to check this resource on the given port. The check passes as soon as the connection is accepted.
portnumber—The port the app listens on inside this resource
ConstraintsMinimum 1; Maximum 65535

Computed Attributes

These attributes are set by the system and are read-only.

AttributeTypeDescription
image.idstringUnique 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[].namestringName will equal the name of the network as created by jumppad
network[].assigned_addressstringAssignedAddress will equal if IPAddress is set, else it will be the value automatically assigned from the network

image.name takes a Docker/OCI image reference, the same as a container image. Examples are ubuntu:24.04 or registry.example.com/team/custom-vm:1.2. For private registries, set image.username and image.password.

A name without a tag uses the image’s latest tag. If the image has no latest tag, the VM uses its highest version tag, such as v1.

Short names resolve to Instruqt’s managed VM images. Instruqt maintains images for the supported releases of common distributions, such as ubuntu:22.04, ubuntu:24.04, debian:11, debian:12, fedora, rocky, and centos:8. Each distribution also has a :latest tag. The images include the Instruqt agent, which runs terminals, exec resources, task checks, and health checks inside the VM.

Custom base images are not self-service yet. To customize a VM, start from an Instruqt-managed image and set it up at boot:

  • Install packages and configuration with startup_script, or with task setup scripts that target the VM.
  • Add files with volume blocks.

For a fully custom base image, contact Instruqt.

VM images are qcow2 disk images, packaged as OCI artifacts and run under QEMU. A bootable image also needs an Instruqt bootstrap layer, which only Instruqt can add. If you push your own qcow2 image, the VM fails to start with the error image "..." has no bootstrap overlay: it must be published through the Instruqt image pipeline to be bootable.

Set the VM’s size with the resources block, not with a machine type:

  • cpu: number of whole CPU cores. (For containers, cpu counts millicores.)
  • memory: memory in MB, for example 2048 for 2 GB.

A VM runs a full operating system, so give it more memory than a container that runs a single process.

The root filesystem has the size built into the image. It never grows, however much CPU or memory you assign. If your lab writes a lot of data to the root filesystem, it runs out of space (ENOSPC, “No space left on device”), even on a large VM.

To add space, add disk blocks. Each disk is formatted as ext4 and mounted only at its destination. It does not enlarge the root filesystem or any other path:

disk {
destination = "/var/lib/app"
size = "20G"
}

size is a string giving the disk’s capacity, e.g. "20G" for a 20 GB disk. (The validator also accepts a bare number like 20 for the same capacity, but the field is a string - use the "20G" form.)

Troubleshooting ENOSPC / “No space left on device”:

  1. Run df -h in the VM to see which filesystem is full - it is usually the root filesystem, not an attached disk.
  2. Find the paths your workload writes to (e.g. /var/lib/rancher for k3s, container image caches, /root/.local for tooling).
  3. Attach a disk block per heavy path, with destination set to that path, so the writes land on the extra disk instead of the root filesystem.

The environment map is delivered to the VM at boot via cloud-init and exported for login shells.

resource "vm" "ubuntu" {
image {
name = "ubuntu:24.04"
}
}
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"
}
resource "network" "main" {
subnet = "10.50.0.0/24"
}
resource "vm" "web" {
image {
name = "ubuntu:24.04"
}
disk {
destination = "/var/lib/app"
size = "20G"
}
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
}
  1. Choose the right compute type: Use VMs for full OS behavior and containers for lightweight application runtimes
  2. Attach a network for interactive labs: Terminal access, service tabs, startup scripts, and exec health checks depend on VM reachability
  3. Size resources deliberately: VMs need enough CPU and memory for the guest operating system and your lab workload
  4. Use explicit image tags: Pin images to stable versions for repeatable lab runs
  5. Make startup scripts idempotent: Startup scripts should handle reruns and partially completed setup safely
  6. Use disks for durable paths: Mount additional disks at application data paths and avoid reserved system paths
  7. Expose only needed ports: Map the smallest set of ports required for the user experience