Skip to content
  • Independent cloud account marketplace
  • Options and USD prices shown before checkout
  • Support available before and after ordering
Developer Workflows

Infrastructure as Code: Getting Started with Terraform on Any Cloud

How Terraform is organised, which providers cover AWS, Google Cloud, Azure, DigitalOcean, and Hetzner, what init, plan, and apply do, where to keep state, and how to handle credentials safely.

Infrastructure as code means describing servers, networks, DNS records, and storage in text files that a tool turns into real resources. Terraform is the most widely used tool for this job because one workflow covers AWS, Google Cloud, Azure, DigitalOcean, Hetzner, and hundreds of other platforms through plugins called providers. This guide explains how Terraform is organised, what the core commands do, where to keep state, and how to handle the credentials that Terraform needs without leaking them.

What Terraform does and what it does not

Terraform reads configuration files written in HCL, compares the desired resources with what already exists, and works out the changes needed to close the gap. It creates, updates, or destroys resources by calling each provider’s API, and it records what it built in a state file so the next run knows what it owns. The tool is declarative: you describe the end result, not the steps.

Terraform does not configure the inside of a server. It can launch a VM and pass a cloud-init script to it, but installing packages, editing config files, and starting services is a job for cloud-init, Ansible, or a container image. Terraform also does not replace a version control system. The configuration belongs in a Git repository, reviewed through pull requests like application code.

Since 2023 Terraform ships under the Business Source License, and the OpenTofu project maintains an open source fork that reads the same configuration files. Both are in wide use, and the guidance here applies to either.

Providers for AWS, Google Cloud, Azure, DigitalOcean, and Hetzner

A provider is a plugin that knows how to talk to one platform’s API. You declare the providers a project needs in a required providers block, and the init command downloads the matching versions from the public registry. Pin a version range for every provider so that a new release cannot change behaviour under you. Each provider exposes resources (things Terraform manages) and data sources (things it only reads), and the registry documentation lists every argument each one accepts.

Platform Provider name Typical first resources Authentication
AWS hashicorp/aws VPC, subnets, security groups, EC2 instances, S3 buckets, IAM roles IAM role, SSO profile, or environment variables
Google Cloud hashicorp/google Project services, VPC network, Compute Engine instances, Cloud Storage buckets Application Default Credentials or a service account key
Azure hashicorp/azurerm Resource group, virtual network, virtual machines, storage accounts Azure CLI login, managed identity, or a service principal
DigitalOcean digitalocean/digitalocean Droplets, VPC, firewalls, Spaces buckets, DNS records Personal access token with read and write scope
Hetzner Cloud hetznercloud/hcloud Servers, networks, firewalls, volumes, floating IPs, SSH keys Project API token

The smaller providers are a good place to learn. A Hetzner or DigitalOcean project with one server, one firewall, and one SSH key resource is a few dozen lines and applies in under a minute. The AWS provider needs more scaffolding because networking is not created for you: a usable VM needs a VPC, a subnet, an internet gateway, a route table, and a security group before the instance resource makes sense.

The core workflow: init, plan, apply, destroy

Every project follows the same loop. Running init in the project directory installs providers and prepares the backend that holds state. Running plan produces a preview that lists every resource to be added, changed, or destroyed, with a summary count at the end; nothing is modified. Running apply executes that plan after you confirm, and prints outputs such as the public IP of a new server. Running destroy removes everything the state file tracks, which is useful for temporary environments and dangerous for anything else.

  1. Write or change the configuration and commit it to a branch.
  2. Run plan and read the whole preview. Pay attention to resources marked for replacement, because replacement means delete and recreate.
  3. Have a second person review the plan output alongside the code change.
  4. Run apply from a controlled place, ideally a CI job rather than a laptop.
  5. Commit nothing that apply generated locally; the state file and provider binaries stay out of Git.

Use variables for anything that differs between environments, such as region, instance size, or the count of servers, and use outputs to expose values that other systems need. Group related resources into modules once a pattern repeats, but resist building modules before you have written the same thing twice.

Managing the state file

The state file maps each resource block to the real object it created. It contains resource IDs, IP addresses, and often sensitive values such as database passwords in plain text. Two people applying with separate copies of the state will overwrite each other’s work, so the state must live in one shared location with locking.

Remote backends solve this. On AWS the usual choice is an S3 bucket with versioning enabled and either a DynamoDB table or the native lock file support in recent Terraform releases. Google Cloud uses a Cloud Storage bucket, and Azure uses a storage account container; both lock the state during a run. DigitalOcean Spaces speaks the S3 API, so the S3 backend works there with the endpoint overridden. Hetzner has no object storage backend of its own in the provider, so teams there typically use HCP Terraform, a self hosted state server, or a bucket at another provider. Whatever you pick, restrict read access to the state bucket as tightly as you restrict production credentials, and turn on versioning so a corrupted state can be rolled back.

Credentials and security hygiene

Terraform needs credentials with enough rights to create and delete infrastructure, which makes them among the most powerful secrets in your organisation. Never write them into configuration files or commit a provider block with a token argument. Supply them through environment variables, a credentials file outside the repository, or better still a short lived identity: an IAM role assumed through OIDC from GitHub Actions or GitLab CI on AWS, Workload Identity Federation on Google Cloud, or a federated credential on an Azure service principal. For DigitalOcean and Hetzner, where API tokens are the only option, create a token per environment, name it after the pipeline that uses it, and store it in the CI platform’s secret store.

  • Mark sensitive variables as sensitive so plan output masks them.
  • Run plan on every pull request and apply only from the main branch.
  • Keep production and staging in separate state files and separate cloud accounts or projects where possible.
  • Rotate long lived tokens on a schedule and immediately when a team member leaves.
  • Review provider version upgrades in their own pull request so the diff is only the upgrade.

The Cloud Library guide to API keys and secrets management covers where those tokens should live in more detail.

Where to go next

Terraform works the same way on every account listed in the marketplace. The product pages for AWS accounts, DigitalOcean accounts, and Hetzner accounts list the exact variants available, including the Droplet and server limits that determine how many instances a first apply can create.

Once a configuration applies cleanly, the next step is automating it. Read CI/CD pipelines that deploy to the cloud for running plan and apply from a pipeline, and cloud networking basics for the VPC, subnet, and firewall concepts that AWS configurations depend on. The Cloud Library hub lists every developer workflow guide.

More from the Cloud Library

Telegram