Terraform Cloud-Init Integration for VM Bootstrap

cloud-init is the industry standard for initializing cloud instances at boot time. When combined with Terraform, it provides a powerful way to bootstrap virtual machines with the exact configuration they need from the moment they start. Instead of manually configuring servers after creation, cloud-init runs during the first boot to set up everything from package installation to user creation to service configuration. This guide shows how to use Terraform and cloud-init together effectively.

cloud-init is a multi-distribution package that handles early initialization of cloud instances. It runs during the boot process and can perform tasks like setting the hostname, installing packages, writing files, running commands, configuring SSH keys, creating users and groups, mounting filesystems, and setting up networking.

When you create a generic compute resource in Terraform, your virtual machine may not have much capability because it is a fresh install and needs to be provisioned with the software you want to use. Manually installing the necessary software and its respective dependencies on each VM is time consuming and difficult to maintain at scale.

cloud-init is a standard configuration support tool available on most Linux distributions and all major cloud providers. cloud-init allows you to pass a shell script to your instance that installs or configures the machine to your specifications.

What Cloud-Init Does At Boot

cloud-init is an open-source package that handles early initialization of a cloud instance. It identifies the cloud provider on which it's running, reads any provided metadata from that provider, and initializes the instance accordingly.

In the context of Terraform deployments, cloud-init allows for consistent instance configuration across multiple cloud providers.

Core capabilities provided by cloud-init during first boot include:

  • Hostname configuration
  • Package installation
  • File writing and templating
  • Command execution
  • SSH key configuration
  • User and group creation
  • Filesystem mounting
  • Network setup

cloud-init serves as a vital bridge between infrastructure provisioning and operating system configuration. Terraform creates the cloud resources, and cloud-init ensures they are properly bootstrapped on first boot.

Terraform Integration Patterns

The cloud-init configurations in the terraform-examples repository follow a common pattern across providers.

Provider File Format Typical Location Reference in Terraform
OCI YAML (cloud-config) cloud-init/vm.cloud-config base64encode(file("./cloud-init/vm.cloud-config"))
AWS YAML (cloud-config) cloud-init/vm.cloud-config filebase64(var.user_data)
Azure Shell Script cloud-init/centos_userdata.txt Included as user data

In Oracle Cloud Infrastructure, cloud-init is implemented using a YAML-formatted cloud-config file.

cloud-init is a widely used industry standard for customizing cloud instances during initialization. This page explains how cloud-init configurations are structured and implemented across Oracle Cloud Infrastructure, Amazon Web Services, and Microsoft Azure within this repository.

Sources for these patterns include terraform-provider-oci/launch-instance/cloud-init/vm.cloud-config, terraform-provider-aws/launch-instance/cloud-init/vm.cloud-config, and terraform-provider-azure/launch-instance/cloud-init/centos_userdata.txt.

Structuring Cloud-Init With Terraform

Use the cloudinit_config data source to organize complex configurations into multiple parts. Template your configurations with Terraform's templatefile function for dynamic values. Validate configurations locally before deploying with cloud-init schema.

Keep cloud-init scripts focused on initial bootstrap and use configuration management tools for ongoing state management. Log all custom script output for troubleshooting. Use phone_home or similar mechanisms to verify that cloud-init completed successfully.

A typical Terraform configuration that references cloud-init user data looks like:

```hcl
data "cloudinitconfig" "bootstrap" {
part {
content
type = "text/cloud-config"
content = templatefile("${path.module}/cloud-init/vm.cloud-config", {
hostname = var.hostname
})
}
}

resource "awsinstance" "example" {
ami = var.ami
instance
type = var.instancetype
user
database64 = data.cloudinitconfig.bootstrap.rendered
}
```

The cloudinitconfig data source allows multi-part configurations to be merged into a single userdata payload. This is useful when you need to combine cloud-config YAML with shell scripts or cloud-config fragments.

Template files can be stored separately and rendered with dynamic values from Terraform variables.

Provider Specific Implementation Details

Configuration differences across providers are handled through file format and encoding.

OCI implementation uses YAML cloud-config and base64 encoding.

AWS implementation uses YAML cloud-config with filebase64 encoding of user data.

Azure implementation in examples uses a shell script included as user data.

Did you know that you could use cloud-init with Terraform? The plan is to pick your favorite cloud provider to try this, as all you need is a Linux VM. Using Azure to spin up an Ubuntu server and apply a cloud-init template is a common example.

