Unit 2.1: Compute and Server Automation
Introduction
You are building TaskBoard, a task-management service that needs a persistent API server and a fleet of stateless workers. Before any application code runs, you need the compute tier provisioned reproducibly, every time, from a Git repository rather than a console. This unit takes you from a single ionoscloud_server resource to an auto-scaling group, all expressed as code.
Compute on IONOS Cloud gives you two server models with different billing and CPU semantics, automated first-boot configuration through cloud-init, and a horizontal auto-scaler that has hard requirements you must encode correctly or your terraform apply will fail. You will write the Terraform for TaskBoard's API server (Dedicated Core, 4 cores, 8 GB RAM) and its worker (vCPU, 2 cores), wire in cloud-init to install the runtime, and attach a public IP so you can SSH in and verify the result.
1. Provisioning Servers with Terraform
The ionoscloud_server resource is the core building block of the compute tier. A server always lives inside a ionoscloud_datacenter (a Virtual Data Center, or VDC) and is created with a CPU family, a core count, and an amount of RAM. The two decisions that drive everything else are the server type, ENTERPRISE for Dedicated Core or CUBE for Cubes, and whether you pick a Dedicated Core configuration or a vCPU configuration.
A minimal Dedicated Core server for TaskBoard's API tier looks like this. The boot volume is declared inline, and the OS image is resolved by a data source rather than hardcoded.
resource "ionoscloud_datacenter" "taskboard" {
name = "taskboard-prod"
location = "de/txl"
}
data "ionoscloud_image" "ubuntu" {
type = "HDD"
cloud_init = "V1"
image_alias = "ubuntu:latest"
location = "de/txl"
}
resource "ionoscloud_server" "api" {
name = "taskboard-api"
datacenter_id = ionoscloud_datacenter.taskboard.id
cores = 4
ram = 8192
cpu_family = "INTEL_ICELAKE"
availability_zone = "AUTO"
volume {
name = "api-boot"
size = 20
disk_type = "SSD Premium"
image_name = data.ionoscloud_image.ubuntu.id
ssh_keys = [var.ssh_public_key]
}
nic {
lan = ionoscloud_lan.public.id
dhcp = true
firewall_active = false
}
}
RAM is specified in megabytes, so 8 GB is 8192. The server creation is asynchronous, but the Terraform provider polls the IONOS request status internally, so by the time apply returns, the server has reached the DONE state and the boot volume is attached. You do not poll manually inside Terraform the way you would with a raw API call.
1.1 Dedicated Core versus vCPU
The choice of server model is not cosmetic. A Dedicated Core server gives your VM exclusive use of its physical cores, and you can pick and later change the CPU family. A vCPU server shares CPU resources and its CPU family is fixed at creation and cannot be changed afterward. TaskBoard's API tier wants predictable latency, so it uses Dedicated Core. The worker tier is bursty and cost-sensitive, so it uses vCPU.
The two models share the same RAM ceiling but differ on the CPU dimension. The limits below come directly from the platform and are the values Terraform validates against.
| Model | CPU class | Max cores/vCPUs | Max RAM | CPU family selectable |
|---|---|---|---|---|
| Dedicated Core | Exclusive | 62 cores | 230 GB | Yes (changeable later) |
| vCPU | Shared | 60 vCPUs | 230 GB | No (fixed at creation) |
RAM can be set in increments of 0.25 GB. The CPU family changing feature on Dedicated Core servers is called Core Technology Choice, which lets you move an existing server to a newer CPU generation without rebuilding it. Available CPU models include AMD EPYC Gen 3 (Milan), AMD EPYC Gen 5 (Turin), several Intel Xeon Gen 5 families (Haswell, Broadwell, Skylake, Ice Lake), and Intel Xeon Gen 6 (Sierra Forest).
The TaskBoard worker is a vCPU server. Note the absence of cpu_family, because it cannot be chosen.
resource "ionoscloud_vcpu_server" "worker" {
name = "taskboard-worker"
datacenter_id = ionoscloud_datacenter.taskboard.id
cores = 2
ram = 4096
# cpu_family omitted: vCPU servers do not allow CPU family selection
volume {
name = "worker-boot"
size = 10
disk_type = "SSD Standard"
image_name = data.ionoscloud_image.ubuntu.id
ssh_keys = [var.ssh_public_key]
}
nic {
lan = ionoscloud_lan.app.id
dhcp = true
}
}
1.2 Availability Zones
Compute servers can be placed in availability Zone 1, Zone 2, or AUTO. There is no Zone 3 for compute, even though Block Storage volumes do offer a Zone 3. If you set availability_zone = "ZONE_3" on a server, the request fails. Use AUTO unless you are deliberately spreading replicas across zones for fault tolerance, in which case pin one server to ZONE_1 and another to ZONE_2.
resource "ionoscloud_server" "api_zone_a" {
# ...
availability_zone = "ZONE_1"
}
resource "ionoscloud_server" "api_zone_b" {
# ...
availability_zone = "ZONE_2"
}
2. Cloud-init for Automated Bootstrapping
A provisioned server with no software is not useful. Cloud-init is the package that runs on first boot and applies your configuration: installing packages, writing files, starting services, and injecting SSH keys. All public Linux images on IONOS Cloud (Alma Linux, Debian, Rocky Linux, and Ubuntu) ship with cloud-init already installed, so you pass configuration through the user_data field and it runs automatically.
You provide user_data as a base64-encoded string in Terraform and the API. The decoded content can be a cloud-config YAML document, a shell script, an include file, or several other supported formats. For TaskBoard, a cloud-config document is the cleanest way to declare the desired end state.
2.1 Cloud-config for TaskBoard
The following cloud-config installs the container runtime, creates an application user, and starts the TaskBoard API service. It lives in a separate file and is base64-encoded by Terraform's base64encode function.
#cloud-config
package_update: true
packages:
- docker.io
- postgresql-client
users:
- name: taskboard
groups: docker
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- ${ssh_public_key}
runcmd:
- systemctl enable --now docker
- docker run -d --restart unless-stopped -p 80:8080 \
--name taskboard-api registry.example/taskboard-api:latest
Wire it into the server resource with user_data. Using templatefile lets you inject variables such as the SSH key into the cloud-config at plan time.
resource "ionoscloud_server" "api" {
name = "taskboard-api"
datacenter_id = ionoscloud_datacenter.taskboard.id
cores = 4
ram = 8192
cpu_family = "INTEL_ICELAKE"
volume {
name = "api-boot"
size = 20
disk_type = "SSD Premium"
image_name = data.ionoscloud_image.ubuntu.id
user_data = base64encode(templatefile("${path.module}/cloud-init.yaml", {
ssh_public_key = var.ssh_public_key
}))
}
nic {
lan = ionoscloud_lan.public.id
dhcp = true
}
}
The supported user-data formats include base64-encoded data (the decoded content must resolve to a supported type), a user-data script beginning with #! or Content-Type: text/x-shellscript, an include file beginning with #include, cloud-config data beginning with #cloud-config in YAML, and upstart jobs. The user_data property is immutable: it is only honored on volume creation, so changing it later requires recreating the volume, not updating it in place.
2.2 SSH Keys and First-Boot Verification
You can inject SSH keys two ways: through the volume's ssh_keys list, or inside the cloud-config ssh_authorized_keys block. The ssh_keys argument is the simplest path for a single key on the default user. After apply, the public IP appears in the NIC's exported attributes, and you can connect immediately.
terraform output api_public_ip
#=> 203.0.113.42
ssh root@203.0.113.42 'cloud-init status --wait'
#=> status: done
Running cloud-init status --wait blocks until first-boot configuration finishes, which is the correct signal that your packages are installed and services are running. Do not assume the server is ready just because SSH accepts the connection.
3. Public IP Allocation
Servers on a public LAN receive an IP from DHCP automatically, but that address is ephemeral and can change. For a stable, reserved public IP, allocate an ionoscloud_ipblock and assign one of its addresses to the NIC. This is required for TaskBoard's API endpoint, which DNS records and load balancers point at.
resource "ionoscloud_ipblock" "api_ip" {
location = ionoscloud_datacenter.taskboard.location
size = 1
name = "taskboard-api-ip"
}
resource "ionoscloud_server" "api" {
# ... cores, ram, volume as above ...
nic {
lan = ionoscloud_lan.public.id
dhcp = true
ips = [ionoscloud_ipblock.api_ip.ips[0]]
}
}
output "api_public_ip" {
value = ionoscloud_ipblock.api_ip.ips[0]
}
The size argument controls how many addresses the block reserves. A reserved IP survives server recreation, which means you can rebuild the API server without changing the address your DNS records resolve to. The location of the IP block must match the data center location, or assignment fails.
4. Images and Snapshots as Code
The ionoscloud_image data source resolves an OS image to an ID at plan time so you never hardcode a volatile image identifier. Filter by type, location, cloud_init, and image_alias to pin the exact image you want.
data "ionoscloud_image" "rocky" {
type = "HDD"
cloud_init = "V1"
image_alias = "rockylinux:latest"
location = "de/txl"
}
For reproducible golden environments, capture a configured boot volume as a snapshot and provision new servers from it. The ionoscloud_snapshot resource creates a snapshot from an existing volume, and that snapshot ID can seed future volumes.
resource "ionoscloud_snapshot" "api_golden" {
datacenter_id = ionoscloud_datacenter.taskboard.id
volume_id = ionoscloud_server.api.boot_volume
name = "taskboard-api-golden-v3"
}
Snapshots are full and live in the same region as the source volume. Using a snapshot as the base for new servers skips the cloud-init install step for everything baked into the image, which speeds up scaling and removes drift between freshly built nodes.
5. VM Auto Scaling
VM Auto Scaling is currently in Early Access (EA); IONOS recommends limiting it to non-production workloads, and its Cloud API is versioned separately at https://api.ionos.com/autoscaling (version v1.ea) rather than the stable /cloudapi/v6 surface used for servers and IP blocks.
VM Auto Scaling creates and removes server replicas automatically based on metrics. It supports horizontal scaling only: it adds more VMs, it does not resize an existing VM. You define a target replica configuration, scale-in and scale-out policies, and the metric that drives them.
Keep the scaling model in mind: VM Auto Scaling is horizontal only. It adds and removes whole server replicas from your ionoscloud_autoscaling_group, backed by Compute Engine servers. Changes to the replica configuration apply to newly created replicas, not to replicas that are already running. The supported replica storage types are HDD, SSD Premium, and SSD Standard. The scaling metric options are instance CPU utilization average (percentage), incoming network bytes, outgoing network bytes, incoming network packets, and outgoing network packets.
5.1 Auto Scaling Group via Terraform
The ionoscloud_autoscaling_group resource defines the group, its replica template, and its policy. The replica template defines the sizing for each server replica the group provisions.
resource "ionoscloud_autoscaling_group" "workers" {
datacenter_id = ionoscloud_datacenter.taskboard.id
name = "taskboard-worker-asg"
max_replica_count = 10
min_replica_count = 1
policy {
metric = "INSTANCE_CPU_UTILIZATION_AVERAGE"
range = "PT24H"
scale_in_threshold = 33
scale_out_threshold = 77
unit = "PERCENTAGE"
scale_in_action {
amount = 1
amount_type = "ABSOLUTE"
cooldown_period = "PT5M"
}
scale_out_action {
amount = 1
amount_type = "ABSOLUTE"
cooldown_period = "PT5M"
}
}
replica_configuration {
cores = 4
ram = 4096
cpu_family = "INTEL_ICELAKE"
availability_zone = "AUTO"
nic {
lan = ionoscloud_lan.app.id
name = "worker-nic"
dhcp = true
}
volume {
image = data.ionoscloud_image.ubuntu.id
name = "worker-vol"
size = 10
type = "SSD Standard"
user_data = base64encode(file("${path.module}/worker-init.yaml"))
}
}
}
The amount_type can be ABSOLUTE (a fixed number of VMs) or PERCENTAGE (a proportion of the current count). The default cooldown period is 5 minutes, the minimum replica count is 1, and up to roughly 100 replicas is the recommended ceiling. A group can be associated with an Application Load Balancer so new replicas are automatically added to the LB target pool.
5.2 Replicas Are Named, Not Addressable by Server ID
A subtle operational gotcha: auto-generated replica server names are names, not server IDs, and they cannot be used to retrieve information over the API. If your monitoring or deployment scripts assume they can GET /servers/{id} using the replica name, the call fails. Treat replicas as cattle. Reference them through the group and the load balancer, not by individual server ID.
6. Cubes for Stateless Workloads
Cubes are a compute server type with mandatory direct-attached NVMe storage defined by a configuration template. The smallest template, Basic Cube XS, provides 1 vCPU, 2 GB RAM, and 60 GB of direct-attached NVMe. The vCPU, RAM, and direct-attached storage size are fixed by the template and cannot be changed after the Cube is provisioned, and the NVMe volume cannot be unmounted or deleted.
A Cube is not storage-only, though. In addition to the mandatory NVMe volume, you can attach up to 23 add-on block storage devices, for 24 total device slots, with the NVMe volume occupying one slot. The fixed-template NVMe plus the cheaper suspend-friendly model makes Cubes a good fit for stateless or ephemeral workloads where you want a predictable bundle.
resource "ionoscloud_server" "cube_runner" {
name = "taskboard-batch"
type = "CUBE"
template_uuid = data.ionoscloud_template.basic_xs.id
datacenter_id = ionoscloud_datacenter.taskboard.id
volume {
name = "cube-das"
disk_type = "DAS"
licence_type = "LINUX"
}
nic {
lan = ionoscloud_lan.app.id
dhcp = true
}
}
One billing nuance to encode in your lifecycle scripts: for Cubes, only deletion stops billing, not suspension. Suspending a Cube keeps the meter running. If you spin up Cubes for batch jobs and expect to stop paying by suspending them, you will be surprised. Run terraform destroy on ephemeral Cubes to actually stop charges.
API Reference Quick Card
Key API endpoints for compute provisioning:
| Method | Endpoint | Description |
|---|---|---|
GET |
/datacenters/{dcId}/servers |
List all servers in a VDC |
POST |
/datacenters/{dcId}/servers |
Create a new server |
GET |
/datacenters/{dcId}/servers/{serverId} |
Get server details |
PATCH |
/datacenters/{dcId}/servers/{serverId} |
Update server properties |
DELETE |
/datacenters/{dcId}/servers/{serverId} |
Delete a server |
POST |
/ipblocks |
Reserve a public IP block |
GET |
/groups |
List auto-scaling groups |
POST |
/groups |
Create an auto-scaling group |
Compute Engine / IP Blocks Base URL: https://api.ionos.com/cloudapi/v6
VM Auto Scaling Base URL (Early Access): https://api.ionos.com/autoscaling/v1.ea
Authentication: Authorization: Bearer <token>
Code Lab
Objective: Deploy TaskBoard's compute tier with Terraform: a Dedicated Core API server bootstrapped by cloud-init, with a reserved public IP, and verify SSH access.
Prerequisites:
- IONOS Cloud account with an API token (
IONOS_TOKENexported) - Terraform 1.5+ installed locally
- An SSH key pair (
~/.ssh/id_ed25519.pub)
Step 1: Configure the provider
terraform {
required_providers {
ionoscloud = {
source = "ionos-cloud/ionoscloud"
version = "~> 6.0"
}
}
}
provider "ionoscloud" {}
Expected output:
$ terraform init
Terraform has been successfully initialized!
Step 2: Write the cloud-init file (cloud-init.yaml)
#cloud-config
package_update: true
packages: [docker.io]
runcmd:
- systemctl enable --now docker
Expected output:
(file saved, no command output)
Step 3: Define the data center, image, and IP block
resource "ionoscloud_datacenter" "taskboard" {
name = "taskboard-lab"; location = "de/txl"
}
data "ionoscloud_image" "ubuntu" {
type = "HDD"; cloud_init = "V1"
image_alias = "ubuntu:latest"; location = "de/txl"
}
resource "ionoscloud_ipblock" "api_ip" {
location = "de/txl"; size = 1; name = "lab-ip"
}
Expected output:
(validated by terraform plan in Step 5)
Step 4: Define the API server
resource "ionoscloud_server" "api" {
name = "taskboard-api"; datacenter_id = ionoscloud_datacenter.taskboard.id
cores = 4; ram = 8192; cpu_family = "INTEL_ICELAKE"
volume {
name = "api-boot"; size = 20; disk_type = "SSD Premium"
image_name = data.ionoscloud_image.ubuntu.id
ssh_keys = [file("~/.ssh/id_ed25519.pub")]
user_data = base64encode(file("cloud-init.yaml"))
}
nic { lan = 1; dhcp = true; ips = [ionoscloud_ipblock.api_ip.ips[0]] }
}
output "api_ip" { value = ionoscloud_ipblock.api_ip.ips[0] }
Step 5: Plan and apply
terraform plan
terraform apply -auto-approve
Expected output:
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
api_ip = "203.0.113.42"
Step 6: Verify cloud-init and SSH
ssh root@$(terraform output -raw api_ip) 'cloud-init status --wait && docker --version'
Expected output:
status: done
Docker version 24.0.x, build ...
Validation Checklist:
- [ ]
terraform applycompletes with 4 resources added - [ ] The output
api_ipshows a reserved public address - [ ]
cloud-init statusreturnsdoneand Docker is installed - [ ] SSH connects using the injected key
Cleanup:
terraform destroy -auto-approve
Common Pitfalls
Developer mistakes to avoid with compute automation on IONOS Cloud:
-
Setting a CPU family on a vCPU server
- Problem: Your
terraform applyfails or the worker server provisions with an unexpected CPU when you setcpu_familyon a vCPU configuration. - Why it happens: A vCPU server's CPU family cannot be chosen at creation and cannot be changed later. Only Dedicated Core servers support family selection (Core Technology Choice).
- Fix: Omit
cpu_familyentirely for vCPU servers and only set it on Dedicated Core servers:
resource "ionoscloud_server" "worker" { cores = 2 ram = 4096 # no cpu_family on vCPU servers } - Problem: Your
-
Expecting VM Auto Scaling to resize a running VM
- Problem: You raise the cores or RAM in the replica configuration expecting existing replicas to grow, but the running VMs stay the same size.
- Why it happens: VM Auto Scaling is horizontal only. It adds and removes whole replicas rather than resizing them, and replica-configuration changes apply only to newly created replicas.
- Fix: Let the group scale out by adding replicas to absorb load. If a workload genuinely needs bigger individual VMs, resize the server outside the auto-scaling group, or let the group replace replicas so the new sizing takes effect.
-
Assuming a Cube stops billing when suspended
- Problem: You suspend Cubes used for batch jobs to save money, but the invoice keeps growing.
- Why it happens: For Cubes, only deletion stops billing, not suspension. The mandatory NVMe volume and template resources stay reserved while suspended.
- Fix: Tear down ephemeral Cubes with
terraform destroyinstead of suspending them, or convert the workload to a standard server you can stop.
Summary
You can now provision IONOS compute entirely as code: Dedicated Core and vCPU servers with the correct CPU and RAM constraints, cloud-init bootstrapping that installs and starts your runtime at first boot, reserved public IPs that survive rebuilds, and a horizontal auto-scaling group for stateless workers. You also know which workloads belong on standard servers versus Cubes, and how the billing and storage models differ between them.
This is TaskBoard's compute foundation. The next unit attaches these servers to a multi-tier network, but the patterns here, data-source-resolved images, base64 cloud-config, reserved IP blocks, and horizontal auto-scaling, recur throughout every infrastructure stack you build on IONOS Cloud.
Key Points:
- Dedicated Core servers allow CPU family selection; vCPU servers have a fixed family set at creation
- VM Auto Scaling is horizontal only: it adds and removes whole server replicas, and replica-configuration changes apply only to new replicas
- Both server models top out at 62 cores / 60 vCPUs and 230 GB RAM, with RAM set in 0.25 GB increments
- Cloud-init
user_datais base64-encoded and immutable; changing it requires recreating the volume - Compute servers exist in Zone 1, Zone 2, or AUTO only, with no Zone 3 for compute
- Cubes carry a fixed mandatory NVMe volume, support up to 23 add-on block storage devices, and stop billing only on deletion
Important Terminology:
- Dedicated Core server: A compute server with exclusive physical cores and a selectable, changeable CPU family.
- vCPU server: A compute server with shared CPU resources whose CPU family is fixed at creation.
- Cloud-init: The first-boot automation package, present on all public Linux images, that applies
user_dataconfiguration like package installs and SSH key injection. - Core Technology Choice: The Dedicated Core feature that lets you move an existing server to a newer CPU generation without rebuilding it.
- IP block: A reserved set of public IP addresses (
ionoscloud_ipblock) that persists across server recreation, used for stable endpoints.
Next Steps
Continue Learning: Unit 2.2: Network and Connectivity as Code
Related Topics: