How to Create Cloud Infrastructure with Terraform
Learn how Terraform automates cloud infrastructure creation on InMotion Cloud. This guide walks through a real example that provisions a network, security group, and virtual machine with a single command.

Creating a server on InMotion Cloud through Horizon takes a few minutes of pointing and clicking. Creating ten servers the exact same way takes ten times the effort, and each one introduces the chance of a missed setting or a misconfigured firewall rule. Terraform eliminates that problem by letting you define your infrastructure in a text file and create all of it with a single command.
This guide walks through a working example that provisions a complete environment on InMotion Cloud: a private network, a security group, and a virtual machine with a public IP address. Everything is created from code, and the same code can rebuild the entire setup from scratch whenever you need it.
This article is part of our series on How Modern Applications Run on Cloud Infrastructure. Terraform is the first stage of the modern cloud workflow. Read the overview for the full picture of how Terraform, Ansible, Docker, and Kubernetes work together.
What Terraform Does
Terraform is an infrastructure-as-code tool. Instead of logging into a dashboard and clicking through menus to create servers, networks, and firewall rules, you write a configuration file that describes exactly what you want. Terraform reads that file and makes API calls to your cloud provider to create everything automatically.
Think of it as a blueprint for your infrastructure. An architect does not build a house by describing each step in real time. They draw a blueprint, and the builders follow it. Terraform works the same way: you describe the end result, and Terraform figures out what needs to happen to get there.
In practical terms: Terraform creates servers and networks automatically instead of doing it manually.
What You Need Before Starting
Before running Terraform against InMotion Cloud, gather the following:
- Terraform installed on your local machine (version 1.0 or later)
- OpenStack API credentials from your InMotion Cloud project (see the authentication options below)
- An SSH key pair already created in your InMotion Cloud project (see Managing SSH Key Pairs)
- Your project's external network name (typically
External)
Installing Terraform
Download Terraform from the official HashiCorp website and add it to your system path. Verify the installation:
1terraform --version
1Terraform v1.9.82on linux_amd64
Setting Up OpenStack Credentials
Terraform needs credentials to authenticate with the InMotion Cloud API. There are three ways to provide them. Choose whichever fits your workflow.
Option 1: Download an OpenRC File from Horizon
Horizon can generate a shell script that sets the environment variables Terraform needs. This is the fastest way to get started because there is nothing to write manually.
- Log into Horizon and navigate to Identity > Application Credentials (or Project > API Access depending on your Horizon version).
- Click Download OpenRC File and select OpenStack RC File.
- Save the file (typically named
app-openrc.shorproject-openrc.sh). - Source the file in your terminal before running Terraform:
1source app-openrc.sh
The script will prompt for your password (or set the application credential secret, depending on the file type). Once sourced, the OS_AUTH_URL, OS_PROJECT_NAME, OS_USERNAME, and related variables are set in your shell session. Terraform's OpenStack provider reads these automatically, so no clouds.yaml file is needed.
Tip: If you downloaded an application credential OpenRC file, it setsOS_APPLICATION_CREDENTIAL_IDandOS_APPLICATION_CREDENTIAL_SECRETinstead of username/password. Application credentials are preferred because they can be scoped to a single project and revoked without changing your account password.
Option 2: clouds.yaml with Application Credentials
If you prefer a file over environment variables, create a clouds.yaml in your working directory. Application credentials are the recommended authentication method because they are scoped to a single project and can be revoked independently of your account password.
To create an application credential, go to Identity > Application Credentials in Horizon and click Create Application Credential. Note the ID and secret.
1clouds:2 inmotioncloud:3 auth:4 auth_url: https://iad1.inmotioncloud.com:50005 application_credential_id: "your-credential-id"6 application_credential_secret: "your-credential-secret"7 region_name: "IAD1"8 interface: "public"9 identity_api_version: 310 auth_type: "v3applicationcredential"
Option 3: clouds.yaml with Username and Password
You can also authenticate with your OpenStack username and password directly. This is simpler to set up but uses your full account credentials rather than a scoped application credential.
1clouds:2 inmotioncloud:3 auth:4 auth_url: https://iad1.inmotioncloud.com:50005 username: "your-username"6 password: "your-password"7 project_name: "your-project-name"8 user_domain_name: "Default"9 project_domain_name: "Default"10 region_name: "IAD1"11 interface: "public"12 identity_api_version: 3
Telling Terraform Which Cloud to Use
If you chose Option 2 or Option 3 with a clouds.yaml file, the provider block in your Terraform configuration references it by name:
1provider "openstack" {2 cloud = "inmotioncloud"3}
If you chose Option 1 (OpenRC environment variables), you can omit the cloud argument entirely. The provider will read credentials from the environment:
1provider "openstack" {}
Security note: Never commitclouds.yamlor OpenRC files to a public repository. Add both to your.gitignorefile.
Writing the Terraform Configuration
Create a file called main.tf. This single file defines everything Terraform will create: a network, a subnet, a router, a security group, and a virtual machine with a floating IP.
Provider Configuration
The first block tells Terraform to use the OpenStack provider and connect to your InMotion Cloud project:
1terraform {2 required_providers {3 openstack = {4 source = "terraform-provider-openstack/openstack"5 version = "~> 3.0"6 }7 }8}910provider "openstack" {11 cloud = "inmotioncloud"12}
Network and Subnet
Next, define a private network for the instance. This is equivalent to creating a network and subnet manually through Project > Network > Networks in Horizon:
1resource "openstack_networking_network_v2" "demo_network" {2 name = "terraform-demo-network"3 admin_state_up = true4}56resource "openstack_networking_subnet_v2" "demo_subnet" {7 name = "terraform-demo-subnet"8 network_id = openstack_networking_network_v2.demo_network.id9 cidr = "192.168.100.0/24"10 ip_version = 411 dns_nameservers = ["8.8.8.8", "8.8.4.4"]12}
Router for Internet Access
Connect the private network to the external network through a router, so the instance can reach the internet:
1data "openstack_networking_network_v2" "external" {2 name = "External"3}45resource "openstack_networking_router_v2" "demo_router" {6 name = "terraform-demo-router"7 external_network_id = data.openstack_networking_network_v2.external.id8}910resource "openstack_networking_router_interface_v2" "demo_router_interface" {11 router_id = openstack_networking_router_v2.demo_router.id12 subnet_id = openstack_networking_subnet_v2.demo_subnet.id13}
Security Group
Define firewall rules that allow SSH (port 22) and HTTP (port 80) access. Without this, the instance would be unreachable:
1resource "openstack_networking_secgroup_v2" "demo_secgroup" {2 name = "terraform-demo-secgroup"3 description = "Allow SSH and HTTP access"4}56resource "openstack_networking_secgroup_rule_v2" "ssh" {7 direction = "ingress"8 ethertype = "IPv4"9 protocol = "tcp"10 port_range_min = 2211 port_range_max = 2212 remote_ip_prefix = "0.0.0.0/0"13 security_group_id = openstack_networking_secgroup_v2.demo_secgroup.id14}1516resource "openstack_networking_secgroup_rule_v2" "http" {17 direction = "ingress"18 ethertype = "IPv4"19 protocol = "tcp"20 port_range_min = 8021 port_range_max = 8022 remote_ip_prefix = "0.0.0.0/0"23 security_group_id = openstack_networking_secgroup_v2.demo_secgroup.id24}
The Virtual Machine
Finally, define the instance (virtual machine) itself, along with a floating IP that makes it reachable from the internet:
1resource "openstack_compute_instance_v2" "demo_instance" {2 name = "terraform-demo-vm"3 image_name = "Ubuntu 24.04 LTS"4 flavor_name = "gen2.small"5 key_pair = "my-keypair"6 security_groups = [openstack_networking_secgroup_v2.demo_secgroup.name]78 network {9 uuid = openstack_networking_network_v2.demo_network.id10 }1112 depends_on = [openstack_networking_router_interface_v2.demo_router_interface]13}1415resource "openstack_networking_floatingip_v2" "demo_fip" {16 pool = "External"17}1819resource "openstack_compute_floatingip_associate_v2" "demo_fip_assoc" {20 floating_ip = openstack_networking_floatingip_v2.demo_fip.address21 instance_id = openstack_compute_instance_v2.demo_instance.id22}2324output "instance_ip" {25 value = openstack_networking_floatingip_v2.demo_fip.address26 description = "The public IP address of the demo instance"27}
That is the entire configuration. Six resources and roughly 80 lines of code define a complete, functional environment.
Running Terraform
With the configuration written, creating the infrastructure takes three commands.
Step 1: Initialize
terraform init downloads the OpenStack provider plugin and prepares the working directory:
1terraform init
1Initializing the backend...23Initializing provider plugins...4- Finding terraform-provider-openstack/openstack versions matching "~> 3.0"...5- Installing terraform-provider-openstack/openstack v3.0.0...6- Installed terraform-provider-openstack/openstack v3.0.078Terraform has been successfully initialized!
Step 2: Preview the Changes
terraform plan shows exactly what Terraform will create before it makes any changes. This is one of Terraform's most valuable features. You see the full list of resources that will be added, changed, or destroyed before anything happens:
1terraform plan
1Terraform will perform the following actions:23 # openstack_compute_instance_v2.demo_instance will be created4 + resource "openstack_compute_instance_v2" "demo_instance" {5 + name = "terraform-demo-vm"6 + image_name = "Ubuntu 24.04 LTS"7 + flavor_name = "gen2.small"8 + key_pair = "my-keypair"9 + security_groups = ["terraform-demo-secgroup"]10 ...11 }1213 # openstack_networking_network_v2.demo_network will be created14 + resource "openstack_networking_network_v2" "demo_network" {15 + name = "terraform-demo-network"16 ...17 }1819 ...2021Plan: 9 to add, 0 to change, 0 to destroy.
Nine resources will be created: the network, subnet, router, router interface, security group, two security group rules, the instance, the floating IP, and the floating IP association.
Step 3: Apply
terraform apply executes the plan and creates all resources on InMotion Cloud:
1terraform apply
1openstack_networking_network_v2.demo_network: Creating...2openstack_networking_secgroup_v2.demo_secgroup: Creating...3openstack_networking_network_v2.demo_network: Creation complete after 3s4openstack_networking_subnet_v2.demo_subnet: Creating...5openstack_networking_secgroup_v2.demo_secgroup: Creation complete after 2s6openstack_networking_secgroup_rule_v2.ssh: Creating...7openstack_networking_secgroup_rule_v2.http: Creating...8openstack_networking_subnet_v2.demo_subnet: Creation complete after 4s9openstack_networking_router_v2.demo_router: Creating...10openstack_networking_secgroup_rule_v2.ssh: Creation complete after 2s11openstack_networking_secgroup_rule_v2.http: Creation complete after 2s12openstack_networking_router_v2.demo_router: Creation complete after 5s13openstack_networking_router_interface_v2.demo_router_interface: Creating...14openstack_networking_router_interface_v2.demo_router_interface: Creation complete after 4s15openstack_compute_instance_v2.demo_instance: Creating...16openstack_compute_instance_v2.demo_instance: Still creating... [10s elapsed]17openstack_compute_instance_v2.demo_instance: Creation complete after 18s18openstack_networking_floatingip_v2.demo_fip: Creating...19openstack_networking_floatingip_v2.demo_fip: Creation complete after 3s20openstack_compute_floatingip_associate_v2.demo_fip_assoc: Creating...21openstack_compute_floatingip_associate_v2.demo_fip_assoc: Creation complete after 2s2223Apply complete! Resources: 9 added, 0 changed, 0 destroyed.2425Outputs:2627instance_ip = "173.231.195.160"
Terraform created the entire environment in under 45 seconds: a network, subnet, router, security group with rules, a virtual machine, and a public IP address.
Verifying the Result
After terraform apply completes, you can verify the resources exist in two ways.
From the command line, SSH into the new instance using the floating IP from the output:
1ssh ubuntu@173.231.195.160
1Welcome to Ubuntu 24.04 LTS (GNU/Linux 6.8.0-31-generic x86_64)2ubuntu@terraform-demo-vm:~$
From Horizon, navigate to Project > Compute > Instances. The terraform-demo-vm instance appears in the list with its assigned network and floating IP. Under Project > Network > Networks, the terraform-demo-network and terraform-demo-subnet are visible. Under Project > Network > Security Groups, the terraform-demo-secgroup shows the SSH and HTTP rules.
Every resource Terraform created is visible in Horizon, just as if you had created them manually. The difference is that the configuration file is the permanent record of what exists and why.
Why Repeatability Matters
The real power of Terraform is not creating infrastructure once. It is creating the same infrastructure reliably, every time.
Recreate After a Failure
If something goes wrong with your environment, you do not need to remember what you built or how you built it. The configuration file is the complete record. Destroy everything and rebuild:
1terraform destroy2terraform apply
An identical environment is back in under a minute.
Duplicate for Another Team Member
A new developer joins the team and needs their own environment. They clone the repository, update one variable (their SSH key name), and run terraform apply. They get the same network topology, the same security rules, and the same instance configuration. No setup guide to follow. No steps to miss.
Promote Across Environments
Your development environment works. Now you need staging and production. Use Terraform variables to customize instance sizes and naming while keeping the same architecture:
1variable "environment" {2 description = "Environment name (dev, staging, production)"3 type = string4 default = "dev"5}67variable "instance_flavor" {8 description = "Instance size"9 type = string10 default = "gen2.small"11}1213resource "openstack_compute_instance_v2" "app_server" {14 name = "${var.environment}-app-server"15 flavor_name = var.instance_flavor16 # ... rest of configuration17}
Run terraform apply -var="environment=staging" -var="instance_flavor=gen2.medium" and staging is live. The same configuration, scaled up, with a different name.
Track Every Change
Because the .tf files are plain text, they belong in version control alongside your application code. Every infrastructure change goes through a pull request. Team members review what will change before it happens. The git history shows who changed what, when, and why. This audit trail does not exist when changes are made by clicking through a dashboard.
Cleaning Up
When you are done with the demo environment, Terraform removes everything it created:
1terraform destroy
1openstack_compute_floatingip_associate_v2.demo_fip_assoc: Destroying...2openstack_compute_floatingip_associate_v2.demo_fip_assoc: Destruction complete after 2s3openstack_networking_floatingip_v2.demo_fip: Destroying...4openstack_compute_instance_v2.demo_instance: Destroying...5openstack_networking_floatingip_v2.demo_fip: Destruction complete after 3s6openstack_compute_instance_v2.demo_instance: Still destroying... [10s elapsed]7openstack_compute_instance_v2.demo_instance: Destruction complete after 12s8openstack_networking_router_interface_v2.demo_router_interface: Destroying...9openstack_networking_secgroup_v2.demo_secgroup: Destroying...10openstack_networking_secgroup_v2.demo_secgroup: Destruction complete after 3s11openstack_networking_router_interface_v2.demo_router_interface: Destruction complete after 5s12openstack_networking_router_v2.demo_router: Destroying...13openstack_networking_subnet_v2.demo_subnet: Destroying...14openstack_networking_router_v2.demo_router: Destruction complete after 4s15openstack_networking_subnet_v2.demo_subnet: Destruction complete after 3s16openstack_networking_network_v2.demo_network: Destroying...17openstack_networking_network_v2.demo_network: Destruction complete after 3s1819Destroy complete! Resources: 9 destroyed.
Every resource is gone. No orphaned networks, no forgotten security groups, no stale floating IPs consuming quota. Terraform tracks what it created and removes exactly those resources.
Where Terraform Fits in the Bigger Picture
Terraform handles the first stage of a modern cloud workflow: building the raw infrastructure. Once servers exist, other tools take over. Configuration management tools like Ansible install software and apply settings. Container tools like Docker package applications for consistent deployment. Orchestration platforms like Kubernetes manage those applications at scale.
Each tool solves one problem well. Terraform's job is to make sure the infrastructure under all of it is consistent, repeatable, and defined in code.
For a complete view of how these tools work together, see How Modern Applications Run on Cloud Infrastructure.
Related Resources
Related Articles
How to Configure Servers with Ansible
Automate server setup on InMotion Cloud with Ansible. This guide walks through installing Ansible, building an inventory from OpenStack instances, and writing reusable playbooks for repeatable, idempotent deployments.
How to Deploy a Kubernetes Cluster Using GitHub Actions on InMotion Cloud
Step-by-step guide to provisioning a Magnum Kubernetes cluster on InMotion Cloud using a pre-built GitHub Actions repository, with automatic HTTPS and WordPress or Drupal deployment.