Infrastructure as Code: An Introduction with Terraform
Infrastructure as code replaces console clicking with reviewable text files. This introduction explains Terraform's declarative model, why state deserves care, when modules help, and how plan and apply belong in a pipeline rather than on a laptop.

- The Console Is a Bad Place to Keep Your Architecture
- Declaring What You Want, Not the Steps to Get There
- A Configuration Small Enough to Read
- State Is Where the Surprises Live
- Variables, Outputs and the Point of Modules
- Running It Where Humans Are Not
- What Terraform Does Not Do
- Getting Started Without Regrets
The Console Is a Bad Place to Keep Your Architecture
Every cloud account starts the same way. Somebody opens the web console, clicks through a wizard, picks a region, accepts a few defaults and ends up with a working server. Six months later nobody can say why that security group allows a particular port, or which of the four load balancers is still in use.
Infrastructure as code replaces that clicking with text files. The files describe the resources you want, a tool reconciles reality against them, and the version history of the repository becomes the history of your infrastructure. Reviewing a change to a subnet becomes the same activity as reviewing a change to application code.
Terraform is the most widely used tool in this space, and the concepts it teaches transfer to alternatives such as OpenTofu, Pulumi, AWS CloudFormation and Azure Bicep. This introduction walks through its model rather than cataloguing its syntax.
Declaring What You Want, Not the Steps to Get There
Terraform configurations are written in HCL, a block based language. You declare a desired end state. The tool figures out the ordering, the dependencies and the individual API calls required to reach it.
Three ideas carry most of the weight. A provider is a plugin that knows how to talk to one platform API. A resource is a single managed object such as an instance, a bucket or a DNS record. State is the record that maps each declared resource to the real object that was created for it.
Because the description is declarative, running the same configuration twice does not create two copies of anything. That property, idempotence, is what makes the approach safe to automate. You can run it on every merge without thinking about whether it already ran.
A Configuration Small Enough to Read
A minimal project is one directory with a few files. Start by pinning the provider so a colleague running the same code next month gets the same plugin version.
terraform { required_providers { digitalocean = { source = "digitalocean/digitalocean", version = "~> 2.0" } } }
Then declare something concrete. A droplet resource needs a name, an image, a size and a region, and it can reference an SSH key that already exists in the account through a data source.
resource "digitalocean_droplet" "api" { name = "api-01" image = "ubuntu-24-04-x64" size = "s-2vcpu-4gb" region = "fra1" ssh_keys = [data.digitalocean_ssh_key.deploy.id] }
Three commands drive the loop. terraform init downloads the provider plugins and sets up the working directory. terraform plan compares your files against state and prints exactly what it intends to add, change or destroy. terraform apply carries out that plan.
Read the plan output every single time. The line that matters most is any resource marked for replacement, because replacement means destroy then create, and for a database or a volume that is a data loss event rather than an edit.
State Is Where the Surprises Live
By default Terraform writes state to a local file in the working directory. That is fine for an afternoon of learning and wrong for anything shared. Two engineers applying from two laptops produce two divergent views of the same account.
The fix is a remote backend: a shared location for the state plus a locking mechanism so only one apply runs at a time. On AWS that is commonly an S3 bucket with state locking enabled; on Azure it is a storage account container; managed services such as HCP Terraform provide the storage and the lock together.
Treat the state file as sensitive. It can contain resource attributes that count as secrets, including generated passwords and connection strings, so the bucket holding it should be encrypted and tightly restricted. The practices in our guide to managing API keys and secrets apply to it directly.
When an object was created by hand and now needs to be managed, terraform import attaches it to a resource block instead of recreating it. Expect to write the matching configuration yourself and to iterate until the plan comes back clean.
Variables, Outputs and the Point of Modules
Hard coded values age badly. Variables let one configuration describe staging and production without duplication, with values supplied per environment rather than edited in place.
variable "instance_size" { type = string default = "s-1vcpu-2gb" }
Outputs expose computed values, such as an assigned public address, so other tooling can consume them. A module is simply a directory of configuration that can be called with inputs, which is how teams package a repeatable pattern like "a web tier with a load balancer and a firewall" once and reuse it.
Resist building a module for everything on day one. Modules earn their keep when the third environment appears. Before that they mostly add indirection to code you are still changing daily.
The public registry hosts community and vendor modules, and reading a well written one is a fast way to learn idiomatic structure. The Terraform language documentation is the reference to keep open while you do.
Running It Where Humans Are Not
The moment more than one person touches infrastructure, apply should move off laptops. A pipeline runs terraform plan on every pull request and posts the output for review, then runs apply only after the change merges to the main branch.
That arrangement gives you the thing consoles never provide: a reviewed, attributable record of who changed what and why. It pairs naturally with the pull request workflow described in CI/CD pipelines that deploy to cloud servers, and it is a step toward the continuous reconciliation model that cluster based tooling takes further.
Give the pipeline its own credentials with only the permissions it needs, and prefer short lived federated credentials over long lived keys where the provider supports them. A pipeline that can create a virtual machine does not also need permission to delete the billing configuration.
Add terraform fmt -check and terraform validate as early pipeline steps. They cost seconds and remove an entire category of review comments.
What Terraform Does Not Do
Terraform provisions resources. It does not configure the software inside them. Once the instance exists, something else has to install packages, write configuration files and start services.
Two tools usually fill that gap. First boot scripting handles the bootstrap, as explained in cloud-init for server provisioning. Ongoing configuration management is the job described in Ansible for cloud server configuration. A common division of labour is Terraform for anything the provider API owns, and one of those two for anything inside the operating system.
Terraform also does not protect you from a bad plan you approved without reading, and it does not make a poor architecture good. It makes the architecture visible, which is usually enough to start the conversation.
Getting Started Without Regrets
- Pin provider versions from the first commit so runs are reproducible.
- Move state to a remote backend with locking before a second person joins.
- Treat state as secret material, encrypted and access restricted.
- Read every plan, and stop at any resource marked for replacement.
- Import existing objects rather than rebuilding them underneath a running service.
- Introduce modules when repetition appears, not before.
- Run plan in a pipeline on pull requests and apply only from the main branch.
- Pair it with a configuration tool for everything inside the instance.
The approach pays off fastest on platforms with clean, well documented APIs. If you are experimenting, a small developer cloud is a forgiving place to learn, and the DigitalOcean account listings describe what such an account provides. Teams applying the same patterns at hyperscaler scale usually start from an AWS account instead, and the AWS versus DigitalOcean comparison sets out how differently the two model their resources.
Questions about a cloud account?
Ask about products, variants, availability or purchasing on either channel.




