Skip to content

Instantly share code, notes, and snippets.

@gunzip
Last active April 30, 2023 09:08

Revisions

  1. gunzip revised this gist Apr 30, 2023. 1 changed file with 506 additions and 49 deletions.
    555 changes: 506 additions & 49 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -1,79 +1,536 @@
    # Uncomment the following to use S3 as a backend for Terraform state.
    # You will need to create the S3 bucket and DynamoDB table first.
    # terraform {
    # backend "s3" {
    # bucket = "devportal-terraform-state"
    # key = "terraform.tfstate"
    # region = "eu-west-1"
    # dynamodb_table = "terraform-state-lock"
    #  kms_key_id = "alias/terraform-bucket-key"
    # encrypt = true
    # }
    # }
    # Create the whole infrastructure to run the Strapi CMS webserver
    # and the static frontend that uses nextjs.

    # Terraform provider configuration

    resource "aws_kms_key" "terraform_bucket_key" {
    description = "This key is used to encrypt bucket objects"
    deletion_window_in_days = 30
    enable_key_rotation = true
    variable "db_name" {
    description = "The name of the database"
    default = "my_database"
    }

    resource "aws_kms_alias" "key_alias" {
    name = "alias/terraform-bucket-key"
    target_key_id = aws_kms_key.terraform-bucket-key.key_id
    variable "db_username" {
    description = "The username for the database"
    default = "my_username"
    }

    resource "aws_s3_bucket" "tf_state_bucket" {
    bucket = "devportal-terraform-state"
    acl = "private"
    resource "random_password" "db_password" {
    length = 16
    special = true
    }

    lifecycle {
    prevent_destroy = true
    variable "db_password" {
    description = "The password for the database"
    default = random_password.db_password.result
    }

    ####################################

    resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16"

    tags = {
    Name = "vpc"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_subnet" "strapi_subnet" {
    vpc_id = aws_vpc.vpc.id
    cidr_block = "10.0.1.0/24"

    tags = {
    Name = "strapi-subnet"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_db_subnet_group" "strapi_subnet_group" {
    name = "strapi-subnet-group"
    subnet_ids = [aws_subnet.strapi_subnet.id]
    description = "Strapi subnet group"
    tags = {
    ResourceGroup = "devportal"
    }
    }

    ############

    # Define a CloudWatch Logs log group for the ECS task
    resource "aws_cloudwatch_log_group" "strapi_logs" {
    name = "/ecs/strapi-logs"
    retention_in_days = 7
    }

    resource "aws_ecs_cluster" "strapi_cluster" {
    name = "strapi-cluster"
    }

    # Create an IAM policy to allow ECS tasks to write logs to CloudWatch
    resource "aws_iam_policy" "ecs_task_logging_policy" {
    name = "ecs-task-logging-policy"

    policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Action = [
    "logs:CreateLogStream",
    "logs:PutLogEvents"
    ]
    Resource = "arn:aws:logs:*:*:*"
    }
    ]
    })
    }

    # Create an IAM role for ECS task execution
    resource "aws_iam_role" "ecs_task_execution_role" {
    name = "ecs-task-execution-role"

    assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Principal = {
    Service = "ecs-tasks.amazonaws.com"
    }
    Action = "sts:AssumeRole"
    }
    ]
    })

    # Attach the task execution role to the logging policy
    policy = aws_iam_policy.ecs_task_logging_policy.arn
    }

    # This allows traffic from the ALB to the ECS service
    # and from the ECS service to the database
    resource "aws_security_group" "ecs_security_group" {
    name_prefix = "ecs-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 0
    to_port = 65535
    protocol = "tcp"
    cidr_blocks = [
    aws_vpc.vpc.cidr_block,
    ]
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # This defines the container that will run in the ECS service
    resource "aws_ecs_task_definition" "strapi_task" {
    family = "strapi-task"
    execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
    network_mode = "awsvpc"
    requires_compatibilities = ["FARGATE"]

    server_side_encryption_configuration {
    rule {
    apply_server_side_encryption_by_default {
    kms_master_key_id = aws_kms_key.terraform_bucket_key.arn
    sse_algorithm = "aws:kms"
    container_definitions = jsonencode([
    {
    name = "strapi-container"
    image = "docker.pkg.github.com/OWNER/REPO/IMAGE:TAG"
    portMappings = [
    {
    containerPort = 1337
    protocol = "tcp"
    }
    ]
    environment = [
    {
    name = "DATABASE_URL"
    value = format("postgres://%s:%s@%s/%s",
    aws_db_instance.strapi_db.username,
    aws_db_instance.strapi_db.password,
    aws_db_instance.strapi_db.endpoint,
    aws_db_instance.strapi_db.db_name)
    }
    ]
    logConfiguration = {
    logDriver = "awslogs"
    options = {
    awslogs-group = aws_cloudwatch_log_group.strapi_logs.name
    awslogs-region = "eu-west-1"
    awslogs-stream-prefix = "ecs"
    }
    }
    }
    ])
    }

    resource "aws_ecs_service" "strapi_service" {
    name = "strapi-service"
    cluster = aws_ecs_cluster.strapi_cluster.id
    task_definition = aws_ecs_task_definition.strapi_task.arn
    launch_type = "FARGATE"
    desired_count = 1

    network_configuration {
    assign_public_ip = true
    security_groups = [aws_security_group.ecs_security_group.id]
    subnets = [
    aws_subnet.strapi_subnet.id
    ]
    }

    load_balancer {
    target_group_arn = aws_lb_target_group.strapi_target_group.arn
    container_name = "strapi-container"
    container_port = 1337
    }
    }

    resource "aws_s3_bucket_versioning" "tf_state_bucket_versioning" {
    bucket = aws_s3_bucket.tf_state_bucket.id
    resource "aws_lb_target_group" "strapi_target_group" {
    name_prefix = "strapi-target-group"
    port = 1337
    protocol = "HTTP"
    target_type = "ip"
    vpc_id = aws_vpc.vpc.id

    versioning_configuration {
    status = "Enabled"
    health_check {
    interval = 30
    path = "/"
    port = "traffic-port"
    protocol = "HTTP"
    timeout = 10
    healthy_threshold = 3
    unhealthy_threshold = 3
    matcher = "200"
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_s3_bucket_public_access_block" "block" {
    bucket = aws_s3_bucket.tf_state_bucket.id
    ##############

    block_public_acls = true
    block_public_policy = true
    ignore_public_acls = true
    restrict_public_buckets = true
    resource "aws_security_group" "rds_security_group" {
    name_prefix = "rds-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 5432
    to_port = 5432
    protocol = "tcp"
    security_groups = [
    aws_security_group.ecs_security_group.id
    ]
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_dynamodb_table" "lock_table" {
    name = "terraform-state-lock"
    hash_key = "LockID"
    read_capacity = 1
    write_capacity = 1
    resource "aws_db_instance" "strapi_db" {
    allocated_storage = 10 # GB
    engine = "postgres"
    engine_version = "13.2"
    instance_class = "db.t2.micro"
    identifier = "strapi-db"
    name = var.db_name
    username = var.db_username
    password = var.db_password
    skip_final_snapshot = false

    db_subnet_group_name = aws_db_subnet_group.strapi_subnet_group.name
    vpc_security_group_ids = [aws_security_group.rds_security_group.id]

    backup_retention_period = 7 # retention period in days
    preferred_backup_window = "01:00-02:00" # backup window in UTC time
    backup_window = "02:00-03:00" # maintenance window in UTC time


    attribute {
    name = "LockID"
    type = "S"
    tags = {
    Name = "strapi-db"
    ResourceGroup = "devportal"
    }
    }

    lifecycle {
    prevent_destroy = true
    resource "aws_s3_bucket" "static_files_bucket" {
    bucket = "static-files-bucket"
    acl = "public-read"

    tags = {
    Name = "static-files-bucket"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # CDN
    resource "aws_cloudfront_distribution" "cdn" {
    origin {
    domain_name = aws_s3_bucket.static_files_bucket.bucket_regional_domain_name
    origin_id = "frontend-static-files-bucket"
    }

    default_cache_behavior {
    allowed_methods = ["GET", "HEAD", "OPTIONS"]
    cached_methods = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = "S3-my-static-files-bucket"
    forwarded_values {
    query_string = false
    cookies {
    forward = "none"
    }
    }
    viewer_protocol_policy = "redirect-to-https"
    min_ttl = 0
    default_ttl = 3600
    max_ttl = 86400
    }

    enabled = true
    is_ipv6_enabled = true
    price_class = "PriceClass_100"
    default_root_object = "index.html"

    tags = {
    Name = "cloudfront-distribution"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_cloudwatch_dashboard" "dashboard" {
    dashboard_name = "my-dashboard"
    dashboard_body = jsonencode({
    "widgets" : [
    {
    "type" : "metric",
    "x" : 0,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "CPUUtilization",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 0,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "CPUUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "NetworkIn",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ],
    [
    "AWS/EC2",
    "NetworkOut",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Sum",
    "region" : "eu-west-1",
    "title" : "Network Traffic"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "MemoryUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS Memory Utilization"
    }
    }
    ]
    })

    tags = {
    ResourceGroup = "devportal"
    }
    }


    # Route53
    resource "aws_route53_zone" "dns_zone" {
    name = "example.com"
    tags = {
    ResourceGroup = "devportal"
    }
    }

    resource "aws_route53_record" "domain_record" {
    zone_id = aws_route53_zone.domain.zone_id
    name = "strapi.example.com"
    type = "A"

    alias {
    name = aws_lb.alb.dns_name
    zone_id = aws_lb.alb.zone_id
    evaluate_target_health = false
    }

    tags = {
    ResourceGroup = "devportal"
    }
    }

    resource "aws_acm_certificate" "cert" {
    domain_name = "strapi.example.com"
    validation_method = "DNS"

    tags = {
    Terraform = "true"
    }

    lifecycle {
    create_before_destroy = true
    }
    }

    resource "aws_acm_certificate_validation" "cert_validation" {
    certificate_arn = aws_acm_certificate.cert.arn
    validation_record_fqdns = aws_route53_record.cert_validation.fqdn
    }

    resource "aws_route53_record" "cert_validation" {
    for_each = {
    for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => {
    name = dvo.resource_record_name
    record = dvo.resource_record_value
    type = dvo.resource_record_type
    }
    }

    allow_overwrite = true
    name = each.value.name
    records = [each.value.record]
    ttl = 60
    type = each.value.type
    zone_id = data.aws_route53_zone.dns_zone.zone_id
    }

    resource "aws_security_group" "alb" {
    name = "alb_security_group"
    description = "Allow inbound traffic for ALB"

    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"]
    }
    }

    resource "aws_lb" "alb" {
    name = "strapi-alb"
    internal = false
    load_balancer_type = "application"
    security_groups = [aws_security_group.alb.id]
    # security_groups = [aws_security_group.ecs_security_group.id]

    subnets = [
    aws_subnet.strapi_subnet.id
    ]
    }

    resource "aws_lb_listener" "https" {
    load_balancer_arn = aws_lb.alb.arn
    port = 443
    protocol = "HTTPS"
    ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
    certificate_arn = aws_acm_certificate_validation.cert_validation.certificate_arn

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

    resource "aws_route53_record" "strapi" {
    name = "strapi.example.com"
    type = "A"
    zone_id = aws_route53_zone.dns_zone.zone_id

    alias {
    name = aws_lb.alb.dns_name
    zone_id = aws_lb.alb.zone_id
    evaluate_target_health = false
    }
    }

    ####################

    # output "strapi_public_ip" {
    # value = aws_eip.strapi_eip.public_ip
    # }

    # output "strapi_dashboard_url" {
    # value = "http://strapi.example.com:1337/admin"
    # }
  2. gunzip revised this gist Apr 30, 2023. 1 changed file with 53 additions and 479 deletions.
    532 changes: 53 additions & 479 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -1,49 +1,65 @@
    # Create the whole infrastructure to run the Strapi CMS webserver
    # and the static frontend that uses nextjs.

    # Terraform provider configuration

    terraform {
    required_version = "~> 1.2.0"

    backend "s3" {
    bucket = "devportal-terraform-state"
    key = "devportal/terraform.tfstate"
    region = "eu-west-1"
    }

    backend "dynamodb" {
    table = "terraform-state-lock"
    hash_key = "devportal-lockid"
    max_lock_time = "10m"
    endpoint = "dynamodb.eu-west-1.amazonaws.com"
    # Uncomment the following to use S3 as a backend for Terraform state.
    # You will need to create the S3 bucket and DynamoDB table first.
    # terraform {
    # backend "s3" {
    # bucket = "devportal-terraform-state"
    # key = "terraform.tfstate"
    # region = "eu-west-1"
    # dynamodb_table = "terraform-state-lock"
    #  kms_key_id = "alias/terraform-bucket-key"
    # encrypt = true
    # }
    # }

    resource "aws_kms_key" "terraform_bucket_key" {
    description = "This key is used to encrypt bucket objects"
    deletion_window_in_days = 30
    enable_key_rotation = true
    }

    resource "aws_kms_alias" "key_alias" {
    name = "alias/terraform-bucket-key"
    target_key_id = aws_kms_key.terraform-bucket-key.key_id
    }

    resource "aws_s3_bucket" "tf_state_bucket" {
    bucket = "devportal-terraform-state"
    acl = "private"

    lifecycle {
    prevent_destroy = true
    }

    server_side_encryption_configuration {
    rule {
    apply_server_side_encryption_by_default {
    kms_master_key_id = aws_kms_key.terraform_bucket_key.arn
    sse_algorithm = "aws:kms"
    }
    }
    }
    }

    required_providers {
    aws = {
    source = "hashicorp/aws"
    version = "~> 4.0"
    }
    resource "aws_s3_bucket_versioning" "tf_state_bucket_versioning" {
    bucket = aws_s3_bucket.tf_state_bucket.id

    dynamodb = {
    source = "hashicorp/aws"
    version = "~> 4.0"
    }
    versioning_configuration {
    status = "Enabled"
    }
    }

    provider "aws" {
    region = "eu-west-1"
    }
    resource "aws_s3_bucket_public_access_block" "block" {
    bucket = aws_s3_bucket.tf_state_bucket.id

    provider "dynamodb" {
    region = "eu-west-1"
    endpoint = "dynamodb.eu-west-1.amazonaws.com"
    block_public_acls = true
    block_public_policy = true
    ignore_public_acls = true
    restrict_public_buckets = true
    }

    resource "aws_dynamodb_table" "lock_table" {
    name = "terraform-state-lock"
    hash_key = "devportal-lockid"
    hash_key = "LockID"
    read_capacity = 1
    write_capacity = 1

    @@ -52,454 +68,12 @@ resource "aws_dynamodb_table" "lock_table" {
    type = "S"
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    ####################################

    resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16"

    tags = {
    Name = "vpc"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_subnet" "strapi_subnet" {
    vpc_id = aws_vpc.vpc.id
    cidr_block = "10.0.1.0/24"

    tags = {
    Name = "strapi-subnet"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_db_subnet_group" "strapi_subnet_group" {
    name = "strapi-subnet-group"
    subnet_ids = [aws_subnet.strapi_subnet.id]
    description = "Strapi subnet group"
    tags = {
    ResourceGroup = "devportal"
    }
    }

    ############

    # Define a CloudWatch Logs log group for the ECS task
    resource "aws_cloudwatch_log_group" "strapi_logs" {
    name = "/ecs/strapi-logs"
    retention_in_days = 7
    }

    resource "aws_ecs_cluster" "strapi_cluster" {
    name = "strapi-cluster"
    }

    # Create an IAM policy to allow ECS tasks to write logs to CloudWatch
    resource "aws_iam_policy" "ecs_task_logging_policy" {
    name = "ecs-task-logging-policy"

    policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Action = [
    "logs:CreateLogStream",
    "logs:PutLogEvents"
    ]
    Resource = "arn:aws:logs:*:*:*"
    }
    ]
    })
    }

    # Create an IAM role for ECS task execution
    resource "aws_iam_role" "ecs_task_execution_role" {
    name = "ecs-task-execution-role"

    assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Principal = {
    Service = "ecs-tasks.amazonaws.com"
    }
    Action = "sts:AssumeRole"
    }
    ]
    })

    # Attach the task execution role to the logging policy
    policy = aws_iam_policy.ecs_task_logging_policy.arn
    }

    resource "aws_security_group" "ecs_security_group" {
    name_prefix = "ecs-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 0
    to_port = 65535
    protocol = "tcp"
    cidr_blocks = [
    aws_vpc.vpc.cidr_block,
    ]
    lifecycle {
    prevent_destroy = true
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # Create a task definition for the ECS service
    resource "aws_ecs_task_definition" "strapi_task" {
    family = "strapi-task"
    execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
    network_mode = "awsvpc"
    requires_compatibilities = ["FARGATE"]

    container_definitions = jsonencode([
    {
    name = "strapi-container"
    image = "docker.pkg.github.com/OWNER/REPO/IMAGE:TAG"
    portMappings = [
    {
    containerPort = 1337
    protocol = "tcp"
    }
    ]
    environment = [
    {
    name = "DATABASE_URL"
    value = format("postgres://%s:%s@%s/%s",
    aws_db_instance.strapi_db.username,
    aws_db_instance.strapi_db.password,
    aws_db_instance.strapi_db.endpoint,
    aws_db_instance.strapi_db.db_name)
    }
    ]
    }
    ])

    network_configuration {
    subnets = [aws_subnet.strapi_subnet.id]

    security_groups = [aws_security_group.ecs_security_group.id]

    assign_public_ip = "DISABLED"
    }

    logConfiguration = {
    logDriver = "awslogs"
    options = {
    "awslogs-group" = aws_cloudwatch_log_group.strapi_logs.name
    "awslogs-region" = aws_cloudwatch_log_group.strapi_logs.region
    "awslogs-stream-prefix" = "strapi-container"
    }
    }
    }

    resource "aws_ecs_service" "strapi_service" {
    name = "strapi-service"
    cluster = aws_ecs_cluster.strapi_cluster.id
    task_definition = aws_ecs_task_definition.strapi_task.arn
    desired_count = 1

    network_configuration {
    assign_public_ip = true
    security_groups = [aws_security_group.ecs_security_group.id]
    subnets = [
    aws_subnet.strapi_subnet.id
    ]
    }

    load_balancer {
    target_group_arn = aws_lb_target_group.strapi_target_group.arn
    container_name = "strapi-container"
    container_port = 1337
    }

    depends_on = [aws_lb_target_group_attachment.strapi]
    }

    resource "aws_lb_target_group" "strapi_target_group" {
    name_prefix = "strapi-target-group"
    port = 1337
    protocol = "HTTP"
    target_type = "ip"
    vpc_id = aws_vpc.vpc.id

    health_check {
    interval = 30
    path = "/health"
    port = "traffic-port"
    protocol = "HTTP"
    timeout = 10
    healthy_threshold = 3
    unhealthy_threshold = 3
    matcher = "200"
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    ##############

    resource "aws_security_group" "rds_security_group" {
    name_prefix = "rds-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 5432
    to_port = 5432
    protocol = "tcp"
    security_groups = [
    aws_security_group.ecs_security_group.id
    ]
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_db_instance" "strapi_db" {
    allocated_storage = 10 # GB
    engine = "postgres"
    engine_version = "13.2"
    instance_class = "db.t2.micro"
    identifier = "strapi-db"
    name = var.db_name
    username = var.db_username
    password = var.db_password
    skip_final_snapshot = false
    db_subnet_group_name = aws_db_subnet_group.strapi_subnet_group.name

    vpc_security_group_ids = [aws_security_group.rds_security_group.id]

    backup_retention_period = 7 # retention period in days
    preferred_backup_window = "01:00-02:00" # backup window in UTC time
    backup_window = "02:00-03:00" # maintenance window in UTC time


    tags = {
    Name = "strapi-db"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_s3_bucket" "static_files_bucket" {
    bucket = "static-files-bucket"
    acl = "public-read"

    tags = {
    Name = "static-files-bucket"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # CDN
    resource "aws_cloudfront_distribution" "cdn" {
    origin {
    domain_name = aws_s3_bucket.static_files_bucket.bucket_regional_domain_name
    origin_id = "S3-my-static-files-bucket"
    }

    default_cache_behavior {
    allowed_methods = ["GET", "HEAD", "OPTIONS"]
    cached_methods = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = "S3-my-static-files-bucket"
    forwarded_values {
    query_string = false
    cookies {
    forward = "none"
    }
    }
    viewer_protocol_policy = "redirect-to-https"
    min_ttl = 0
    default_ttl = 3600
    max_ttl = 86400
    }

    enabled = true
    is_ipv6_enabled = true
    price_class = "PriceClass_100"
    default_root_object = "index.html"

    tags = {
    Name = "cloudfront-distribution"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_cloudwatch_dashboard" "dashboard" {
    dashboard_name = "my-dashboard"
    dashboard_body = jsonencode({
    "widgets" : [
    {
    "type" : "metric",
    "x" : 0,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "CPUUtilization",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 0,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "CPUUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "NetworkIn",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ],
    [
    "AWS/EC2",
    "NetworkOut",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Sum",
    "region" : "eu-west-1",
    "title" : "Network Traffic"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "MemoryUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS Memory Utilization"
    }
    }
    ]
    })

    tags = {
    ResourceGroup = "devportal"
    }
    }


    # Route53
    resource "aws_route53_zone" "domain" {
    name = "example.com."
    tags = {
    ResourceGroup = "devportal"
    }
    }

    resource "aws_route53_record" "domain_record" {
    zone_id = aws_route53_zone.domain.zone_id
    name = "strapi.example.com"
    type = "A"
    ttl = "300"
    records = ["${aws_eip.strapi_eip.public_ip}"]
    tags = {
    ResourceGroup = "devportal"
    }
    }

    ####################

    # Elastic IP for EC2 instance
    resource "aws_eip" "strapi_eip" {
    vpc = true

    tags = {
    Name = "strapi-eip"
    ResourceGroup = "devportal"
    }
    }

    # Associate Elastic IP with ECS instance
    resource "aws_eip_association" "strapi_eip_association" {
    instance_id = aws_ecs_instance.strapi_instance.id
    allocation_id = aws_eip.strapi_eip.id

    tags = {
    Name = "strapi-eip-association"
    ResourceGroup = "devportal"
    }
    }

    # Outputs
    output "strapi_public_ip" {
    value = aws_eip.strapi_eip.public_ip
    }

    output "strapi_dashboard_url" {
    value = "https://${aws_eip.strapi_eip.public_ip}:1337/admin"
    }
  3. gunzip revised this gist Apr 29, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -466,7 +466,7 @@ resource "aws_route53_record" "domain_record" {
    name = "strapi.example.com"
    type = "A"
    ttl = "300"
    records = ["${aws_instance.webserver.private_ip}"]
    records = ["${aws_eip.strapi_eip.public_ip}"]
    tags = {
    ResourceGroup = "devportal"
    }
  4. gunzip created this gist Apr 29, 2023.
    505 changes: 505 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,505 @@
    # Create the whole infrastructure to run the Strapi CMS webserver
    # and the static frontend that uses nextjs.

    # Terraform provider configuration

    terraform {
    required_version = "~> 1.2.0"

    backend "s3" {
    bucket = "devportal-terraform-state"
    key = "devportal/terraform.tfstate"
    region = "eu-west-1"
    }

    backend "dynamodb" {
    table = "terraform-state-lock"
    hash_key = "devportal-lockid"
    max_lock_time = "10m"
    endpoint = "dynamodb.eu-west-1.amazonaws.com"
    }

    required_providers {
    aws = {
    source = "hashicorp/aws"
    version = "~> 4.0"
    }

    dynamodb = {
    source = "hashicorp/aws"
    version = "~> 4.0"
    }
    }
    }

    provider "aws" {
    region = "eu-west-1"
    }

    provider "dynamodb" {
    region = "eu-west-1"
    endpoint = "dynamodb.eu-west-1.amazonaws.com"
    }

    resource "aws_dynamodb_table" "lock_table" {
    name = "terraform-state-lock"
    hash_key = "devportal-lockid"
    read_capacity = 1
    write_capacity = 1

    attribute {
    name = "LockID"
    type = "S"
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    ####################################

    resource "aws_vpc" "vpc" {
    cidr_block = "10.0.0.0/16"

    tags = {
    Name = "vpc"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_subnet" "strapi_subnet" {
    vpc_id = aws_vpc.vpc.id
    cidr_block = "10.0.1.0/24"

    tags = {
    Name = "strapi-subnet"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_db_subnet_group" "strapi_subnet_group" {
    name = "strapi-subnet-group"
    subnet_ids = [aws_subnet.strapi_subnet.id]
    description = "Strapi subnet group"
    tags = {
    ResourceGroup = "devportal"
    }
    }

    ############

    # Define a CloudWatch Logs log group for the ECS task
    resource "aws_cloudwatch_log_group" "strapi_logs" {
    name = "/ecs/strapi-logs"
    retention_in_days = 7
    }

    resource "aws_ecs_cluster" "strapi_cluster" {
    name = "strapi-cluster"
    }

    # Create an IAM policy to allow ECS tasks to write logs to CloudWatch
    resource "aws_iam_policy" "ecs_task_logging_policy" {
    name = "ecs-task-logging-policy"

    policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Action = [
    "logs:CreateLogStream",
    "logs:PutLogEvents"
    ]
    Resource = "arn:aws:logs:*:*:*"
    }
    ]
    })
    }

    # Create an IAM role for ECS task execution
    resource "aws_iam_role" "ecs_task_execution_role" {
    name = "ecs-task-execution-role"

    assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
    {
    Effect = "Allow"
    Principal = {
    Service = "ecs-tasks.amazonaws.com"
    }
    Action = "sts:AssumeRole"
    }
    ]
    })

    # Attach the task execution role to the logging policy
    policy = aws_iam_policy.ecs_task_logging_policy.arn
    }

    resource "aws_security_group" "ecs_security_group" {
    name_prefix = "ecs-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 0
    to_port = 65535
    protocol = "tcp"
    cidr_blocks = [
    aws_vpc.vpc.cidr_block,
    ]
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # Create a task definition for the ECS service
    resource "aws_ecs_task_definition" "strapi_task" {
    family = "strapi-task"
    execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
    network_mode = "awsvpc"
    requires_compatibilities = ["FARGATE"]

    container_definitions = jsonencode([
    {
    name = "strapi-container"
    image = "docker.pkg.github.com/OWNER/REPO/IMAGE:TAG"
    portMappings = [
    {
    containerPort = 1337
    protocol = "tcp"
    }
    ]
    environment = [
    {
    name = "DATABASE_URL"
    value = format("postgres://%s:%s@%s/%s",
    aws_db_instance.strapi_db.username,
    aws_db_instance.strapi_db.password,
    aws_db_instance.strapi_db.endpoint,
    aws_db_instance.strapi_db.db_name)
    }
    ]
    }
    ])

    network_configuration {
    subnets = [aws_subnet.strapi_subnet.id]

    security_groups = [aws_security_group.ecs_security_group.id]

    assign_public_ip = "DISABLED"
    }

    logConfiguration = {
    logDriver = "awslogs"
    options = {
    "awslogs-group" = aws_cloudwatch_log_group.strapi_logs.name
    "awslogs-region" = aws_cloudwatch_log_group.strapi_logs.region
    "awslogs-stream-prefix" = "strapi-container"
    }
    }
    }

    resource "aws_ecs_service" "strapi_service" {
    name = "strapi-service"
    cluster = aws_ecs_cluster.strapi_cluster.id
    task_definition = aws_ecs_task_definition.strapi_task.arn
    desired_count = 1

    network_configuration {
    assign_public_ip = true
    security_groups = [aws_security_group.ecs_security_group.id]
    subnets = [
    aws_subnet.strapi_subnet.id
    ]
    }

    load_balancer {
    target_group_arn = aws_lb_target_group.strapi_target_group.arn
    container_name = "strapi-container"
    container_port = 1337
    }

    depends_on = [aws_lb_target_group_attachment.strapi]
    }

    resource "aws_lb_target_group" "strapi_target_group" {
    name_prefix = "strapi-target-group"
    port = 1337
    protocol = "HTTP"
    target_type = "ip"
    vpc_id = aws_vpc.vpc.id

    health_check {
    interval = 30
    path = "/health"
    port = "traffic-port"
    protocol = "HTTP"
    timeout = 10
    healthy_threshold = 3
    unhealthy_threshold = 3
    matcher = "200"
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    ##############

    resource "aws_security_group" "rds_security_group" {
    name_prefix = "rds-"
    vpc_id = aws_vpc.vpc.id

    ingress {
    from_port = 5432
    to_port = 5432
    protocol = "tcp"
    security_groups = [
    aws_security_group.ecs_security_group.id
    ]
    }

    tags = {
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_db_instance" "strapi_db" {
    allocated_storage = 10 # GB
    engine = "postgres"
    engine_version = "13.2"
    instance_class = "db.t2.micro"
    identifier = "strapi-db"
    name = var.db_name
    username = var.db_username
    password = var.db_password
    skip_final_snapshot = false
    db_subnet_group_name = aws_db_subnet_group.strapi_subnet_group.name

    vpc_security_group_ids = [aws_security_group.rds_security_group.id]

    backup_retention_period = 7 # retention period in days
    preferred_backup_window = "01:00-02:00" # backup window in UTC time
    backup_window = "02:00-03:00" # maintenance window in UTC time


    tags = {
    Name = "strapi-db"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_s3_bucket" "static_files_bucket" {
    bucket = "static-files-bucket"
    acl = "public-read"

    tags = {
    Name = "static-files-bucket"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    # CDN
    resource "aws_cloudfront_distribution" "cdn" {
    origin {
    domain_name = aws_s3_bucket.static_files_bucket.bucket_regional_domain_name
    origin_id = "S3-my-static-files-bucket"
    }

    default_cache_behavior {
    allowed_methods = ["GET", "HEAD", "OPTIONS"]
    cached_methods = ["GET", "HEAD", "OPTIONS"]
    target_origin_id = "S3-my-static-files-bucket"
    forwarded_values {
    query_string = false
    cookies {
    forward = "none"
    }
    }
    viewer_protocol_policy = "redirect-to-https"
    min_ttl = 0
    default_ttl = 3600
    max_ttl = 86400
    }

    enabled = true
    is_ipv6_enabled = true
    price_class = "PriceClass_100"
    default_root_object = "index.html"

    tags = {
    Name = "cloudfront-distribution"
    Environment = "production"
    ResourceGroup = "devportal"
    }
    }

    resource "aws_cloudwatch_dashboard" "dashboard" {
    dashboard_name = "my-dashboard"
    dashboard_body = jsonencode({
    "widgets" : [
    {
    "type" : "metric",
    "x" : 0,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "CPUUtilization",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 0,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "CPUUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS CPU Utilization"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 0,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/EC2",
    "NetworkIn",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ],
    [
    "AWS/EC2",
    "NetworkOut",
    "InstanceId",
    "${aws_ecs_task_definition.strapi_task.family}:*"
    ]
    ],
    "period" : 300,
    "stat" : "Sum",
    "region" : "eu-west-1",
    "title" : "Network Traffic"
    }
    },
    {
    "type" : "metric",
    "x" : 6,
    "y" : 6,
    "width" : 6,
    "height" : 6,
    "properties" : {
    "metrics" : [
    [
    "AWS/ECS",
    "MemoryUtilization",
    "ClusterName",
    "${aws_ecs_cluster.strapi_cluster.name}",
    "ServiceName",
    "${aws_ecs_service.strapi_service.name}"
    ]
    ],
    "period" : 300,
    "stat" : "Average",
    "region" : "eu-west-1",
    "title" : "ECS Memory Utilization"
    }
    }
    ]
    })

    tags = {
    ResourceGroup = "devportal"
    }
    }


    # Route53
    resource "aws_route53_zone" "domain" {
    name = "example.com."
    tags = {
    ResourceGroup = "devportal"
    }
    }

    resource "aws_route53_record" "domain_record" {
    zone_id = aws_route53_zone.domain.zone_id
    name = "strapi.example.com"
    type = "A"
    ttl = "300"
    records = ["${aws_instance.webserver.private_ip}"]
    tags = {
    ResourceGroup = "devportal"
    }
    }

    ####################

    # Elastic IP for EC2 instance
    resource "aws_eip" "strapi_eip" {
    vpc = true

    tags = {
    Name = "strapi-eip"
    ResourceGroup = "devportal"
    }
    }

    # Associate Elastic IP with ECS instance
    resource "aws_eip_association" "strapi_eip_association" {
    instance_id = aws_ecs_instance.strapi_instance.id
    allocation_id = aws_eip.strapi_eip.id

    tags = {
    Name = "strapi-eip-association"
    ResourceGroup = "devportal"
    }
    }

    # Outputs
    output "strapi_public_ip" {
    value = aws_eip.strapi_eip.public_ip
    }

    output "strapi_dashboard_url" {
    value = "https://${aws_eip.strapi_eip.public_ip}:1337/admin"
    }