/terraform-aws-serverless-static-wordpress

Terraform module for deploying Serverless Static Wordpress on AWS

Primary LanguageHCLGNU General Public License v3.0GPL-3.0

terraform-aws-serverless-static-wordpress

Test Suite follow on Twitter

Introduction

Serverless Static Wordpress is a Community Terraform Module from TechToSpeech that needs nothing more than a registered domain name with its DNS pointed at AWS.

It creates a complete infrastructure framework that allows you to launch a temporary, transient Wordpress container. You then log in and customize it like any Wordpress site, and finally publish it as a static site fronted by a global CloudFront CDN and S3 Origin. When you’re done you shut down the Wordpress container and it costs you almost nothing.

The emphasis is on extremely minimal configuration as the majority of everything you’d need is pre-installed and pre-configured in line with industry best practices and highly efficient running costs.

Architecture Overview

Architecture

Pre-requisites

  • A domain name either hosted with AWS, or with its DNS delegated to a Route53 hosted zone.
  • A VPC configured with at least one public subnet in your desired deployment region.
  • Desired deployment region cannot be one of the following, as Aurora Serverless v1 is not yet supported there:
    • Africa (Cape Town)
    • Asia Pacific (Hong Kong)
    • Asia Pacific (Osaka)
    • Europe (Milan)
    • Europe (Stockholm)
    • Middle East (Bahrain)
    • South America (São Paulo)
    • AWS GovCloud (US-East)
    • AWS GovCloud (US-West)
    • China (Beijing)
    • China (Ningxia)

Alternatives for Aurora Serverless will be supported in a future release.

Provider Set-up

Terraform best practice is to configure providers at the top-level module and pass them downwards through implicit inheritance or explicit passing. Whilst the module and child-modules reference required_providers, it is also necessary for you to provide a regional alias for operations that must be executed in us-east-1 (CloudFront, ACM, and WAF). As such you should include the following in your provider configuration:

terraform {
  required_version = "> 0.15.1"
  required_providers {
    aws = {
      source                = "hashicorp/aws"
      version               = "~> 3.0"
      configuration_aliases = [aws.ue1]
    }
  }
}

provider "aws" {
  alias   = "ue1"
  region  = "us-east-1"
}

The ue1 alias is essential for this module to work correctly.

Module instantiation example

locals {
  aws_account_id = "998877676554"
  aws_region     = "eu-west-1"
  site_name      = "peterdotcloud"
  profile        = "peterdotcloud"
  site_domain    = "peter.cloud"
}

data "aws_caller_identity" "current" {}

module "peterdotcloud_website" {
  source         = "TechToSpeech/serverless-static-wordpress/aws"
  version        = "0.1.0"
  main_vpc_id    = "vpc-e121c09b"
  subnet_ids     = ["subnet-04b97235","subnet-08fb235","subnet-04b97734"]
  aws_account_id = data.aws_caller_identity.current.account_id

  # site_name will be used to prepend resource names - use no spaces or special characters
  site_name           = local.site_name
  site_domain         = local.site_domain
  wordpress_subdomain = "wordpress"
  hosted_zone_id      = "Z00437553UWAVIRHANGCN"
  s3_region           = local.aws_region

  # Send ECS and RDS events to Slack
  slack_webhook       = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
  ecs_cpu             = 1024
  ecs_memory          = 2048
  cloudfront_aliases  = ["www.peter.cloud", "peter.cloud"]
  waf_enabled         = true

  # Provides the toggle to launch Wordpress container
  launch         = 0

  ## Passing in Provider block to module is essential
  providers = {
    aws.ue1 = aws.ue1
  }
}

Do not to set launch to 1 initially as the module uses a Codebuild pipeline to take a vanilla version of the Wordpress docker container and rebake it to include all of the pre-requisites required to publish the Wordpress site to S3.

The step to push the required Wordpress container from Dockerhub to your own ECR repository can be tied into your module instantiation using our helper module as follows:

Note this requires Docker to be running on your Terraform environment with either a named AWS profile or credentials otherwise available.

module "docker_pullpush" {
  source         = "TechToSpeech/ecr-mirror/aws"
  version        = "0.0.6"
  aws_account_id = data.aws_caller_identity.current.account_id
  aws_region     = local.aws_region
  docker_source  = "wordpress:php7.4-apache"
  aws_profile    = "peterdotcloud"
  ecr_repo_name  = module.peterdotcloud_website.wordpress_ecr_repository
  ecr_repo_tag   = "base"
  depends_on     = [module.peterdotcloud_website]
}

