Terraform VM Bootstrap with Cloud-Init for Immutable Instance Configuration

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 you 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 in the Bootstrap Lifecycle

Cloud-init is a multi-distribution package that handles early initialization of cloud instances. 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.

The core responsibilities performed by cloud-init during first boot include:

  • Setting the hostname
  • Installing packages
  • Writing files
  • Running commands
  • Configuring SSH keys
  • Creating users and groups
  • Mounting filesystems
  • Setting up networking

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.

This separation means Terraform creates the cloud resources, and cloud-init ensures they are properly bootstrapped on first boot.

Cloud-Init Integration Patterns Across Cloud Providers

This document provides a comprehensive overview of how cloud-init is utilized across different cloud providers in the terraform-examples repository. 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 (OCI), Amazon Web Services (AWS), and Microsoft Azure within this repository.

The cloud-init configurations in this 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 serves as a vital bridge between infrastructure provisioning and operating system configuration in the terraform-examples repository.

Terraform Provisioning with User Data

Install software, edit files, and provision machines created with Terraform. Use Packer or Cloud-Init to automatically provision SSH keys and a web server onto a Linux VM created by Terraform in AWS.

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 here.
  • Change into your cloned repo directory.
  • 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.

Structuring Cloud-Init Configuration in 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 multi-part cloud-init configuration in Terraform can be built with the cloudinit_config data source:

```hcl
data "cloudinitconfig" "init" {
gzip = false
base64
encode = false

part {
filename = "00-packages.yml"
content_type = "text/cloud-config"
content = file("${path.module}/cloud-init/packages.yml")
}

part {
filename = "01-users.yml"
contenttype = "text/cloud-config"
content = templatefile("${path.module}/cloud-init/users.yml", {
ssh
pubkey = var.sshpublic_key
})
}

part {
filename = "02-scripts.sh"
content_type = "text/x-shellscript"
content = file("${path.module}/cloud-init/bootstrap.sh")
}
}
```

The data source output is then referenced as user data in the compute resource:

hcl resource "aws_instance" "example" { ami = var.ami instance_type = "t3.micro" user_data_base64 = data.cloudinit_config.init.rendered }

For Oracle Cloud Infrastructure, the pattern uses base64 encoding:

hcl source_details = { user_data = base64encode(file("./cloud-init/vm.cloud-config")) }

For AWS, user data is commonly passed via filebase64:

hcl user_data = filebase64(var.user_data)

For Azure, the shell script is included as user data directly:

hcl user_data = file("${path.module}/cloud-init/centos_userdata.txt")

Practical VM Bootstrap Example

cloud-init allows you to pass a shell script to your instance that installs or configures the machine to your specifications.

A minimal cloud-config for first-boot bootstrap can include package installation, user creation, and SSH key injection:

```yaml

cloud-config

hostname: terraform-node
users:
- name: terraform
sudo: true
shell: /bin/bash
sshauthorizedkeys:
- ssh-ed25519 AAAAC3NzaC1lZDI1N...
packages:
- curl
- git
runcmd:
- echo "Bootstrap complete" > /var/log/bootstrap.log
```

More advanced configurations separate concerns into parts and use Terraform templating for dynamic values. Template your configurations with Terraform's templatefile function for dynamic values.

Configuration (AWS specific) sources are documented for 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.

Validation, Logging, and Reliability Practices

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.

Best practice recommendations for reliable VM bootstrap:

  • 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

These practices prevent drift between infrastructure code and instance state and ensure reproducible bootstraps across scale-out events.

Provider Specific Implementation Notes

Cloud-init is a widely used industry standard for customizing cloud instances during initialization.

Sources: terraform-provider-oci/launch-instance/cloud-init/vm.cloud-config terraform-provider-aws/launch-instance/cloud-init/vm.cloud-config terraform-provider-azure/launch-instance/cloud-init/centos_userdata.txt

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.

The cloud-init configurations in this 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 serves as a vital bridge between infrastructure provisioning and operating system configuration in the terraform-examples repository.

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.

The combination delivers declarative infrastructure with imperative first-boot customization without manual SSH intervention. Using the cloudinit_config data source, templatefile for dynamic values, and schema validation ensures configurations remain portable across OCI, AWS, and Azure. Keeping bootstrap scripts focused on initial setup and delegating ongoing state management to dedicated configuration management tools preserves idempotency and auditability.

Logging and verification mechanisms like phone_home provide operational visibility into bootstrap success, while consistent file formats and Terraform references across providers reduce cognitive overhead for teams operating multi-cloud fleets.

Sources

  1. OneUptime Cloud Init Terraform Guide
  2. DeepWiki Terraform Examples Cloud Init Configuration
  3. HashiCorp Terraform Provision Tutorial
  4. HashiCorp Terraform Provision Cloud Init

Related Posts