linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/lessons/tf-beginner-01-hello-s3

lesson ── terraform-beginner ── ~12 мин ── 5 шагов

Hello, S3: your first resource in Terraform

In the next 10 minutes you will run the core Terraform cycle from end to end: write the HCL for a single S3 bucket, initialize the working directory, read the plan, apply it, and confirm that state knows about the resource you created.

The cloud here is LocalStack (see localstack-provider). No real AWS, no bills. You are free to break things, tear them down, and start over.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Describe an S3 bucket in HCL

    The working directory ~/tf-hello already contains a provider.tf file, the AWS provider settings pointed at LocalStack (endpoints { ... }). Your job now is to create a main.tf next to it that describes a single S3 bucket.

    Create the file ~/tf-hello/main.tf with the following contents:

    hcl
    resource "aws_s3_bucket" "demo" {
      bucket = "linuxlab-hello-${random_id.suffix.hex}"
      tags = {
        Owner   = "student"
        Project = "terraform-hello"
      }
    }
    resource "random_id" "suffix" {
      byte_length = 4
    }

    Why random_id? S3 bucket names have to be globally unique. We keep that discipline even in LocalStack, so the habit stays good.

    For the shape of resource "..." "..." { ... } blocks, see tf-resource-block and hcl-syntax.

    подсказка

    You can use `cat > main.tf <<EOF ... EOF` or the `nano main.tf` editor.

    ✓ The HCL is written. Now Terraform needs to be initialized.

  2. 02

    terraform init: download the AWS provider

    The init command reads provider.tf and downloads the AWS plugin from the Terraform registry. After it runs you will have:

    • the .terraform/ directory, with the plugin inside.
    • the .terraform.lock.hcl file, with the plugin version pinned.

    Without init the other commands will not run.

    Run:

    bash
    cd /home/student/tf-hello
    terraform init

    For more on init and the lockfile, see tf-init and tf-lockfile.

    подсказка

    Remember to `cd /home/student/tf-hello` before `terraform init`.

    ✓ The AWS provider is downloaded, the lockfile is created.

    What is inside .terraform.lock.hcl

    The lockfile is your guarantee of reproducibility. It records the exact provider version and its SHA hashes. If you or a colleague run init again, Terraform downloads exactly that version. If a hash does not match, it fails with an error (protection against a swapped package).

    • → Terraform lockfile
  3. 03

    terraform plan: see what will be created

    plan compares HCL with state. State is empty right now, so plan will show "+ 2 to add": one aws_s3_bucket and one random_id.

    Run:

    bash
    terraform plan

    Look at the lines with + resource, those are the resources to be created. At the end you get a summary like Plan: 2 to add, 0 to change, 0 to destroy.

    Plan changes nothing in the cloud. You can run it as many times as you like. See tf-plan.

    подсказка

    If plan complains "provider not initialized": go back and check that `init` ran.

    ✓ Plan showed the plan: 2 resources to create. Now let's apply it.

  4. 04

    terraform apply: create the resource

    apply runs the plan against a real cloud. Here the "cloud" is LocalStack: a container next door that pretends to be AWS.

    Run it with -auto-approve (no interactive "yes", this is a practice task):

    bash
    terraform apply -auto-approve

    After apply you should see lines like:

    random_id.suffix: Creation complete after Xs
    aws_s3_bucket.demo: Creation complete after Xs
    Apply complete! Resources: 2 added

    If something fails, check that LocalStack is responding (the wait-for-localstack.sh init script should have waited for it when the session started). See tf-apply and localstack-provider.

    подсказка

    If you get a TLS error: the provider is trying to reach real AWS. Check that `provider.tf` has the endpoints block.

    ✓ The bucket is created in LocalStack. State is updated. Open it and take a look.

    The same thing on OpenTofu

    OpenTofu keeps its CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On your first switch, though, back up state and run on a feature branch, since the differences cluster in the newer features (variables in the backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  5. 05

    A second plan: it should be clean

    The main rule of Terraform: after apply a second plan should show "No changes". That is an invariant. If it shows changes, then state and HCL disagree, or there is drift.

    Run it once more:

    bash
    terraform plan -detailed-exitcode

    The -detailed-exitcode flag matters: exit 0 = no changes, exit 2 = there are some. This check passes only if the plan is clean.

    подсказка

    If plan shows changes: look at `terraform show -json | jq` and compare it with your HCL.

    ✓ The plan is clean: state and HCL match. The Terraform invariant holds.

    What drift is

    Drift is when the state in the cloud differs from what Terraform recorded in state. It happens: someone deleted the bucket by hand through the AWS console, or auto-scaling adjusted the capacity on its own. Terraform sees the drift on the next plan or refresh. The strategies for handling it live in lifecycle.ignore_changes.

    • → Terraform state
    • → lifecycle block

Что ты узнал

You saw the declarative approach of Terraform: you described the desired state, and Terraform worked out what to create. You met the three required commands (init, plan, apply) and learned what each one does.

команды

  • terraform initdownload providers and create the lockfile
  • terraform planshow the diff between HCL and state
  • terraform apply -auto-approveapply the plan
  • terraform showprint the contents of state

концепции

  • · HCL is the desired state, not a sequence of commands
  • · init is needed once per project (and after you change the provider or backend)
  • · plan is safe to run, you can run it as many times as you like
  • · apply actually changes the cloud and updates state

← предыдущий

Terragrunt, DRY across dev/stage/prod

следующий →

Troubleshooting Garden: untangle the Cycle Error

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies