Infrastructure as Code on T Cloud Public with Terraform / OpenTofu

I have been meaning to write an article on using Terraform on T Cloud Public for some time now.
If you've been working with cloud infrastructure for a while, you've probably run into the same frustration I have: clicking through web consoles gets old fast. You provision a VPC here, spin up a server there, forget what security group rules you set last week — and suddenly your infrastructure is a mystery box that only works by accident. That's where Terraform comes in, and pairing it with T Cloud Public makes for a genuinely compelling setup, especially if you care about data sovereignty and European cloud regulations.
In this article I'll walk through why OTC + Terraform is worth your time, how to get started, and show a few practical examples you can actually use.
What Is T Cloud Public?
T Cloud Public is Deutsche Telekom's public cloud offering — a sovereign European alternative to the hyperscalers (AWS, Azure, GCP). It's built on OpenStack and operated from data centres in Germany and the Netherlands, which makes it attractive for anyone dealing with GDPR, BSI requirements, or simply wanting their data to stay in Europe.
The platform covers all the usual IaaS bases: compute (Elastic Cloud Server), networking (VPC, load balancers, DNS), storage (Object Storage Service, block volumes), managed databases (RDS), container orchestration (CCE — Cloud Container Engine), and more. It's been steadily growing its service catalogue and, critically for us today, it has a well-maintained official Terraform provider.
Why Open Tofu

Open Tofu is a Open Source fork of Terraform. Terraform is HashiCorp's Infrastructure as Code (IaC) tool. You describe the infrastructure you want in HCL (HashiCorp Configuration Language), run a plan to see what will change, and then apply it. The key ideas:
- Declarative: you describe the desired state, Terraform figures out how to get there.
- Idempotent: running
terraform applytwice produces the same result. - State tracking: Terraform keeps a state file so it knows what it already provisioned.
- Provider ecosystem: hundreds of providers let you manage cloud resources, DNS, databases, certificates, and more from one tool.
For T Cloud Public specifically, T-Systems actively maintains an official provider
(opentelekomcloud/opentelekomcloud) that covers the full service catalogue and is updated on
a weekly cadence.
Setting Up the Provider
First, make sure you have Terraform installed (v1.3+ recommended).
Create a providers.tf file in your project directory:
terraform {
required_providers {
opentelekomcloud = {
source = "opentelekomcloud/opentelekomcloud"
version = ">= 1.23.2"
}
}
}
provider "opentelekomcloud" {
auth_url = "https://iam.eu-de.otc.t-systems.com/v3"
domain_name = var.domain_name
tenant_name = var.tenant_name
# credentials via environment variables (see below)
}
Authentication
Rather than hardcoding credentials, use environment variables — your future self will thank you.
Your providers.tf then will look like:
provider "opentelekomcloud" {}
You can then define your environment as:
export OS_USERNAME="your-iam-username"
export OS_PASSWORD="your-iam-password"
export OS_DOMAIN_NAME="your-domain-name"
export OS_PROJECT_NAME="eu-de" # or your project/tenant name
Or use permanent AK/SK credentials:
export OS_ACCESS_KEY="your-iam-username"
export OS_SECRET_KEY="your-iam-password"
Last time I introduced tcurl. With that project installed you could:
eval $(tcurl-login)
This will let you do an interactive login with optional virtual MFA (OTP code) support.
You probably should create a dedicated IAM user in the T Cloud Public console with the minimum permissions needed. This is good practice for CI/CD pipelines too.
Then initialise the provider:
terraform init
Terraform will download the provider plugin and you're ready to go.
Your First Resources: VPC and Subnet
Everything in T Cloud Public lives inside a VPC. Let's create a basic network setup:
# main.tf
resource "opentelekomcloud_vpc_v1" "main" {
name = "my-vpc"
cidr = "10.0.0.0/16"
}
resource "opentelekomcloud_vpc_subnet_v1" "web" {
name = "web-subnet"
vpc_id = opentelekomcloud_vpc_v1.main.id
cidr = "10.0.1.0/24"
gateway_ip = "10.0.1.1"
# OTC requires a DNS server
primary_dns = "100.125.4.25"
secondary_dns = "100.125.129.199"
}
Run terraform plan to preview, then terraform apply to create them.
Spinning Up a Compute Instance (ECS)
With a network in place, let's add an Elastic Cloud Server:
resource "opentelekomcloud_compute_secgroup_v2" "web" {
name = "web-sg"
description = "Allow HTTP and SSH"
rule {
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr = "0.0.0.0/0" # tighten this in production!
}
rule {
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
}
resource "opentelekomcloud_compute_instance_v2" "web" {
name = "web-server-01"
image_name = "Standard_Debian_12_latest"
flavor_name = "s3.medium.1"
key_pair = "my-keypair" # must exist in OTC already
security_groups = [opentelekomcloud_compute_secgroup_v2.web.name]
network {
uuid = opentelekomcloud_vpc_subnet_v1.web.id
}
metadata = {
managed_by = "terraform"
}
}
# Attach a floating IP for public access
resource "opentelekomcloud_networking_floatingip_v2" "web" {
pool = "admin_external_net"
}
resource "opentelekomcloud_compute_floatingip_associate_v2" "web" {
floating_ip = opentelekomcloud_networking_floatingip_v2.web.address
instance_id = opentelekomcloud_compute_instance_v2.web.id
}
output "public_ip" {
value = opentelekomcloud_networking_floatingip_v2.web.address
}
After terraform apply, Terraform outputs the public IP. SSH in with your key pair and you're live.
Storing Terraform State Remotely (OBS)
The state file (terraform.tfstate) tracks everything Terraform manages. Committing it to
Git is a bad idea (it contains secrets). Instead, store it in T Cloud Public's
Object Storage Service (OBS):
# backend.tf
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-de"
endpoint = "https://obs.eu-de.otc.t-systems.com"
# OBS uses S3-compatible API
skip_region_validation = true
skip_credentials_validation = true
skip_metadata_api_check = true
}
}
You'll need to create the OBS bucket first (you can do this via the web console or with
Terraform itself in a separate bootstrap step), and set
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY to your permanent access key pair.
This gives you locking, versioning, and team-safe state management.
Note, that the AK/SK key pair may be different from the credentials used to manage the cloud.
A More Complete Example: Load-Balanced Web Tier
Here's a sketch of something closer to a production setup — a load balancer fronting two web servers:
# Two web servers (using count for brevity)
resource "opentelekomcloud_compute_instance_v2" "web" {
count = 2
name = "web-${count.index + 1}"
image_name = "Standard_Debian_12_latest"
flavor_name = "s3.medium.1"
key_pair = "my-keypair"
security_groups = [opentelekomcloud_compute_secgroup_v2.web.name]
network {
uuid = opentelekomcloud_vpc_subnet_v1.web.id
}
}
# Elastic Load Balancer
resource "opentelekomcloud_lb_loadbalancer_v2" "web" {
name = "web-lb"
vip_subnet_id = opentelekomcloud_vpc_subnet_v1.web.id
}
resource "opentelekomcloud_lb_listener_v2" "http" {
name = "http-listener"
protocol = "HTTP"
protocol_port = 80
loadbalancer_id = opentelekomcloud_lb_loadbalancer_v2.web.id
}
resource "opentelekomcloud_lb_pool_v2" "web" {
name = "web-pool"
protocol = "HTTP"
lb_method = "ROUND_ROBIN"
listener_id = opentelekomcloud_lb_listener_v2.http.id
}
resource "opentelekomcloud_lb_member_v2" "web" {
count = 2
pool_id = opentelekomcloud_lb_pool_v2.web.id
address = opentelekomcloud_compute_instance_v2.web[count.index].access_ip_v4
protocol_port = 80
subnet_id = opentelekomcloud_vpc_subnet_v1.web.id
}
Clean, reproducible, and easy to tear down with terraform destroy when you're done.
Tips and Gotchas
A few things I've learned the hard way:
Use variables and tfvars files. Don't hardcode region names, flavour sizes, or image
names. Put them in variables.tf and override per environment with terraform.tfvars or CI/CD secrets.
Tag everything. T Cloud Public supports metadata/tags on most resources. Add at minimum
managed_by = "terraform" and environment = "prod" — you'll be glad when you're debugging
at 2am.
Image names change. Standard_Debian_12_latest is a moving target. For production, pin to
a specific image ID using a data source:
data "opentelekomcloud_images_image_v2" "debian" {
name = "Standard_Debian_12_latest"
most_recent = true
}
The provider is OpenStack underneath. If you hit an undocumented edge case, checking OpenStack documentation often fills the gap — T Cloud Public is built on top of it.
Use modules for reuse. The iits-consulting Terraform modules for OTC provide a solid starting point for VPCs, Kubernetes clusters, and more.
Wrapping Up
Terraform and Open Telekom Cloud are a natural pairing. OTC gives you a solid, GDPR-friendly European cloud platform; Terraform gives you the workflow to manage it without clicking through consoles or writing bespoke scripts. The official provider is actively maintained, covers the full OTC service catalogue, and follows the same patterns you'd use with any other Terraform provider.
If you're already comfortable with Terraform on AWS or GCP, you'll feel at home here within minutes. If you're new to IaC altogether, OTC is actually a nice place to start — the platform is straightforward and the provider documentation is clear.
Give it a go, and as always — keep your state files out of Git.
Have questions or run into something interesting? Drop a comment below or find me on the usual channels.