Infrastructure as Code (IaC) means defining your servers, networks, databases, and all cloud resources in version-controlled configuration files — not clicking through web consoles.
# main.tf — Deploy a web app on AWS
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
# Store state remotely (never in git!)
backend "s3" {
bucket = "myapp-terraform-state"
key = "prod/terraform.tfstate"
region = "eu-west-1"
}
}
provider "aws" {
region = var.region
}
# ─── VPC ──────────────────────────────────────────────────
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.0"
name = "${var.app_name}-vpc"
cidr = "10.0.0.0/16"
azs = ["${var.region}a", "${var.region}b"]
public_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
private_subnets = ["10.0.10.0/24", "10.0.11.0/24"]
enable_nat_gateway = true
single_nat_gateway = true # Cost saving for non-prod
}
# ─── Database ─────────────────────────────────────────────
resource "aws_db_instance" "main" {
identifier = "${var.app_name}-db"
engine = "postgres"
engine_version = "16"
instance_class = "db.t3.micro"
allocated_storage = 20
storage_encrypted = true
db_name = var.app_name
username = "admin"
password = var.db_password # From secrets, never hardcoded
vpc_security_group_ids = [aws_security_group.db.id]
db_subnet_group_name = aws_db_subnet_group.main.name
skip_final_snapshot = false
final_snapshot_identifier = "${var.app_name}-final-snapshot"
backup_retention_period = 7
multi_az = var.environment == "prod" ? true : false
}
# ─── EC2 Instance ─────────────────────────────────────────
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
subnet_id = module.vpc.public_subnets[0]
vpc_security_group_ids = [aws_security_group.web.id]
key_name = aws_key_pair.deploy.key_name
user_data = templatefile("${path.module}/userdata.sh", {
app_name = var.app_name
db_host = aws_db_instance.main.endpoint
})
tags = {
Name = "${var.app_name}-web"
Environment = var.environment
}
}
# ─── Variables ────────────────────────────────────────────
# variables.tf
variable "app_name" { default = "myapp" }
variable "region" { default = "eu-west-1" }
variable "environment" { default = "prod" }
variable "instance_type" { default = "t3.small" }
variable "db_password" { sensitive = true }
# Terraform workflow:
terraform init # Download providers, initialize backend
terraform plan # Preview changes (ALWAYS review this)
terraform apply # Apply changes (creates/modifies resources)
terraform destroy # Tear down everything (careful!)
Terraform creates infrastructure. Ansible configures it (installs software, deploys apps, manages configs).
# playbook.yml — Configure a web server
---
- name: Configure web server
hosts: webservers
become: yes
vars:
app_name: myapp
app_port: 3000
node_version: "20"
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install required packages
apt:
name: [nginx, certbot, python3-certbot-nginx, ufw]
state: present
- name: Configure UFW firewall
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: ['22', '80', '443']
- name: Enable UFW
ufw:
state: enabled
policy: deny
- name: Install Node.js
shell: |
curl -fsSL https://deb.nodesource.com/setup_{{ node_version }}.x | bash -
apt-get install -y nodejs
args:
creates: /usr/bin/node
- name: Deploy application
git:
repo: "https://github.com/you/{{ app_name }}.git"
dest: "/opt/{{ app_name }}"
version: main
notify: restart app
- name: Install dependencies
npm:
path: "/opt/{{ app_name }}"
production: yes
- name: Create systemd service
template:
src: templates/app.service.j2
dest: "/etc/systemd/system/{{ app_name }}.service"
notify: restart app
- name: Configure Nginx
template:
src: templates/nginx.conf.j2
dest: "/etc/nginx/sites-available/{{ app_name }}"
notify: reload nginx
handlers:
- name: restart app
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
- name: reload nginx
systemd:
name: nginx
state: reloaded
# Run Ansible:
ansible-playbook -i inventory.yml playbook.yml
# inventory.yml
webservers:
hosts:
web1:
ansible_host: 93.184.216.34
ansible_user: deploy