portfolio /devops/infrastructure as code
MAY 2026

Infrastructure as Code
with Terraform & Ansible

Provision cloud infrastructure with Terraform. Configure it with Ansible. Two tools, one clean automated workflow — from zero to a live web server without touching a server by hand.

Terraform Ansible AWS EC2 AWS S3 IAM Nginx Amazon Linux 2023 IaC Configuration Management
7 AWS Resources
3 Ansible Tasks
2 Deploys
0 Manual Server Touches
01

Architecture

Terraform and Ansible address two distinct layers of the infrastructure problem. Terraform talks to AWS APIs and creates resources. Ansible connects to those resources over SSH and configures what runs on them. The split is intentional — provisioning and configuration management are different problems that benefit from different tools.

// PROVISIONING LAYER — Terraform

terraform apply
  │
  ├── aws_key_pair          → uploads SSH public key to AWS
  ├── aws_security_group    → port 22 (SSH) + port 80 (HTTP) inbound
  ├── aws_iam_role          → EC2 service role
  ├── aws_iam_role_policy_attachment  → AmazonS3ReadOnlyAccess
  ├── aws_iam_instance_profile  → wraps role for EC2 attachment
  ├── aws_s3_bucket         → object storage (force_destroy enabled)
  └── aws_instance          → EC2 t2.micro, Amazon Linux 2023
        │
        └── outputs: ec2_public_ip, web_url, s3_bucket_name

// CONFIGURATION LAYER — Ansible

ansible-playbook -i inventory.ini playbook.yml
  │
  ├── Install Nginx         → dnf package manager (AL2023)
  ├── Deploy index.html     → copy module → /usr/share/nginx/html/
  └── Start + enable Nginx  → service module, survives reboots
        │
        └── http://<EC2_PUBLIC_IP>  ← live web page
Why separate provisioning from configuration management? Terraform creates infrastructure and tracks state — it knows what it built and can calculate exactly what needs to change on the next run. Ansible configures what's already running — idempotent by design, it only changes what needs changing. Using one tool to do both creates fragile, hard-to-debug configurations. Using each tool for what it's actually designed for keeps the workflow clean and predictable.
02

What I Built

01
terraform variables outputs

Terraform Configuration — Three-File Structure

Split across variables.tf, main.tf, and outputs.tf following standard Terraform practice. Variables centralize configurable inputs — region, AMI ID, instance type, key name, bucket name — so the full infrastructure can be redeployed to a different region by editing a single file. Outputs expose the EC2 public IP and web URL after apply, which feeds directly into Ansible's inventory.

02
ec2 iam security group

EC2 Instance with IAM Role and Security Group

Provisioned a t2.micro EC2 instance on Amazon Linux 2023 with a dedicated IAM role granting S3 read access via instance profile — no static credentials embedded anywhere. The security group allows inbound SSH on port 22 for Ansible connectivity and HTTP on port 80 for the web server, with unrestricted outbound. SSH key pair is provisioned by Terraform from the local public key, so no manual key upload required.

03
s3 iam policy

S3 Bucket with IAM Policy Attachment

S3 bucket provisioned alongside EC2 in a single Terraform config, demonstrating multi-resource IaC management. The EC2 instance's IAM role has the AWS-managed AmazonS3ReadOnlyAccess policy attached via aws_iam_role_policy_attachment, giving the instance read access to the bucket without any embedded credentials. force_destroy = true is set so terraform destroy cleans up the bucket even if it contains objects.

04
ansible nginx idempotent

Ansible Playbook — Nginx Install and Web Deployment

Three-task playbook running against the EC2 instance with become: yes. Uses the dnf module for package management — Amazon Linux 2023 replaced yum with dnf, a distinction that matters in practice. The copy module deploys the HTML page directly to the Nginx web root. The service module starts Nginx and enables it on boot. Idempotency confirmed — a second playbook run after an HTML correction showed changed=1 on only the modified file.

03

Problems I Solved

