15 min read

Learning Objectives

By the end of this module, you will be able to:

  • Configure and version-pin the IONOS Terraform provider and authenticate using the `IONOS_TOKEN` environment variable or a provider block
  • Provision a complete base VDC with `ionoscloud_datacenter`, `ionoscloud_lan`, `ionoscloud_server`, `ionoscloud_volume`, and `ionoscloud_nic` resources
  • Configure an S3-compatible remote state backend on IONOS Cloud Object Storage with state locking considerations
  • Use the `ionoscloud_image` and `ionoscloud_location` data sources to select images and regions dynamically
  • Import existing IONOS resources into Terraform state and run an ephemeral environment lifecycle with `plan`, `apply`, and `destroy`

Unit 1.2: Terraform Provider and Core Patterns

Introduction

In Unit 1.1 you provisioned a server three ways: raw API with curl, the Python SDK, and ionosctl. All three are imperative. You issue a POST, poll /requests/{id}/status until DONE, then issue the next call. That works, but it does not give you a reproducible, version-controlled description of your infrastructure. For TaskBoard, the task management API and frontend you are building across this course, you need infrastructure that lives in Git, reviews like code, and tears down cleanly so test environments never silently bill you.

This unit moves you to declarative provisioning with the IONOS Terraform provider. You will write the HCL that builds TaskBoard's base VDC, watch what Terraform does under the hood with the async IONOS API, store state remotely in Object Storage, and adopt the ephemeral-environment pattern that keeps cost under control. The async polling you handled manually in 1.1 is now the provider's job.

1. Provider Setup and Authentication

The IONOS provider for Terraform interacts with IONOS Cloud compute, storage, and networking resources. It currently supports the latest V5 and V6 API offerings, and is published on the Terraform Registry under ionos-cloud/ionoscloud. Before anything works, you must have an IONOS account whose credentials authenticate against the Cloud API, exactly as in Unit 1.1.

1.1 Declaring and Pinning the Provider

Always pin the provider version. Terraform's required_providers block locks you to a known-good release so a terraform init next month does not pull a breaking change into your pipeline.

# versions.tf
terraform {
  required_version = ">= 1.5.0"

  required_providers {
    ionoscloud = {
      source  = "ionos-cloud/ionoscloud"
      version = "~> 6.4"
    }
  }
}

The ~> 6.4 constraint allows patch and minor updates within the 6.x line but blocks a jump to 7.0. Run terraform init to download the pinned provider into .terraform/. Commit the generated .terraform.lock.hcl file so every teammate and CI runner resolves the identical provider build.

1.2 Authentication: Environment Variable vs Provider Block

The cleanest pattern is to authenticate with the bearer token from Token Manager (covered in Unit 1.1) through the IONOS_TOKEN environment variable. Nothing secret then touches your HCL or your Git history.

export IONOS_TOKEN="eyJ0eXAiOiJKV1QiLCJraWQiO..."
terraform plan

With the variable set, the provider block can be empty:

# provider.tf
provider "ionoscloud" {}

You can also pass credentials explicitly in the provider block, which is useful when a single configuration manages resources across more than one account. Username and password (Basic auth) are supported as an alternative to the token.

provider "ionoscloud" {
  token = var.ionos_token   # never hardcode; pass via TF_VAR_ionos_token
}

Prefer the IONOS_TOKEN environment variable over a token committed to a .tfvars file. If you must use a variable, mark it sensitive = true and supply it through TF_VAR_ionos_token in your shell or CI secret store.

2. Core Resources: Building a VDC

A TaskBoard base environment needs a virtual data center, a LAN, a server, a boot volume, and a NIC connecting the server to the LAN. Every IONOS Terraform resource is prefixed with ionoscloud_, which keeps them unambiguous when a configuration mixes providers.

2.1 Datacenter, LAN, Server, Volume, NIC

The resource names below (ionoscloud_datacenter, ionoscloud_lan, ionoscloud_server, ionoscloud_nic) and their attribute schemas follow the IONOS Terraform provider documentation. The location value and image selection are grounded in the data-source sections of this unit.

# main.tf
resource "ionoscloud_datacenter" "taskboard" {
  name        = "taskboard-base"
  location    = "de/fra"
  description = "TaskBoard base VDC, managed by Terraform"
}

resource "ionoscloud_lan" "app" {
  datacenter_id = ionoscloud_datacenter.taskboard.id
  name          = "taskboard-app-lan"
  public        = true
}

resource "ionoscloud_server" "api" {
  name              = "taskboard-api"
  datacenter_id     = ionoscloud_datacenter.taskboard.id
  cores             = 2
  ram               = 4096
  cpu_family        = "INTEL_SKYLAKE"
  availability_zone = "AUTO"

  volume {
    name           = "taskboard-api-boot"
    size           = 20
    disk_type      = "SSD"
    image_name     = data.ionoscloud_image.ubuntu.id
    image_password = var.server_password
  }

  nic {
    lan  = ionoscloud_lan.app.id
    dhcp = true
  }
}

