Template Catalog
L2IaCIntermediate

Terraform EC2 + Security Group + Docker

Declare your infrastructure instead of clicking it.

Overview

Now the whole environment becomes code. Terraform creates the EC2 instance, a least-privilege security group, an IAM role, a key pair, and basic networking — all reproducible with `terraform apply` and destroyable with `terraform destroy`. This is the jump from 'a server I built' to 'infrastructure I can recreate anywhere'.

Architecture

Terraform CLI
Terraform state
IAM role
Security group
EC2 instance
Docker container

Terraform talks to the AWS API to create the security group, IAM role, key pair, and EC2 instance. The instance still bootstraps Docker via user-data, but every resource now lives in code and state.

  1. 1You run `terraform plan` to preview exactly what will change.
  2. 2`terraform apply` creates the SG, IAM role, key pair, and EC2 instance.
  3. 3User-data installs Docker and runs the container on boot.
  4. 4`terraform destroy` removes every resource when you're done.

Terraform CLI calls the AWS API to provision a security group, IAM role, key pair, and an EC2 instance running a Docker container; Terraform state tracks all of it.

What you'll understand

  • Express infrastructure as declarative, version-controlled code.
  • Understand Terraform state — what it is and why it matters.
  • Create a scoped security group and an IAM role instead of clicking in the console.
  • Tear everything down cleanly with a single command.

Prerequisites

Generated files

The files this template produces. Copy any of them straight into your project.

4 files

Defines the security group, IAM role, and EC2 instance.

main.tf
hcl
terraform {
  required_version = ">= 1.6"
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

provider "aws" {
  region = var.region
}

# Scoped security group: SSH from your IP, HTTP/HTTPS from anywhere.
resource "aws_security_group" "web" {
  name        = "${var.name}-sg"
  description = "Web + restricted SSH"

  ingress {
    description = "SSH from operator"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.operator_cidr]
  }
  ingress {
    description = "HTTP"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = { Project = var.name }
}

resource "aws_instance" "web" {
  ami                    = var.ami_id
  instance_type          = "t3.micro"
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.web.id]
  iam_instance_profile   = aws_iam_instance_profile.web.name

  user_data = <<-EOF
    #!/usr/bin/env bash
    dnf install -y docker && systemctl enable --now docker
    docker run -d --name web -p 80:80 --restart unless-stopped nginx:1.27-alpine
  EOF

  tags = { Name = var.name, Project = var.name }
}

Step-by-step guide

  1. 1

    Initialize Terraform

    Download the AWS provider and set up the working directory.

    terraform init

    Pulls the AWS provider plugin.

  2. 2

    Preview the plan

    Always look before you leap. `plan` shows every resource Terraform will create — nothing is applied yet.

    terraform plan -var operator_cidr="$(curl -s ifconfig.me)/32"

    Scopes SSH to your current public IP automatically.

  3. 3

    Apply

    Create the security group, IAM role, and instance. Terraform records everything in its state file.

    terraform apply

    Type 'yes' to confirm the plan.

    The state file describes your real infrastructure — keep it safe (Level 9 moves it to a remote backend).

  4. 4

    Reach the app

    Open the public_ip output in a browser. The same box as before — but now it's fully described by code.

  5. 5

    Destroy it

    Prove the value of IaC: tear the whole thing down in one command, then recreate it any time.

    terraform destroy

    Removes every resource it created.

AI insight

Ask the assistant to explain, review, or recommend — authored for this template.

AI insightAuthored

What this architecture is doing

Terraform is now the thing that talks to AWS. From a few .tf files it creates the security group, an IAM role, a key pair, and the EC2 instance, and it remembers what it made in a state file so it can update or destroy it precisely.

  • Declarative: you describe the end state, Terraform makes it so.
  • State = Terraform's memory of your real resources.
  • plan shows the diff before anything changes.

Security notes

  • SSH is now scoped to your IP

    Info

    The security group only allows SSH from operator_cidr, not the whole internet.

  • IAM role enables keyless access

    Info

    Attaching AmazonSSMManagedInstanceCore lets you use Session Manager instead of SSH keys.

  • HTTP still open to the world

    Low

    Port 80 is public (expected for a web app), but there's no TLS in this minimal config.

    Reuse the Nginx + Certbot pattern from Level 1, or terminate TLS at a load balancer later.

Cost notes

Low cost~$8/mo~$0.01/hr if left running · free-tier eligible
  • EC2 t3.micro

    Same instance cost as earlier levels; Terraform doesn't add charges, it just manages them.

  • Leaving it applied

    IaC makes it easy to forget a running apply. Destroy when you're done to avoid a surprise bill.

Cleanup guide

Tear it down when you're done — the fastest way to avoid a surprise bill.

  1. 1

    Run terraform destroy — this is the whole point of IaC.

    Billing
    terraform destroy
  2. 2

    Confirm no leftover key pairs or Elastic IPs remain in the console.

Troubleshooting

Where to go next