AWS Builder Center
 Zero to Hero: Automate installation of WordPress CMS with Terraform and Amazon Q

Zero to Hero: Automate installation of WordPress CMS with Terraform and Amazon Q

In this blog, we will see how to use prompts in Visual Studio Code to build a Terraform project with Amazon Q. We will also explore how to run Terraform init, Terraform plan, Terraform apply, and Terraform destroy commands. Additionally, we'll demonstrate how to install a WordPress Content Management System on an EC2 instance with RDS as the database.

Welcome to another installment of  Q-Bits , our regular series showcasing cool ways Amazon employees are leveraging Amazon Q Developer. Let's create a WordPress content management system on AWS using Terraform Infrastructure as Code (IaC) and Amazon Q. This guide assumes you have Visual Studio Code installed and have configured Amazon Q through your free Amazon Builder account. To install the Amazon Q Developer extension for your IDE, visit  https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-in-IDE-setup.html 
Open Amazon Q in your preferred IDE to ask your software development questions.
Amazon Q in Visual Studio IDE

Ask Amazon Q to help you build a three-tier WordPress CMS application using Terraform. Amazon Q will generate the necessary Terraform configuration files and provide step-by-step guidance.
Terraform files generated by Amazon Q

The following files were generated through additional prompts to Amazon Q, including a customized userdata.sh file. Store all files in a directory named 'deploy-three-tier-webapp-on-aws-using-terraform'.
  • providers.tf - Configures AWS as the cloud provider and sets up basic connectivity
  • variables.tf - Defines input variables used across the project
  • main.tf - Contains the core infrastructure code (VPC, subnets, etc.)
  • outputs.tf - Specifies what values should be shown after deployment
  • terraform.tfvars - Stores the actual values for variables (should be git-ignored if contains sensitive data)
  • .terraform.lock.hcl - Locks provider versions for consistent deployments
  • backend.tf - (Optional) Configures where Terraform stores its state file

→ Providers.tf

Think of this configuration file as establishing the connection between Terraform and AWS - similar to how you configure your phone's WiFi settings with a network name and password. When you set up a new Terraform project, this provider configuration is typically one of your first files because it creates the essential link that lets Terraform communicate with AWS.

Key points in this configuration:
  • The  provider  block specifies which AWS region to use
  • The  profile  parameter indicates which AWS credentials to use
  • The  required_providers  block ensures you use a compatible AWS provider version
YAML
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}

provider "aws" {
region = var.aws_region
profile = "default" # AWS credentials profile to use
}
Let's break it down into two main parts:
  1. The  terraform  block:
YAML
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
This  required_providers  block defines the essential tools for your Terraform configuration - similar to a shopping list for a specific project. Here's what each part specifies:
  • The AWS provider is your primary tool
  • The source  hashicorp/aws  indicates where Terraform downloads the provider
  • The version constraint  ~> 4.0  lets you use any 4.x version (for example, 4.1, 4.2, but not 5.0)
  1. The  provider  block:
YAML
provider "aws" {
region = var.aws_region
}
The  provider  block configures your AWS connection settings - similar to selecting a specific AWS region for your resources. This configuration uses a variable ( var.aws_region ) for the region value instead of a hardcoded string, which provides two key benefits:
  1. Flexibility: You can change the region by updating a single variable
  1. Reusability: You can use the same configuration across different environments

→ variables.tf

This file defines Terraform variables that can be used throughout the configuration. Variables make the code more flexible and reusable by allowing users to specify values dynamically rather than hardcoding them.
YAML
variable "aws_region" {
description = "AWS region"
type = string
default = "us-west-2"
}

variable "vpc_cidr" {
description = "CIDR for VPC"
type = string
default = "10.0.0.0/16"
}

variable "instance_type" {
description = "EC2 instance type"
type = string
default = "t2.micro"
}

variable "db_instance_class" {
description = "RDS instance class"
type = string
default = "db.t3.micro"
}

variable "db_name" {
description = "The name of the WordPress database"
type = string
default = "wordpress"
}