Notice that the inline volume and nic blocks let you express the boot disk and network attachment as part of the server. For a data volume you attach after boot, or to manage a disk's lifecycle independently, declare a standalone ionoscloud_volume instead.

2.2 Standalone Volume and NIC

resource "ionoscloud_volume" "data" {
  datacenter_id = ionoscloud_datacenter.taskboard.id
  server_id     = ionoscloud_server.api.id
  name          = "taskboard-data"
  size          = 50
  disk_type     = "SSD"
  licence_type  = "OTHER"
}

resource "ionoscloud_nic" "extra" {
  datacenter_id = ionoscloud_datacenter.taskboard.id
  server_id     = ionoscloud_server.api.id
  lan           = ionoscloud_lan.app.id
  dhcp          = true
}

The references like ionoscloud_datacenter.taskboard.id and ionoscloud_server.api.id are what give Terraform its ordering. Because the volume's server_id reads from the server resource, Terraform knows the server must exist first. You never write explicit "wait for the datacenter" logic. That dependency graph is the whole point of moving off the imperative scripts from Unit 1.1.

3. The plan/apply/destroy Lifecycle

The three commands you will run constantly are terraform plan, terraform apply, and terraform destroy. Understanding what each does against the async IONOS API saves hours of confusion.

3.1 What Happens Under the Hood

terraform plan reads your HCL, reads current state, queries the IONOS API for the live state of each managed resource, and prints the diff. It changes nothing. terraform apply executes that diff by issuing the same POST, PUT, and DELETE calls you made by hand in 1.1.

Here is the key point: IONOS API operations are asynchronous. A POST returns a request ID and the resource is not ready yet. In Unit 1.1 you polled /requests/{id}/status until DONE before the next call. The IONOS Terraform provider does that polling internally for every resource. When apply shows a resource as still creating, it is waiting on exactly that request status before proceeding to dependent resources.

terraform plan -out=taskboard.tfplan
terraform apply taskboard.tfplan

Capturing the plan to a file with -out and applying that exact file guarantees apply does precisely what you reviewed, with no drift between plan and apply. This is the safe pattern for CI/CD.

3.2 Destroy and Targeted Operations

# Preview what will be torn down
terraform plan -destroy

# Tear it all down
terraform destroy

# Operate on a single resource (use sparingly)
terraform apply -target=ionoscloud_server.api

terraform destroy walks the dependency graph in reverse: NICs and volumes before the server, the server before the LAN, the LAN before the datacenter. This reverse ordering is why you should let Terraform manage the full stack rather than deleting resources manually in the DCD, which can leave Terraform state out of sync with reality.

4. Remote State on Object Storage

By default Terraform writes state to a local terraform.tfstate file. That does not work for a team or a pipeline. State must live somewhere shared, and IONOS Cloud Object Storage is S3-compatible, so it serves as a Terraform backend.

4.1 Configuring the S3 Backend

IONOS Object Storage authenticates with an Access Key and a Secret Key, not bearer tokens. These are the same S3 credentials you generate in Object Storage Key management. Object Storage is available in several regions, each with its own endpoint, including Frankfurt (de) at s3.eu-central-1.ionoscloud.com, Berlin (eu-central-2) at s3.eu-central-2.ionoscloud.com, and Logroño (eu-south-2) at s3.eu-south-2.ionoscloud.com.

# backend.tf
terraform {
  backend "s3" {
    bucket   = "taskboard-tfstate"
    key      = "base/terraform.tfstate"
    region   = "eu-central-1"
    endpoints = {
      s3 = "https://s3.eu-central-1.ionoscloud.com"
    }
    skip_credentials_validation = true
    skip_region_validation      = true
    skip_requesting_account_id  = true
    skip_s3_checksum            = true
  }
}

The skip_* flags are required because this is an external (non-AWS) S3 provider. Without them, the backend tries to call AWS-specific endpoints like the Security Token Service and fails. Supply the credentials through the standard S3 environment variables so they never enter your HCL:

export AWS_ACCESS_KEY_ID="your-object-storage-access-key"
export AWS_SECRET_ACCESS_KEY="your-object-storage-secret-key"
terraform init   # migrates local state into the bucket

4.2 State Locking Considerations

State locking prevents two apply runs from corrupting state simultaneously. AWS S3 backends traditionally rely on a DynamoDB table for locks, which IONOS Object Storage does not provide. Treat the IONOS S3 backend as unlocked: serialize applies through a single CI/CD pipeline, never run concurrent applies against the same state key, and use a separate state key per environment (for example base/, staging/, prod/) so unrelated stacks never contend.

