Terraform basics · Lesson 7 of 9 · about 30 minutes

Your first real cloud

How to create a real web server on DigitalOcean with Terraform, keep your access token out of your code, know exactly what it costs, and destroy it cleanly when you're done.

By the end you'll know

  • What it costs to run a small server in the cloud, and how billing works
  • How to create an access token, and give it to Terraform without writing it down
  • How to create a real server with a web page on it, from one configuration
  • What a startup script is, and why the page takes a minute to appear
  • How to destroy everything and check that it's really gone

Where you are

At the end of Lesson 6, you'd used every core Terraform idea on your own computer: plans, state, providers, dependencies, variables, outputs, loops and conditionals. Nothing could cost you money, and nothing could be seen from outside.

This lesson takes the same ideas to a real cloud. You'll create a small server on DigitalOcean, have it serve a web page that anyone on the internet can open, and then destroy it. The Terraform is almost exactly what you already know. What's new is what surrounds it: an account, a secret token, a bill, and the habit of cleaning up.

DigitalOcean is used here because it's simple, its prices are clear, and it's what CivicGrid runs on. Everything you learn transfers to AWS, Google Cloud and Azure: only the provider and the resource names change.

What it costs

Read this part before you create anything.

You'll create the smallest Basic server DigitalOcean offers, which it calls a Droplet: 1 virtual CPU, 512 MiB of memory and a 10 GiB disk. At the time of writing, DigitalOcean lists it at $4 a month, or $0.00595 an hour, and Basic Droplets are billed per second, with a minimum of 60 seconds or one cent, whichever is higher. Check the current prices on DigitalOcean's pricing page (opens in a new tab) before you start, as they can change.

So working through this lesson, with the server alive for well under an hour, should cost about a cent. The real risk isn't this lesson; it's forgetting to destroy the server afterwards. A forgotten $4 server costs $4 a month until you notice, and forgotten bigger ones cost far more. That's why this lesson ends with a clean-up you check, not just a command you run.

Two more things:

  • You'll need an account with a payment method. New accounts are sometimes offered free credit; check what DigitalOcean offers when you sign up.
  • Set a billing alert. In the DigitalOcean control panel, under Billing, you can set an alert to email you when your monthly spending passes an amount, like $5. It's the smoke alarm for forgotten servers.

Your access token

Terraform talks to DigitalOcean through its API, the same way Python Lesson 6 talked to GitHub's. To be allowed to create servers, it needs an access token: a long secret string that works like a password for your account.

A spare key you give to a builder lets them into your house, so you'd give it to them in person, not tape it to the front door. The token is that key, and writing it into a .tf file is the tape on the door: it would end up in your files, maybe in Git, and in the hands of anyone who reads them.

Step 1. Create the token:

  1. Log in to the DigitalOcean control panel, and open API in the left-hand menu.
  2. Choose Generate New Token.
  3. Give it a name, like terraform-practice, and pick a short expiration, like 7 days, so it stops working by itself.
  4. For scopes, Full Access is the simplest for learning.
  5. Copy the token straight away. DigitalOcean only shows it once.

Step 2. Give it to Terraform without writing it down. The DigitalOcean provider reads a token from an environment variable called DIGITALOCEAN_TOKEN, so set it with read -s, which reads what you type without showing it on screen. On a Mac, in the default terminal:

BASH
read -s "DIGITALOCEAN_TOKEN?Token: "
export DIGITALOCEAN_TOKEN

The first line asks for the token, with Token: as the prompt, and stores what you paste without showing it. The second makes it an environment variable, so Terraform can see it. (On Linux or in WSL, where the shell is usually bash, the first line is read -s -p "Token: " DIGITALOCEAN_TOKEN instead.) Nothing is printed, nothing is saved in a file, and the token disappears when you close the terminal, like the virtual environment in Python Lesson 6. In a new terminal, you'd run these two lines again.

The configuration

This is a separate project from terraform-practice, with its own state, so make a new folder:

BASH
mkdir terraform-cloud
cd terraform-cloud

Step 3. Create four files. versions.tf:

HCL
terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.106"
    }
  }
}

variables.tf, with the region and size as variables, so you can change them without editing the server itself:

HCL
variable "region" {
  description = "DigitalOcean region for the server"
  type        = string
  default     = "nyc3"
}

variable "size" {
  description = "The server's size, as DigitalOcean names it"
  type        = string
  default     = "s-1vcpu-512mb-10gb"
}

main.tf, with the server itself:

HCL
provider "digitalocean" {}

resource "digitalocean_droplet" "web" {
  name   = "terraform-practice-web"
  image  = "ubuntu-24-04-x64"
  region = var.region
  size   = var.size

  user_data = <<-EOT
    #!/bin/bash
    apt-get update
    apt-get install -y nginx
    echo "<h1>Built by Terraform</h1>" > /var/www/html/index.html
  EOT
}

and outputs.tf:

HCL
output "ip_address" {
  description = "The server's public IP address"
  value       = digitalocean_droplet.web.ipv4_address
}

output "url" {
  description = "Where to see the web page"
  value       = "http://${digitalocean_droplet.web.ipv4_address}"
}

Here's what's new:

  • provider "digitalocean" {} is empty, because the provider finds the token in DIGITALOCEAN_TOKEN by itself.
  • image is the operating system, Ubuntu 24.04. region is the data centre, nyc3 in New York. size is the plan from the cost section.
  • user_data is a startup script: commands the server runs once, by itself, the first time it boots. This one installs nginx and writes your page, the same apt and echo you'd type by hand.
  • <<-EOT and EOT mark a heredoc: a block of text over several lines, which ends at the line that says EOT. The - lets you indent the text to match your code.
  • ipv4_address is a value DigitalOcean only knows once the server exists, so both outputs say (known after apply) in the plan.