variable "db_username" {
description = "The username for the WordPress database"
type = string
default = "wordpress"
}

variable "db_password" {
description = "The password for the WordPress database"
type = string
sensitive = true
}

# Add this to variables.tf
variable "key_name" {
description = "Name of the EC2 key pair"
type = string
}

This variable defines the AWS Region where resources will be deployed, defaulting to us-west-2 if not provided, which Terraform will use automatically.
YAML
# VPC CIDR Block
variable "vpc_cidr" {
description = "CIDR for VPC"
type = string
default = "10.0.0.0/16"
}

Defines the IP address range for the Virtual Private Cloud (VPC) using a default CIDR block of 10.0.0.0/16, which provides up to 65,536 IP addresses.
YAML
# EC2 Instance Type:
variable "instance_type" {Deploying WP, RDS and ELB with the help of terraform4
description = "EC2 instance type"
type = string
default = "t2.micro"
Specifies the Amazon RDS instance class, with a default of db.t3.micro, which is a cost-effective instance type.
YAML
# RDS Database Instance Class:
variable "db_instance_class" {
description = "RDS instance class"
type = string
default = "db.t3.micro"
}

→ main.tf


YAML
# VPC Configuration
resource "aws_vpc" "wordpress_vpc" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "wordpress-vpc"
}
}

# Public Subnets
resource "aws_subnet" "public_subnet_1" {
vpc_id = aws_vpc.wordpress_vpc.id
cidr_block = "10.0.1.0/24"
availability_zone = "${var.aws_region}a"

tags = {
Name = "WordPress Public Subnet 1"
}
}

resource "aws_subnet" "public_subnet_2" {
vpc_id = aws_vpc.wordpress_vpc.id
cidr_block = "10.0.2.0/24"
availability_zone = "${var.aws_region}b"

tags = {
Name = "WordPress Public Subnet 2"
}
}

# Private Subnets for RDS
resource "aws_subnet" "private_subnet_1" {
vpc_id = aws_vpc.wordpress_vpc.id
cidr_block = "10.0.3.0/24"
availability_zone = "${var.aws_region}a"

tags = {
Name = "WordPress Private Subnet 1"
}
}

resource "aws_subnet" "private_subnet_2" {
vpc_id = aws_vpc.wordpress_vpc.id
cidr_block = "10.0.4.0/24"
availability_zone = "${var.aws_region}b"

tags = {
Name = "WordPress Private Subnet 2"
}
}

# Internet Gateway
resource "aws_internet_gateway" "wordpress_igw" {
vpc_id = aws_vpc.wordpress_vpc.id

tags = {
Name = "WordPress IGW"
}
}

# Route Table
resource "aws_route_table" "public_rt" {
vpc_id = aws_vpc.wordpress_vpc.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.wordpress_igw.id
}

tags = {
Name = "WordPress Public Route Table"
}
}

resource "aws_route_table_association" "public_1" {
subnet_id = aws_subnet.public_subnet_1.id
route_table_id = aws_route_table.public_rt.id
}

resource "aws_route_table_association" "public_2" {
subnet_id = aws_subnet.public_subnet_2.id
route_table_id = aws_route_table.public_rt.id
}

# Elastic IP for WordPress Server 1
resource "aws_eip" "wordpress_1_eip" {
instance = aws_instance.wordpress_1.id
vpc = true

tags = {
Name = "WordPress Server 1 EIP"
}
}

# Elastic IP for WordPress Server 2
resource "aws_eip" "wordpress_2_eip" {
instance = aws_instance.wordpress_2.id
vpc = true

tags = {
Name = "WordPress Server 2 EIP"
}
}

# Create template file for user data
data "template_file" "user_data" {
template = file("userdata.sh")

vars = {
db_name = var.db_name
db_username = var.db_username
db_password = var.db_password
db_endpoint = replace(aws_db_instance.wordpress.endpoint, ":3306", "")
}
}

