Virtual Machines
The vms resource type provisions virtual machines from cloud providers. VMs are ideal for non-Kubernetes workloads, legacy applications, or scenarios requiring full OS access.
Basic Usage
spec:
resources:
vms:
- name: dev-server
provider: hetzner
type: cx21
image: ubuntu-22.04 Schema Reference
vms:
- name: string # Required: VM name (supports templating)
provider: string # Required: Cloud provider (hetzner, aws, azure)
type: string # Required: Instance/server type
image: string # Required: OS image
count: integer # Optional: Number of VMs to create (default: 1)
enabled: boolean # Optional: Enable/disable this VM (default: true)
region: string # Optional: Override default region
sshKeys: # Optional: SSH keys to install
- string # Key name or fingerprint
userData: string # Optional: Cloud-init user data
volumes: # Optional: Attached volumes
- name: string # Volume name
size: string # Size (e.g., "50Gi")
mountPath: string # Mount path in VM
network: # Optional: Network configuration
privateIp: string # Static private IP
publicIp: boolean # Assign public IP (default: true)
vpc: string # VPC/network name
tags: # Optional: Provider-specific tags
key: value
dependsOn: # Optional: Resource dependencies
- string Provider-Specific Configuration
Hetzner Cloud
vms:
- name: server
provider: hetzner
type: cx21 # 2 vCPU, 4GB RAM
image: ubuntu-22.04
region: eu-central # Falkenstein, GermanyHetzner Server Types:
| Type | vCPU | RAM | Disk | Use Case |
|---|---|---|---|---|
cx11 | 1 | 2GB | 20GB | Minimal workloads |
cx21 | 2 | 4GB | 40GB | Development |
cx31 | 2 | 8GB | 80GB | Standard apps |
cx41 | 4 | 16GB | 160GB | Heavy workloads |
cx51 | 8 | 32GB | 240GB | Large applications |
ccx11 | 2 | 8GB | 80GB | CPU-optimized |
ccx21 | 4 | 16GB | 160GB | CPU-optimized |
Hetzner Images:
ubuntu-22.04,ubuntu-20.04debian-12,debian-11centos-stream-9,rocky-9fedora-39
AWS EC2
vms:
- name: server
provider: aws
type: t3.medium # 2 vCPU, 4GB RAM
image: ami-ubuntu-22.04
region: us-east-1Common EC2 Instance Types:
| Type | vCPU | RAM | Use Case |
|---|---|---|---|
t3.micro | 2 | 1GB | Minimal |
t3.small | 2 | 2GB | Light workloads |
t3.medium | 2 | 4GB | Development |
t3.large | 2 | 8GB | Standard |
m5.large | 2 | 8GB | General purpose |
c5.large | 2 | 4GB | Compute-optimized |
Azure VMs
vms:
- name: server
provider: azure
type: Standard_B2s # 2 vCPU, 4GB RAM
image: Canonical:0001-com-ubuntu-server-jammy:22_04-lts:latest
region: eastus
network:
resourceGroup: my-rg
vnet: my-vnet
subnet: default
publicIp: true Cloud-Init Configuration
Use userData to configure VMs at boot time with cloud-init:
Basic Setup
vms:
- name: dev-server
provider: hetzner
type: cx21
image: ubuntu-22.04
userData: |
#cloud-config
package_update: true
packages:
- docker.io
- git
- curl
users:
- name: developer
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
ssh_authorized_keys:
- ssh-rsa AAAA...
runcmd:
- systemctl enable docker
- systemctl start docker
- usermod -aG docker developer Participant Workstations
spec:
variables:
- name: participant_count
type: integer
default: 10
resources:
vms:
- name: "workstation-{{ .Index }}"
count: "{{ .Variables.participant_count }}"
provider: hetzner
type: cx21
image: ubuntu-22.04
userData: |
#cloud-config
hostname: workstation-{{ .Index }}
users:
- name: participant
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash
lock_passwd: false
# Password: participant123 (change in production!)
passwd: $6$rounds=4096$...
packages:
- docker.io
- kubectl
- helm
- git
write_files:
- path: /home/participant/.kube/config
permissions: '0600'
owner: participant:participant
content: |
{{ .Resources.clusters.main.kubeconfig | indent 16 }}
runcmd:
- usermod -aG docker participant
- chown -R participant:participant /home/participant Installing Custom Software
vms:
- name: gitlab-runner
provider: hetzner
type: cx31
image: ubuntu-22.04
userData: |
#cloud-config
package_update: true
apt:
sources:
gitlab-runner:
source: "deb https://packages.gitlab.com/runner/gitlab-runner/ubuntu/ jammy main"
keyid: F6403F6544A38863DAA0B6E03F01618A51312F3F
packages:
- gitlab-runner
- docker.io
write_files:
- path: /etc/gitlab-runner/config.toml
content: |
concurrent = 4
[[runners]]
name = "docker-runner"
url = "{{ .Resources.helm.gitlab.url }}"
token = "{{ .Resources.secrets.runner-token.value }}"
executor = "docker"
[runners.docker]
image = "alpine:latest"
privileged = true
runcmd:
- systemctl enable gitlab-runner
- systemctl start gitlab-runner Per-Participant VMs
Create individual VMs for each participant:
spec:
variables:
- name: participant_count
type: integer
default: 15
ui:
label: "Number of Participants"
resources:
vms:
- name: "participant-{{ .Index }}"
count: "{{ .Variables.participant_count }}"
provider: hetzner
type: cx21
image: ubuntu-22.04
userData: |
#cloud-config
hostname: participant-{{ .Index }}
users:
- name: user
sudo: ALL=(ALL) NOPASSWD:ALL
shell: /bin/bash This creates participant-0, participant-1, …, participant-14.
Note
.Index value accessible in templates. Use it for unique hostnames, IPs, or configurations.Attached Volumes
Add persistent storage to VMs:
vms:
- name: database-server
provider: hetzner
type: cx31
image: ubuntu-22.04
volumes:
- name: postgres-data
size: 100Gi
mountPath: /var/lib/postgresql
- name: backups
size: 200Gi
mountPath: /backups
userData: |
#cloud-config
packages:
- postgresql-15
runcmd:
# Wait for volumes to be mounted
- while [ ! -d /var/lib/postgresql ]; do sleep 1; done
- chown postgres:postgres /var/lib/postgresql
- systemctl enable postgresql
- systemctl start postgresql Networking
Private Networks
Connect VMs to private networks:
spec:
resources:
vms:
- name: web-server
provider: hetzner
type: cx21
image: ubuntu-22.04
network:
publicIp: true
privateIp: 10.0.1.10
vpc: training-network
- name: database
provider: hetzner
type: cx31
image: ubuntu-22.04
network:
publicIp: false # No public access
privateIp: 10.0.1.20
vpc: training-network Accessing VMs
VMs with public IPs are accessible via:
# SSH directly
ssh user@<vm-public-ip>
# Through Teabar proxy
teactl vm ssh my-env/web-server VMs without public IPs require jumping through a bastion:
# Use Teabar's built-in proxy
teactl vm ssh my-env/database --jump web-server VM Outputs
Reference VM outputs in other resources:
spec:
resources:
vms:
- name: app-server
provider: hetzner
type: cx21
image: ubuntu-22.04
dns:
- name: app
type: A
target: "{{ .Resources.vms.app-server.public_ip }}"
manifests:
- name: app-config
cluster: main
template: |
apiVersion: v1
kind: ConfigMap
metadata:
name: app-endpoints
data:
app_server_ip: "{{ .Resources.vms.app-server.private_ip }}"
app_server_hostname: "{{ .Resources.vms.app-server.hostname }}" Available VM outputs:
| Output | Description |
|---|---|
.public_ip | Public IP address |
.private_ip | Private IP address |
.hostname | VM hostname |
.id | Provider-specific VM ID |
.status | Current status |
Best Practices
Use Cloud-Init for Configuration
Always use userData instead of manual configuration:
# Good: Declarative, reproducible
userData: |
#cloud-config
packages:
- nginx
runcmd:
- systemctl enable nginx
# Avoid: Manual post-creation steps
# ssh root@server 'apt install nginx' Minimize Public IPs
Only expose VMs that need public access:
vms:
# Public: Load balancer/bastion
- name: bastion
network:
publicIp: true
# Private: Application servers
- name: app-server
network:
publicIp: false
vpc: internal Use Appropriate Sizes
Start small and scale up as needed:
# Development
vms:
- name: dev
type: cx21 # 2 vCPU, 4GB
# Production training
vms:
- name: prod
type: cx41 # 4 vCPU, 16GB Tag Resources
Use tags for organization and cost tracking:
vms:
- name: training-server
provider: hetzner
type: cx31
tags:
environment: "{{ .Environment.Name }}"
project: kubernetes-training
owner: devops-team
ttl: "8h" Comparison with Kubernetes
| Consideration | VMs | Kubernetes Pods |
|---|---|---|
| Startup time | 30-60 seconds | Seconds |
| Isolation | Full OS isolation | Container isolation |
| Resource efficiency | Lower | Higher |
| Scaling | Manual/slower | Fast auto-scaling |
| State management | Persistent by default | Ephemeral by default |
| Use case | Legacy apps, full OS access | Cloud-native apps |
Tip
Related Resources
- Clusters - Kubernetes clusters
- DNS - DNS records for VMs
- Secrets - Inject secrets into VMs
- Templating - Dynamic VM configuration