The cloud-init configurations in this repository follow a common pattern across providers with provider specific references in Terraform.

Practical Tutorial Workflow

In this tutorial, you will create a Terraform instance with the user_data to deploy a Go web app and SSH key to the newly created device, allowing you to SSH into the machine without a password and start the app with that user.

For this tutorial, you will need the following:

  • Clone the example repository
  • Change into your cloned repo directory
  • Generate a new SSH key to pair with the new terraform user you create on this instance

Generate a new SSH key in your terminal called tf-cloud-init. The argument provided with the -f flag creates the key in the current directory and creates two files called tf-cloud-init and tf-cloud-init.pub.

A typical workflow includes:

  • Clone the example repository here
  • $ git clone -b cloudinit https://github.com/hashicorp-education/learn-terraform-provisioning
  • Change into your cloned repo directory
  • $ cd learn-terraform-provisioning

The user_data block is passed to the compute resource at creation time. On first boot, cloud-init reads the metadata, applies the cloud-config, creates the user, installs packages, and runs commands.

Best Practices for Reliable Bootstrap

Terraform and cloud-init together provide a clean separation between infrastructure provisioning and instance initialization. Terraform creates the cloud resources, and cloud-init ensures they are properly bootstrapped on first boot. By using multi-part configurations, templates, and proper validation, you can build reliable VM bootstrap processes that work consistently across your infrastructure. Whether you are setting up a single instance or an auto scaling group with hundreds of nodes, cloud-init gives you the flexibility to configure each machine exactly as needed.

Key operational practices include:

  • Keep cloud-init scripts focused on initial bootstrap
  • Use configuration management tools for ongoing state management
  • Log all custom script output for troubleshooting
  • Use phone_home or similar mechanisms to verify that cloud-init completed successfully
  • Validate configurations locally before deploying with cloud-init schema
  • Template configurations with Terraform's templatefile function for dynamic values
  • Organize complex configurations into multiple parts with the cloudinit_config data source

These practices reduce drift and make debugging easier when instances fail to reach a ready state.

Common Configuration Examples

A minimal cloud-config example for user creation and SSH key injection can be expressed in YAML and rendered via templatefile.

Example cloud-config structure:

```yaml

cloud-config

hostname: ${hostname}
users:
- name: terraform
sudo: ALL=(ALL) NOPASSWD:ALL
sshauthorizedkeys:
- ${sshpublickey}
package_update: true
packages:
- git
runcmd:
- echo "Bootstrap complete"
```

When combined with Terraform, variables can be injected at plan time to make the configuration reusable across environments.

Why Separate Provisioning From Initialization

Manual installation of necessary software and its respective dependencies on each VM is time consuming and difficult to maintain at scale.

cloud-init allows you to pass a shell script to your instance that installs or configures the machine to your specifications. Terraform handles the declarative creation of the VM, networking, and storage. cloud-init handles the imperative first-boot setup inside the OS.

This separation makes infrastructure code more portable and testable. Terraform state tracks resource existence, while cloud-init ensures the operating system reaches the desired initial state without additional SSH connections or remote provisioners.

Conclusion

Conclusion

Terraform and cloud-init together provide a clean separation between infrastructure provisioning and instance initialization. Terraform creates the cloud resources, and cloud-init ensures they are properly bootstrapped on first boot. By using multi-part configurations, templates, and proper validation, you can build reliable VM bootstrap processes that work consistently across your infrastructure. Whether you are setting up a single instance or an auto scaling group with hundreds of nodes, cloud-init gives you the flexibility to configure each machine exactly as needed.

cloud-init is the industry standard for initializing cloud instances at boot time. When combined with Terraform, it provides a powerful way to bootstrap virtual machines with the exact configuration they need from the moment they start. Instead of manually configuring servers after creation, cloud-init runs during the first boot to set up everything from package installation to user creation to service configuration.

Using the cloudinit_config data source, templating with templatefile, and provider-specific encoding patterns allows teams to maintain consistent, repeatable VM bootstrapping across OCI, AWS, Azure, and other providers. The approach scales from a single development instance to hundreds of nodes in auto scaling groups while keeping initial bootstrap logic declarative and version controlled.

Sources

  1. How to use Terraform with Cloud-Init for VM Bootstrap
  2. Cloud-Init Configuration
  3. Terraform Provision Cloud-Init Tutorial
  4. Cloud-Init with Terraform

Related Posts