The CodeBuild pipeline takes a couple of minutes to run and pushes back a 'latest' tagged version of the container, which is what will be used for the Wordpress container. This build either needs to be triggered manually from the CodeBuild console, or you can use this snippet to trigger the build as part of your Terraform flow:

resource "null_resource" "trigger_build" {
  triggers = {
    codebuild_etag = module.peterdotcloud_website.codebuild_package_etag
  }
  provisioner "local-exec" {
    command = "aws codebuild start-build --project-name ${module.peterdotcloud_website.codebuild_project_name} --profile ${local.profile} --region ${local.aws_region}"
  }
  depends_on = [
    module.peterdotcloud_website, module.docker_pullpush
  ]
}

Whilst this might feel convoluted (and you might ask: why not just provide a public customized Docker image?), it was felt important that users should 'own' their own version of the Wordpress container, built transparently from the official Wordpress docker image with full provenance.

Finally, if you wish to fully automate the creation and update of the domain's nameservers if it's registered in Route53 within the same account, you can add these additional snippets to include this in your flow.

resource "aws_route53_zone" "apex" {
  name = "peter.cloud"
}

resource "null_resource" "update_nameservers" {
  triggers = {
    nameservers = aws_route53_zone.apex.id
  }
  provisioner "local-exec" {
    command = "aws route53domains update-domain-nameservers --region us-east-1 --domain-name ${local.site_domain} --nameservers Name=${aws_route53_zone.apex.name_servers.0} Name=${aws_route53_zone.apex.name_servers.1} Name=${aws_route53_zone.apex.name_servers.2} Name=${aws_route53_zone.apex.name_servers.3} --profile peterdotcloud"
  }
  depends_on = [aws_route53_zone.apex]
}

See examples for full set-up example.

Launching container, customize Wordpress and publish static site

Check that the CodeBuild job for the container has built successfully.

Toggle the launch value of the module to 1, and re-run Terraform plan/apply, which will launch the instance of the Wordpress management container.

First-time launch of container will take 5-6 minutes as the installation of Wordpress completes. You can check status if you wish in CloudWatch log groups for ECS. It will come up within a few seconds on subsequent launches.

The Wordpress management container will become available at http://wordpress.yourdomain.com (note HTTP, not HTTPS) by default, unless you specified your own wordpress_subdomain prefix.

Default admin is: supervisor Default password: techtospeech.com

Change these on first log in or specify your own in module instantiation.

You will find WP2Static with S3 Add-on installed. Go to the WP2Static Menu->Addons, and click the 'Disabled' button to Enable the Add-on.

The configuration of the plugin has been set up such that no additional configuration is required unless you wish to change any options.

You may now edit Wordpress as you would normally, customize your site as you like, and when ready proceed to the 'Run' section of the WP2Static plugin, and click the 'Generate Static Site' button. This will take some minutes depending on the size of your site. When complete the site will be published in S3, and available via the public URL configured in your module definition.

Gentle reminder that no backup options are currently bundled with this module - the most effective means would be to generate and retain a backup from within Wordpress for maximum flexibility. We recommend the UpdraftPlus plugin.

Troubleshooting

If you experience issues with the publish element of WP2Static, you can retry. It can be more reliable to proceed to 'Caches' section and select to delete all caches. Currently you need to additionally delete the S3 deploy cache manually.

You should also try increasing the CPU/Memory allocated to the container. Undersizing the container can cause timeout issues that are currently not well handled in the plugin.

If the job fails immediately and your site has previously generated a sitemaps.xml file, ensure you restore the plugin that generates this file and the crawl job can fail fast if it cannot locate it. For all other features and issues relating to WP2Static, raise an issue on their repo. For any issues relating to this module, raise an issue against this repo.

Inputs