# EC2 Instances
resource "aws_instance" "wordpress_1" {
ami = "ami-0606dd43116f5ed57" # Ubuntu 22.04 LTS
instance_type = var.instance_type
subnet_id = aws_subnet.public_subnet_1.id

associate_public_ip_address = true
key_name = var.key_name

vpc_security_group_ids = [aws_security_group.wordpress_sg.id]
user_data = data.template_file.user_data.rendered

tags = {
Name = "WordPress Server 1"
}

depends_on = [aws_db_instance.wordpress]
}

resource "aws_instance" "wordpress_2" {
ami = "ami-0606dd43116f5ed57" # Ubuntu 22.04 LTS
instance_type = var.instance_type
subnet_id = aws_subnet.public_subnet_2.id

associate_public_ip_address = true
key_name = var.key_name

vpc_security_group_ids = [aws_security_group.wordpress_sg.id]
user_data = data.template_file.user_data.rendered

tags = {
Name = "WordPress Server 2"
}

depends_on = [aws_db_instance.wordpress]
}

# RDS Subnet Group
resource "aws_db_subnet_group" "wordpress" {
name = "wordpress-subnet-group"
subnet_ids = [aws_subnet.private_subnet_1.id, aws_subnet.private_subnet_2.id]

tags = {
Name = "WordPress DB subnet group"
}
}

# RDS Instance
resource "aws_db_instance" "wordpress" {
identifier = "wordpress-db"
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "8.0"
instance_class = var.db_instance_class
db_name = var.db_name
username = var.db_username
password = var.db_password
skip_final_snapshot = true

vpc_security_group_ids = [aws_security_group.rds_sg.id]
db_subnet_group_name = aws_db_subnet_group.wordpress.name
}

# Create ALB
resource "aws_lb" "wordpress_alb" {
name = "wordpress-alb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = [aws_subnet.public_subnet_1.id, aws_subnet.public_subnet_2.id]
}

# Create ALB Target Group
resource "aws_lb_target_group" "wordpress_tg" {
name = "wordpress-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.wordpress_vpc.id

health_check {
path = "/"
healthy_threshold = 2
unhealthy_threshold = 10
}
}

# Create ALB Listener
resource "aws_lb_listener" "front_end" {
load_balancer_arn = aws_lb.wordpress_alb.arn
port = "80"
protocol = "HTTP"

default_action {
type = "forward"
target_group_arn = aws_lb_target_group.wordpress_tg.arn
}
}

# Attach EC2 instances to target group
resource "aws_lb_target_group_attachment" "wordpress_1" {
target_group_arn = aws_lb_target_group.wordpress_tg.arn
target_id = aws_instance.wordpress_1.id
port = 80
}

resource "aws_lb_target_group_attachment" "wordpress_2" {
target_group_arn = aws_lb_target_group.wordpress_tg.arn
target_id = aws_instance.wordpress_2.id
port = 80
}


→ security.tf


YAML
# Security Group for EC2 instances
resource "aws_security_group" "wordpress_sg" {
name = "wordpress-sg"
description = "Security group for WordPress servers"
vpc_id = aws_vpc.wordpress_vpc.id

ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

ingress {
from_port = 22
to_port = 22
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"]
}
}

# Security Group for RDS
resource "aws_security_group" "rds_sg" {
name = "wordpress-rds-sg"
description = "Security group for WordPress RDS"
vpc_id = aws_vpc.wordpress_vpc.id

ingress {
from_port = 3306
to_port = 3306
protocol = "tcp"
security_groups = [aws_security_group.wordpress_sg.id]
}

egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}

# Security Group for ALB
resource "aws_security_group" "alb_sg" {
name = "wordpress-alb-sg"
description = "Security group for WordPress ALB"
vpc_id = aws_vpc.wordpress_vpc.id

# Allow HTTP from anywhere
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

# Allow HTTPS from anywhere
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}

# Allow all outbound traffic
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}

tags = {
Name = "WordPress-ALB-SecurityGroup"
}
}

# Update EC2 security group to allow traffic from ALB
resource "aws_security_group_rule" "allow_alb" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
source_security_group_id = aws_security_group.alb_sg.id
security_group_id = aws_security_group.wordpress_sg.id
}

→ outputs.tf