ISSUE Wrong region — resources provisioned in us-east-1 instead of us-east-2

The initial terraform apply completed successfully but all 7 resources landed in us-east-1. The AMI ID ami-0c02fb55956c7d316 is region-specific and only valid in us-east-1, making it unusable in the target region. The EC2 instance also wasn't visible in the AWS Console because EC2 is a regional service — the console only shows instances in the currently selected region. The S3 bucket did appear because S3 is a global service that shows all buckets regardless of region.

RESOLVED

Ran terraform destroy, updated variables.tf with the correct region (us-east-2) and the correct Amazon Linux 2023 AMI for that region (ami-0acc7a0cdd879d0f2). AMI IDs are not portable across regions — the same OS image has a different ID in every region and must be verified before deployment.

ISSUE Terraform hung for 14 minutes on S3 bucket creation — global namespace conflict

During the second terraform apply, all 6 other resources provisioned cleanly but the S3 bucket creation hung indefinitely. The previous bucket from the us-east-1 run was still in a releasing state after destroy. S3 bucket names are globally unique across all AWS accounts worldwide — not just within your own account or region. A name that's claimed anywhere, even temporarily, cannot be used again until it fully releases.

RESOLVED

Cancelled the apply with Ctrl+C and renamed the bucket from tc3-webapp-bucket-leyder-ben to tc3-webapp-bucket-ben-leyder. The name change cleared the global namespace conflict and the bucket created immediately on reapply. Unlike EC2 or most AWS resources where naming conflicts are scoped to a single account, S3 names are a shared global namespace — if Terraform hangs specifically on S3 creation with no error, name conflict is the first thing to check.

ISSUE Ansible returned "Empty playbook, nothing to do"

After updating the Terraform variables, a sed command was run to swap yum for dnf in the playbook. The command wiped the file instead of doing a clean in-place substitution, leaving it empty. Ansible returned "Empty playbook, nothing to do" with no other error.

RESOLVED

Rewrote the playbook directly using cat with a heredoc. For files containing special characters or complex formatting, writing directly with cat or nano is more reliable than sed substitution. Also confirmed that Amazon Linux 2023 uses dnf — not yum — as the package manager. The Ansible yum module will still work on AL2023 as a compatibility alias, but using dnf explicitly is the correct practice.

04

Terraform vs Ansible — Distinct Roles

These tools are often grouped together as "IaC" but they solve fundamentally different problems. Understanding the distinction matters for building maintainable infrastructure.

TERRAFORM ANSIBLE
Primary role Infrastructure provisioning Configuration management
What it manages Cloud resources (EC2, S3, VPC, IAM) Software and config on running servers
State tracking Yes — maintains tfstate, plans diffs No — checks current state on each run
Idempotency Via state file and plan Built-in — only changes what needs changing
Connection method AWS API calls SSH into running instances
Language HCL (HashiCorp Configuration Language) YAML playbooks
Best used for Spinning up the environment Configuring what runs inside it
05

Skills Demonstrated

// Terraform & IaC

  • Multi-resource Terraform configuration
  • variables.tf / outputs.tf separation
  • AWS provider configuration
  • terraform init, validate, plan, apply, destroy
  • State file management and .gitignore hygiene
  • force_destroy for clean teardown

// Ansible

  • Playbook authoring with become: yes
  • dnf module (Amazon Linux 2023)
  • copy module for file deployment
  • service module for process management
  • inventory.ini SSH configuration
  • Idempotency verification

// AWS

  • EC2 provisioning with IAM instance profile
  • S3 bucket management
  • IAM roles and policy attachments
  • Security groups (inbound/outbound rules)
  • SSH key pair provisioning via Terraform
  • Regional vs. global service awareness

// Process & Tooling

  • SSH key-based Git authentication
  • Version control from project start
  • AMI ID region verification workflow
  • S3 global namespace conflict resolution
  • Clean infrastructure teardown
  • Structured troubleshooting documentation