Create it

BASH
terraform init
terraform plan

The plan shows one Droplet to add, with most of its values (known after apply): one to add, nothing else. Read it, as you would any plan that's about to cost money. Then:

BASH
terraform apply

Type yes. This one takes longer than a file: Terraform asks DigitalOcean for the server, then waits while it's built, which usually takes about a minute. It reports progress while it waits, and then:

OUTPUT
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

ip_address = "203.0.113.10"
url = "http://203.0.113.10"

Your address will be different. Open the url in your browser. If you see an error or nginx's default page, wait a minute and refresh: the server exists, but its startup script is still installing nginx. Then you'll see Built by Terraform, served from a real server in New York, to anyone on the internet who has the address.

Open the DigitalOcean control panel too, under Droplets: terraform-practice-web is there. Terraform created it through the same API the control panel uses. (Because you didn't give the server an SSH key, DigitalOcean may email you a password for logging in to it. You won't need it in this lesson, and it stops working when the server is destroyed.)

From your computer to a real serverOn your computer, terraform apply reads the token from the DIGITALOCEAN_TOKEN environment variable and asks DigitalOcean's API for a Droplet. In the nyc3 data centre, the Droplet boots and runs its user_data startup script, which installs nginx and writes the page. Terraform reports the IP address as an output, and your browser loads the page from the Droplet. terraform destroy removes it, and the billing stops.your computerterraform applytoken from the environmentBrowsercreate a DropletDigitalOcean APIchecks the tokennyc3 data centreterraform-practice-webboots, runs user_data:installs nginx,writes the pagethe page, from the IP address in the url outputSame commands as before. The difference is it's real, it's public, and it's billed until you destroy it.
Terraform asks DigitalOcean for a server; the server sets itself up with its startup script, and your browser reaches it at the address Terraform reports.

In my real setup

CivicGrid runs on DigitalOcean too, in the same nyc3 region. Its Terraform creates a Kubernetes cluster rather than a single Droplet, with a 2 CPU, 4 GB node, which is this lesson's server's bigger cousin, but the shape is the same: a provider, a token, a region and a size. The token works the same way as yours, too: it's never in a file, it's marked sensitive, and it's given to Terraform as a setting, in HCP Terraform rather than my terminal, which Lesson 8 covers.

Break it on purpose

Try these with the server still running, then clean up at the end.

A change that replaces. Lesson 2 warned that some changes destroy and recreate. Here's one for real. Run:

BASH
terraform plan -var region=sfo3

The plan says digitalocean_droplet.web must be replaced, with # forces replacement next to the region, and Plan: 1 to add, 0 to change, 1 to destroy. A server can't be moved between data centres, so Terraform would destroy this one, and everything on it, and build a new one in San Francisco. Don't apply this. It's just a plan, so nothing has changed. This is exactly why you read plans: with a real server holding real data, that one line is the difference between a move and a loss.

No token. Remove the token from this terminal, and plan again:

BASH
unset DIGITALOCEAN_TOKEN
terraform plan

Terraform stops with an error from the DigitalOcean provider: it has no token, so it can't prove who you are, and DigitalOcean won't tell it anything. The exact wording depends on the provider version, but it will mention the token or authentication. Set the token again with the two read -s and export lines from step 2.

Forget to clean up. This one you do the right way instead. Destroy the server:

BASH
terraform destroy

Read the plan: Plan: 0 to add, 0 to change, 1 to destroy. Type yes, and wait for Destroy complete! Resources: 1 destroyed. Then check, because a command you ran isn't proof:

  1. Open the DigitalOcean control panel's Droplets page. terraform-practice-web should be gone.
  2. Run terraform state list. It should print nothing: Terraform isn't tracking anything here.
  3. Delete the access token, under API in the control panel, unless you're going straight on to Lesson 8. Its expiry would stop it anyway, but a token you don't need is a token someone could misuse.

Billing stopped the moment the server was destroyed. If the Droplets page is empty, you're done, and the whole lesson cost about a cent.

Where you are now

You've built a real, public server from code, kept the secret out of your files, read a plan that would have destroyed it, and cleaned up and checked. But your state file for it lived on your own computer, which is fine for one person and one laptop. Lesson 8 moves state somewhere safe and shared, so a team can work on the same infrastructure without stepping on each other.

Check yourself

  1. Why give Terraform the token through DIGITALOCEAN_TOKEN instead of writing it in main.tf?

  2. What does read -s do?

  3. When does DigitalOcean stop charging for the Droplet?

  4. You plan with -var region=sfo3. What does the plan show?

  5. Why might the page not load right after terraform apply finishes?

  6. You've finished the lesson. What's the last thing to do?

Key terms

  • DigitalOcean: a cloud provider with simple pricing, and the one CivicGrid runs on.
  • Droplet: DigitalOcean's name for a virtual server.
  • Access token: a secret string that lets a program use your cloud account, like a password.
  • read -s: reads what you type without showing it, for entering secrets.
  • Region: the data centre a resource lives in, like nyc3.
  • user_data: a startup script a server runs by itself the first time it boots.
  • Heredoc: a block of text over several lines, between <<-EOT and EOT.
  • Per-second billing: paying for exactly how long something exists, until it's destroyed.

Go deeper