5. Data Sources, Imports, and Cost Control

Hardcoding image IDs and region strings makes configurations brittle. Data sources query the IONOS API at plan time so your HCL stays portable. Imports bring resources created outside Terraform under management. And disciplined destroy keeps your bill honest.

5.1 Image and Location Data Sources

data "ionoscloud_location" "frankfurt" {
  name    = "fra"
  feature = "SSD"
}

data "ionoscloud_image" "ubuntu" {
  type     = "HDD"
  location = "de/fra"
  cloud_init = "V1"
  image_alias = "ubuntu:latest"
}

The ionoscloud_image data source resolves a current image so you are not pinning a stale image UUID that may be retired. The ionoscloud_location data source confirms a region and its feature availability before you provision into it. Reference the result with data.ionoscloud_image.ubuntu.id in the server's volume block, as shown in Section 2.

The following table summarizes the four ways to drive IONOS provisioning that you have now seen across Units 1.1 and 1.2.

Approach Best For Interface State Tracked
API Direct Full control, one-off calls curl / HTTP None (manual)
SDK Application code Python / Go / JS None (manual)
Terraform Reproducible infrastructure HCL Yes (state file)
ionosctl Quick tasks, scripting CLI None (manual)

Use Terraform when the infrastructure must be reproducible and reviewed. Reach for the SDK or ionosctl for runtime operations and quick checks that do not belong in your declarative stack.

5.2 Importing Existing Resources

If a datacenter or server already exists, perhaps created in the DCD or by an earlier curl script, import it rather than recreating it. Write a matching resource block first, then import. IONOS imports use a compound ID joining the datacenter ID and the resource ID.

# Import an existing server: format is datacenter_id/server_id
terraform import ionoscloud_server.api \
  3fa85f64-5717-4562-b3fc-2c963f66afa6/9bc92f81-1234-4cde-8901-abcdef012345

# Import a datacenter (single ID)
terraform import ionoscloud_datacenter.taskboard 3fa85f64-5717-4562-b3fc-2c963f66afa6

After import, run terraform plan. If the plan shows changes, your HCL does not yet match reality. Reconcile the resource block's attributes to the live configuration until plan reports no changes. Only then is the resource safely under management.

5.3 Cost Awareness and Ephemeral Environments

Every resource Terraform provisions on apply incurs charges from the moment it exists. The disciplined workflow is: always plan before apply, always destroy what you spin up for testing. The ephemeral-environment pattern creates a full stack for a test run, then tears it down.

# Spin up a throwaway environment for a feature branch
terraform workspace new pr-142
terraform apply -auto-approve

# ... run integration tests against the live infrastructure ...

# Tear it all down so it stops billing
terraform destroy -auto-approve
terraform workspace delete pr-142

Wiring terraform destroy into the teardown step of a CI job guarantees a forgotten branch environment cannot quietly accumulate cost for weeks.

API Reference Quick Card

The IONOS Terraform provider calls these Cloud API endpoints under the hood. Knowing them helps you debug a stuck apply.

Method Endpoint Description
POST /datacenters Create a VDC (backs ionoscloud_datacenter)
POST /datacenters/{id}/servers Create a server (backs ionoscloud_server)
POST /datacenters/{id}/volumes Create a volume (backs ionoscloud_volume)
POST /datacenters/{id}/lans Create a LAN (backs ionoscloud_lan)
GET /requests/{id}/status Async request status the provider polls

Base URL: https://api.ionos.com/cloudapi/v6 Authentication: Authorization: Bearer <token> (provider reads IONOS_TOKEN)

Code Lab

Objective: Build TaskBoard's base VDC with Terraform: a datacenter, a LAN, and a server with an attached volume. Plan, apply, verify, and destroy.

Prerequisites:

  • IONOS Cloud account with an API token exported as IONOS_TOKEN
  • Terraform 1.5 or later installed locally
  • ionosctl configured (from Unit 1.1) for verification

Step 1: Scaffold the configuration

mkdir taskboard-base && cd taskboard-base
touch versions.tf provider.tf main.tf

Expected output:

(no output; three empty files created)

Step 2: Add the provider and pin the version

Put the versions.tf content from Section 1.1 and provider "ionoscloud" {} from Section 1.2 into your files, then initialize.

terraform init

Expected output:

Initializing provider plugins...
- Installing ionos-cloud/ionoscloud v6.4.x...
Terraform has been successfully initialized!

Step 3: Write the VDC resources

Add the ionoscloud_datacenter, ionoscloud_lan, and ionoscloud_server blocks from Section 2.1 plus the ionoscloud_image data source from Section 5.1 to main.tf.

Step 4: Plan and review

export TF_VAR_server_password="ChangeMe-Strong-Pw-1"
terraform plan -out=taskboard.tfplan

