
Terraform lets you automate resource deployment.
Real-world use case
You need to deploy cloud resources in a reproducible way on Scaleway or OVHcloud: here's how to configure Terraform to automate this deployment.
Terraform
Terraform is a tool I use to provision my resources hosted with certain providers such as OVH, Scaleway, Azure, or AWS. This is what's known as Infrastructure As Code (IaC): it lets you automate certain tasks. The goal is to provision resources with a provider. More information: Browse Providers | Terraform Registry.
All the commands below are compatible with all clients (Windows, Mac, Linux). In our example, we'll deploy infrastructure on Scaleway with Terraform and run actions on an OVHcloud server.
Installing Terraform on macOS
You can install the package manually on macOS, or via the Homebrew package manager:
brew tap hashicorp/tap
Running `brew update --preinstall`...
==> Auto-updated Homebrew!
d 3 taps (hashicorp/tap, homebrew/core and homebrew/cask).
==> New Formulae
dbml-cli dotdrop [email protected] goctl libabw lndir octosql release-it sdl2_sound sftpgo xcode-kotlin
==> Updated Formulae
Updated 554 formulae.
==> New Casks
aethersx2 jquake plus42-binary ti-smartview-ce-for-the-ti-84-plus-family vieb
bike manila plus42-decimal tqsl yandex-music-unofficial
ferdium mega podman-desktop trivial
groestlcoin-core oxwu protokol universal-android-debloater
==> Updated Casks
Updated 731 casks.
==> Deleted Casks
entropy gloomhaven-helper indigo kite megax nbtexplorer shortery tifig twitch xoctave
brew install hashicorp/tap/terraform
==> Downloading https://releases.hashicorp.com/terraform/1.1.9/terraform_1.1.9_darwin_arm64.zip
######################################################################## 100.0%
==> Installing terraform from hashicorp/tap
???? /opt/homebrew/Cellar/terraform/1.1.9: 3 files, 71.4MB, built in 2 seconds
==> Running `brew cleanup terraform`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
Next I check the registry of existing providers and look for Scaleway: scaleway/scaleway | Terraform Registry and OVHcloud.
Configuring Terraform
To get started, you need to create a folder dedicated to each Terraform project. Then you need to create a main.tf file inside it. There are two ways to write our configuration: using the HashiCorp language or the structured JSON format. In my case, I prefer using JSON formatting.
Terraform with Scaleway
We'll look at an integration with the Scaleway provider (the init, plan, apply commands are explained further below in the OVH provider example if needed).
Here's a configuration example on Scaleway:
terraform {
required_providers {
scaleway = {
source = "scaleway/scaleway"
}
}
required_version = ">= 0.13"
}
provider "scaleway" {
alias = "production"
profile = "kassianoff-blog"
}
resource "scaleway_account_ssh_key" "jérémie" {
provider = scaleway.production
name = "Jérémie"
public_key = "ssh-ed25519 AABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFFGGAABBCCDDEEFF"
}
resource "scaleway_instance_ip" "public_ip" {
provider = scaleway.production
}
locals {
ip_source = [
{ ip = "my_routable_ip_01/32", port = "22" },
{ ip = "my_routable_ip_02/32", port = "22" },
]
any_in = [
{ ip_range = "0.0.0.0/0", port = "80" },
{ ip_range = "0.0.0.0/0", port = "443" },
]
any_out = [
{ ip_range = "0.0.0.0/0", port = "53", protocol = "TCP" },
{ ip_range = "0.0.0.0/0", port = "53", protocol = "UDP" },
{ ip_range = "0.0.0.0/0", port = "80", protocol = "TCP" },
{ ip_range = "0.0.0.0/0", port = "443", protocol = "TCP" },
]
}
resource "scaleway_instance_security_group" "prod" {
provider = scaleway.production
description = "scaleway-firewall"
name = "Kassianoff scaleway firewall"
inbound_default_policy = "drop"
outbound_default_policy = "drop"
enable_default_security = false
dynamic "inbound_rule" {
for_each = local.ip_source && local.any_in
content {
action = "accept"
ip = inbound_rule.value.ip
ip_range = inbound_rule.value.ip_range
port = inbound_rule.value.port
}
}
dynamic "outbound_rule" {
for_each = local.any_out
content {
action = "accept"
ip_range = outbound_rule.value.ip_range
port = outbound_rule.value.port
protocol = outbound_rule.value.protocol
}
}
}
resource "scaleway_instance_server" "blog" {
provider = scaleway.production
name = "kassianoff blog"
type = "DEV1-L"
image = "ubuntu_jammy"
tags = [ "blog" ]
ip_id = scaleway_instance_ip.public_ip.id
security_group_id = scaleway_instance_security_group.blog.id
private_network {
pn_id = scaleway_vpc_private_network.blog.id
}
}
resource "scaleway_rdb_database" "blog" {
instance_id = scaleway_rdb_instance.blog.id
name = "kassianoff-database"
node_type = "DB-DEV-S"
engine = "PostgreSQL-14"
is_ha_cluster = true
disable_backup = true
user_name = "kassianoff"
password = "thiZ_is_v&ry_s3cret"
region= "fr-par"
tags = [ "db", "blog" ]
volume_type = "bssd"
volume_size_in_gb = 5
private_network {
ip_net = "192.168.1.254/24"
pn_id = "${scaleway_vpc_private_network.blog.id}"
}
}
resource scaleway_vpc_private_network blog {
name = "production"
}
Let's not forget to configure a profile for our Scaleway provider:
profiles:
kassianoff-blog:
access_key: XXXXXXXXXXXXXXXXXXXX
secret_key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
default_organization_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
default_project_id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
default_zone: fr-par-1
default_region: fr-par
api_url: https://api.scaleway.com
insecure: false
- We specify the provider we want to interact with, i.e. Scaleway.
- We defined a profile named "kassianoff-blog" to contact our provider. This refers to a configuration file (which isn't the default one). It contains our secrets, our deployment zone, and Scaleway's endpoint:
- We add an SSH key to our Scaleway profile so we can connect to our next instance.
- We add a public_ip resource so we can get an IP address on our next instance.
- We declare a "locals" variable in a JSON array with several values: ip_source, any_in, any_out, so we can reuse them when editing the Scaleway firewall configuration.
- We'll then create a Scaleway security group by adding our firewall rules in a dynamic expression so we can reuse our variables.
- We'll then create our server instance by choosing its name, its type, and its characteristics. We notice that the integration with the public IP and the security group is clearly visible.
- Furthermore, we want to create a database managed by Scaleway so we don't have to manage it ourselves. We also connect a private network to our database and configure its interface on the private network.
- A private network will be created so the instance and the database can communicate over a private network (you'll need to configure your ens5(eth1) interface on the linux server to access the database).
Terraform with OVHcloud
Here's another small example to switch my kimsufi server, dating from 2013, into rescue mode and reboot it:
terraform {
required_providers {
ovh = {
source = "ovh/ovh"
}
}
}
provider "ovh" {
endpoint = "ovh-eu"
application_key = "namJkXXXXXXXXXXX"
application_secret = "rXXXXXXXXXXXX"
consumer_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}
data ovh_dedicated_server_boots "rescue" {
service_name = "ksXXXXXXX.kimsufi.com"
boot_type = "rescue"
kernel = "rescue64-pro"
}
resource ovh_dedicated_server_update "server_on_rescue" {
service_name = "ksXXXXXXX.kimsufi.com"
boot_id = data.ovh_dedicated_server_boots.rescue.result[0]
monitoring = true
state = "ok"
}
resource ovh_dedicated_server_reboot_task "server_reboot" {
service_name = data.ovh_dedicated_server_boots.rescue.service_name
keepers = [
ovh_dedicated_server_update.server_on_rescue.boot_id,
]
}
One of the first Terraform commands is "terraform init". It's used to initialize a working directory containing Terraform configuration files. It's the first command to run after writing a new Terraform configuration.
terraform init
Initializing the backend...
Initializing provider plugins...
- Finding ovh/ovh versions matching "~> 0.11.0"...
- Installing ovh/ovh v0.11.0...
- Installed ovh/ovh v0.11.0 (signed by a HashiCorp partner, key ID F56D1A6CBDAAADA5)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
The second command is "terraform plan". This command creates an execution plan, which lets you preview the changes Terraform plans to make to your infrastructure.
By default, when Terraform creates a plan, it reads the current state of all already-existing remote objects to make sure Terraform's state is up to date. It then compares the current configuration to the previous state and notes any differences. It then proposes a set of change actions that should, if applied, make the remote objects match the current configuration:
terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# ovh_dedicated_server_reboot_task.server_reboot will be created
+ resource "ovh_dedicated_server_reboot_task" "server_reboot" {
+ comment = (known after apply)
+ done_date = (known after apply)
+ function = (known after apply)
+ id = (known after apply)
+ keepers = [
+ "1122",
]
+ last_update = (known after apply)
+ service_name = "ksxxxxxxx.kimsufi.com"
+ start_date = (known after apply)
+ status = (known after apply)
}
# ovh_dedicated_server_update.server_on_rescue will be created
+ resource "ovh_dedicated_server_update" "server_on_rescue" {
+ boot_id = 1122
+ id = (known after apply)
+ monitoring = true
+ service_name = "ksxxxxxxx.kimsufi.com"
+ state = "ok"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
ovh_dedicated_server_update.server_on_rescue: Creating...
ovh_dedicated_server_update.server_on_rescue: Still creating... [10s elapsed]
ovh_dedicated_server_update.server_on_rescue: Creation complete after 11s [id=ksxxxxxxx.kimsufi.com]
ovh_dedicated_server_reboot_task.server_reboot: Creating...
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [10s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [20s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [30s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [40s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [50s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m0s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m10s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m20s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Still creating... [1m30s elapsed]
ovh_dedicated_server_reboot_task.server_reboot: Creation complete after 1m31s [id=251909160]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
An email from OVH will automatically arrive in your mailbox to send you the connection information in rescue mode. I invite you to check out the OVH documentation to deploy other kinds of configuration, and also the Terraform documentation to learn more.
Conclusion
I've presented installing Terraform on macOS as well as two concrete configuration examples: provisioning a complete infrastructure on Scaleway (SSH key, firewall, instance, database, and private network) and driving an OVHcloud dedicated server to switch it into rescue mode. The essential commands terraform init, plan, and apply are detailed with their actual outputs. This infrastructure as code approach lets you automate and document deployments otherwise done manually. It requires properly securing the secrets (API keys, passwords) used in the configuration files.