Name Description Type Default Required
aws_account_id The AWS account ID into which resources will be launched. string n/a yes
cloudfront_aliases The domain and sub-domain aliases to use for the cloudfront distribution. list(any) [] no
cloudfront_class The price class for the distribution. One of: PriceClass_All, PriceClass_200, PriceClass_100 string "PriceClass_All" no
ecs_cpu The CPU limit password to the Wordpress container definition. number 256 no
ecs_memory The memory limit password to the Wordpress container definition. number 512 no
hosted_zone_id The Route53 HostedZone ID to use to create records in. string n/a yes
launch The number of tasks to launch of the Wordpress container. Used as a toggle to start/stop your Wordpress management session. number "0" no
main_vpc_id The VPC ID into which to launch resources. string n/a yes
s3_region The regional endpoint to use for the creation of the S3 bucket for published static wordpress site. string n/a yes
site_domain The site domain name to configure (without any subdomains such as 'www') string n/a yes
site_name The unique name for this instance of the module. Required to deploy multiple wordpress instances to the same AWS account (if desired). string n/a yes
site_prefix The subdomain prefix of the website domain. E.g. www string "www" no
slack_webhook The Slack webhook URL where ECS Cluster EventBridge notifications will be sent. string "" no
snapshot_identifier To create the RDS cluster from a previous snapshot in the same region, specify it by name. string null no
subnet_ids A list of subnet IDs within the specified VPC where resources will be launched. list(any) n/a yes
waf_acl_rules List of WAF rules to apply. Can be customized to apply others created outside of module. list(any)
[
{
"cloudwatch_metrics_enabled": true,
"managed_rule_group_name": "AWSManagedRulesAmazonIpReputationList",
"metric_name": "AWS-AWSManagedRulesAmazonIpReputationList",
"name": "AWS-AWSManagedRulesAmazonIpReputationList",
"priority": 0,
"sampled_requests_enabled": true,
"vendor_name": "AWS"
},
{
"cloudwatch_metrics_enabled": true,
"managed_rule_group_name": "AWSManagedRulesKnownBadInputsRuleSet",
"metric_name": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
"name": "AWS-AWSManagedRulesKnownBadInputsRuleSet",
"priority": 1,
"sampled_requests_enabled": true,
"vendor_name": "AWS"
},
{
"cloudwatch_metrics_enabled": true,
"managed_rule_group_name": "AWSManagedRulesBotControlRuleSet",
"metric_name": "AWS-AWSManagedRulesBotControlRuleSet",
"name": "AWS-AWSManagedRulesBotControlRuleSet",
"priority": 2,
"sampled_requests_enabled": true,
"vendor_name": "AWS"
}
]
no
waf_enabled Flag to enable default WAF configuration in front of CloudFront. bool n/a yes
wordpress_admin_email The email address of the default wordpress admin user. string "admin@example.com" no
wordpress_admin_password The password of the default wordpress admin user. string "techtospeech.com" no
wordpress_admin_user The username of the default wordpress admin user. string "supervisor" no
wordpress_subdomain The subdomain used for the Wordpress container. string "wordpress" no

Modules

Name Source Version
cloudfront ./modules/cloudfront n/a
codebuild ./modules/codebuild n/a
lambda_slack ./modules/lambda_slack n/a
waf ./modules/waf n/a

Outputs

Name Description
cloudfront_ssl_arn The ARN of the ACM certificate used by CloudFront.
codebuild_package_etag The etag of the codebuild package file.
codebuild_project_name The name of the created Wordpress codebuild project.
wordpress_ecr_repository The name of the ECR repository where wordpress image is stored.

Requirements

Name Version
terraform >= 0.15.1
aws ~> 3.0
random ~> 3.1.0

Resources

Name Type
aws_acm_certificate.wordpress_site resource
aws_acm_certificate_validation.wordpress_site resource
aws_cloudwatch_log_group.serverless_wordpress resource
aws_cloudwatch_log_group.wordpress_container resource
aws_db_subnet_group.main_vpc resource
aws_ecr_repository.serverless_wordpress resource
aws_ecs_cluster.wordpress_cluster resource
aws_ecs_service.wordpress_service resource
aws_ecs_task_definition.wordpress_container resource
aws_efs_access_point.wordpress_efs resource
aws_efs_file_system.wordpress_persistent resource
aws_efs_mount_target.wordpress_efs resource
aws_iam_policy.wordpress_bucket_access resource
aws_iam_role.wordpress_task resource
aws_iam_role_policy_attachment.wordpress_bucket_access resource
aws_iam_role_policy_attachment.wordpress_role_attachment_cloudwatch resource
aws_iam_role_policy_attachment.wordpress_role_attachment_ecs resource
aws_rds_cluster.serverless_wordpress resource
aws_route53_record.apex resource
aws_route53_record.wordpress_acm_validation resource
aws_route53_record.www resource
aws_security_group.aurora_serverless_group resource
aws_security_group.efs_security_group resource
aws_security_group.wordpress_security_group resource
aws_security_group_rule.aurora_sg_ingress_3306 resource
aws_security_group_rule.efs_ingress resource
aws_security_group_rule.wordpress_sg_egress_2049 resource
aws_security_group_rule.wordpress_sg_egress_3306 resource
aws_security_group_rule.wordpress_sg_egress_443 resource
aws_security_group_rule.wordpress_sg_egress_80 resource
aws_security_group_rule.wordpress_sg_ingress_80 resource
random_id.rds_snapshot resource
random_password.serverless_wordpress_password resource
aws_iam_policy_document.ecs_assume_role_policy data source
aws_iam_policy_document.wordpress_bucket_access data source