YAML
output "wordpress_server_1_public_ip" {
value = aws_eip.wordpress_1_eip.public_ip
}

output "wordpress_server_2_public_ip" {
value = aws_eip.wordpress_2_eip.public_ip
}

output "wordpress_server_1_public_dns" {
value = aws_eip.wordpress_1_eip.public_dns
}

output "wordpress_server_2_public_dns" {
value = aws_eip.wordpress_2_eip.public_dns
}

output "rds_endpoint" {
value = aws_db_instance.wordpress.endpoint
}

output "alb_dns_name" {
value = aws_lb.wordpress_alb.dns_name
}

→ userdata.sh



YAML
#!/bin/bash
sudo apt-get update -y
sudo apt-get install -y apache2 php php-mysql mysql-client

# Start and enable Apache2
sudo systemctl start apache2
sudo systemctl enable apache2

# Download and configure WordPress
cd /tmp
sudo wget https://wordpress.org/latest.tar.gz
sudo tar -xzf latest.tar.gz
sudo cp -r wordpress/* /var/www/html/
sudo rm -rf wordpress latest.tar.gz

# Set correct permissions
sudo chown -R www-data:www-data /var/www/html/
sudo chmod -R 755 /var/www/html/

# Configure wp-config.php
sudo cp /var/www/html/wp-config-sample.php /var/www/html/wp-config.php

# Update WordPress configuration
sudo sed -i "s/database_name_here/${db_name}/" /var/www/html/wp-config.php
sudo sed -i "s/username_here/${db_username}/" /var/www/html/wp-config.php
sudo sed -i "s/password_here/${db_password}/" /var/www/html/wp-config.php
sudo sed -i "s/localhost/${db_endpoint}/" /var/www/html/wp-config.php

# Remove default Apache index.html
sudo rm -f /var/www/html/index.html

# Restart Apache2
sudo systemctl restart apache2

→ terraforms.tfvars


JSON
aws_region = "us-west-2"
db_name = "wordpress"
db_username = "wordpress"
db_password = "Admin_123"
# Change this to a secure password
key_name = "udemycoursekeypair"
# Requesting you to create a Key Pair in same region as mentioned in Line 1 else whole stack will fail

How to Deploy a WordPress Instance Once All the Files Are Kept Handy?

Here are the key commands and their explanations. We are going to run  terraform init , followed by  terraform plan  and  terraform apply  in sequence.
  1.  terraform init 
  • This is always the first command you run in a new or existing Terraform working directory
  • Initializes the working directory
  • Downloads required providers and modules
  • Sets up the backend for storing state files
  1.  terraform plan 
  • Shows what changes Terraform will make to your infrastructure
  • Creates an execution plan without actually applying changes
  • Good practice to review changes before applying them
  • Optional: Use  terraform plan -out=plan.tf  to save the plan to a file
  1.  terraform apply 
  • Applies the changes to reach the desired state
  • Creates, updates, or deletes infrastructure resources
  • Will show a plan and ask for confirmation before making changes
  • To automatically approve:  terraform apply -auto-approve 
  • To apply a saved plan:  terraform apply plan.tf 
  1.  terraform destroy 
  • Destroys all resources managed by your Terraform configuration
  • Use with caution as this will delete resources
  • Will ask for confirmation before destroying. To automatically approve:  terraform destroy -auto-approve 
Watch the video demonstration of executing Terraform commands and installing WordPress on AWS.


Conclusion

Thank you for completing this tutorial. After completing this demonstration, you can remove all created resources to avoid unnecessary costs. Important: This command permanently deletes resources defined in your Terraform configuration. Make sure you're in the correct working directory and environment before proceeding. To decommission the resources, run:
JSON
terraform destroy
Before we conclude, you  can access all the files in GitHub  here. Please ensure you run these Terraform files in your development or staging environment, adhere to best coding guidelines, and perform a security check. Happy building WordPress CMS on AWS with Terraform and Amazon Q! If this blog helped you leverage Amazon Q, please hit like and feel free to let us know in the comments. Thank you!  
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article