Expected output:

Plan: 3 to add, 0 to change, 0 to destroy.

Step 5: Apply

terraform apply taskboard.tfplan

Expected output:

ionoscloud_datacenter.taskboard: Creation complete after 25s [id=3fa85f64-...]
ionoscloud_server.api: Still creating... [1m20s elapsed]
ionoscloud_server.api: Creation complete after 2m10s [id=9bc92f81-...]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Step 6: Verify with ionosctl

terraform output -raw datacenter_id 2>/dev/null || true
ionosctl server list --datacenter-id $(terraform state show ionoscloud_datacenter.taskboard | grep -m1 'id ' | awk '{print $3}' | tr -d '"')

Expected output:

ServerId   Name            State       Cores   Ram
9bc92f81   taskboard-api   AVAILABLE   2       4096

Step 7: Inspect state

terraform state list

Expected output:

data.ionoscloud_image.ubuntu
ionoscloud_datacenter.taskboard
ionoscloud_lan.app
ionoscloud_server.api

Validation Checklist:

  • [ ] terraform apply completed with 3 resources added
  • [ ] ionosctl server list shows the server as AVAILABLE
  • [ ] terraform state list shows all managed resources

Cleanup:

terraform destroy -auto-approve

Common Pitfalls

  1. Deleting resources in the DCD that Terraform manages

    • Problem: You delete the server manually in the web UI, then terraform apply errors or tries to recreate unrelated resources.
    • Why it happens: Terraform state still records the deleted server. The live API no longer has it, so state and reality diverge.
    • Fix: Remove the stale entry from state, then let Terraform reconcile:
    terraform state rm ionoscloud_server.api
    terraform plan   # now matches reality
    
  2. Forgetting the S3 backend skip flags

    • Problem: terraform init against the Object Storage backend hangs or fails with an STS or account-ID error.
    • Why it happens: The S3 backend assumes AWS and tries AWS-only calls that IONOS Object Storage does not implement.
    • Fix: Add the skip flags and the explicit endpoint to the backend block:
    skip_credentials_validation = true
    skip_region_validation      = true
    skip_requesting_account_id  = true
    endpoints = { s3 = "https://s3.eu-central-1.ionoscloud.com" }
    
  3. Pinning a stale image UUID instead of using a data source

    • Problem: terraform apply fails with an image-not-found error months after the configuration last worked.
    • Why it happens: A hardcoded image UUID was retired upstream. The async server creation aborts because its boot volume references a missing image.
    • Fix: Resolve the image at plan time with the data source and reference it:
    data "ionoscloud_image" "ubuntu" {
      type        = "HDD"
      location    = "de/fra"
      image_alias = "ubuntu:latest"
    }
    # volume { image_name = data.ionoscloud_image.ubuntu.id }
    

Summary

You can now describe IONOS infrastructure declaratively and manage its full lifecycle from code. You configured and version-pinned the IONOS Terraform provider, authenticated with IONOS_TOKEN, and built TaskBoard's base VDC from ionoscloud_datacenter, ionoscloud_lan, ionoscloud_server, ionoscloud_volume, and ionoscloud_nic resources. You moved state into an S3-compatible Object Storage backend, used data sources to resolve images and locations dynamically, imported existing resources, and adopted the ephemeral-environment pattern so test infrastructure tears down cleanly.

The async provisioning model you handled manually in Unit 1.1 is now the provider's internal concern: Terraform polls request status for you and orders operations through its dependency graph. From here, every infrastructure unit in this course builds on this same Terraform foundation.

Key Points:

  • The IONOS provider supports V5 and V6 API offerings; always pin the version with required_providers and commit the lock file
  • Authenticate with IONOS_TOKEN so no secret enters your HCL; all resources are prefixed ionoscloud_
  • Terraform polls the async /requests/{id}/status endpoint internally and orders operations via the resource dependency graph
  • The Object Storage S3 backend needs the skip_* flags and an explicit IONOS endpoint, and authenticates with Access Key plus Secret Key, not a bearer token
  • Always plan before apply and destroy ephemeral environments to control cost

Important Terminology:

  • Provider: The plugin that translates HCL resources into IONOS Cloud API calls, published as ionos-cloud/ionoscloud
  • State: Terraform's record of which real IONOS resources it manages, stored locally or in an Object Storage backend
  • Data source: A read-only query against the IONOS API at plan time, such as ionoscloud_image, used to avoid hardcoding IDs
  • Import: Bringing an existing IONOS resource under Terraform management using a compound datacenter_id/resource_id
  • Ephemeral environment: A full stack created for a test run and torn down with terraform destroy to avoid lingering charges

Next Steps

Continue Learning: Unit 1.3: Knowledge Check - Programmatic Foundations

Related Topics: