From d06b68620d45cc85b3f7be1ef4629caa7c75bfd0 Mon Sep 17 00:00:00 2001 From: brooktewabe Date: Thu, 2 Jan 2025 11:09:46 +0300 Subject: [PATCH 1/2] Terraform deployment --- .dockerignore | 3 + .gitignore | 30 +++++ Dockerfile | 14 +++ README.md | 213 ++++++++++++++++++++---------------- Terraform/acm.tf | 23 ++++ Terraform/alb.tf | 117 ++++++++++++++++++++ Terraform/ecr.tf | 47 ++++++++ Terraform/ecs.tf | 151 +++++++++++++++++++++++++ Terraform/iam.tf | 52 +++++++++ Terraform/main.tf | 6 + Terraform/network.tf | 95 ++++++++++++++++ Terraform/redis.tf | 52 +++++++++ Terraform/terraform.tfvars | 6 + Terraform/variables.tf | 34 ++++++ admin/Dockerfile | 21 ---- admin/Dockerfile.prod | 27 ----- backend/Dockerfile | 19 ---- backend/Dockerfile.prod | 47 -------- backend/develop.sh | 5 - docker-compose.override.yml | 25 ----- docker-compose.prod.yml | 55 ---------- docker-compose.yml | 53 --------- storefront/Dockerfile | 19 ---- storefront/Dockerfile.prod | 25 ----- 24 files changed, 747 insertions(+), 392 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Terraform/acm.tf create mode 100644 Terraform/alb.tf create mode 100644 Terraform/ecr.tf create mode 100644 Terraform/ecs.tf create mode 100644 Terraform/iam.tf create mode 100644 Terraform/main.tf create mode 100644 Terraform/network.tf create mode 100644 Terraform/redis.tf create mode 100644 Terraform/terraform.tfvars create mode 100644 Terraform/variables.tf delete mode 100644 admin/Dockerfile delete mode 100644 admin/Dockerfile.prod delete mode 100644 backend/Dockerfile delete mode 100644 backend/Dockerfile.prod delete mode 100755 backend/develop.sh delete mode 100644 docker-compose.override.yml delete mode 100644 docker-compose.prod.yml delete mode 100644 docker-compose.yml delete mode 100644 storefront/Dockerfile delete mode 100644 storefront/Dockerfile.prod diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..8913673c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +Terraform \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b3545ef8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +/dist +.env +.DS_Store +/uploads +/node_modules +yarn-error.log + +.idea + +coverage + +!src/** +.terraform +*.tfstate.* +*.tfstate +.terraform.lock.* +./tsconfig.tsbuildinfo +package-lock.json +medusa-db.sql +build +.cache + +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +.medusa \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..7ec6da45 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM node:20 + +WORKDIR /app + +COPY package.json yarn.lock ./ + +RUN yarn global add @medusajs/medusa-cli@latest + +RUN yarn install + +COPY . . + +# or "start" for production +CMD ["yarn", "dev"] \ No newline at end of file diff --git a/README.md b/README.md index 3485c9af..44978415 100644 --- a/README.md +++ b/README.md @@ -3,134 +3,155 @@ Medusa

-

- Medusa -

- -

- Medusa Admin | - Website | - Blog | - LinkedIn | - Twitter | - Documentation | - Notion -

-

-Medusa is an open-source headless commerce engine that enables developers to create amazing digital commerce experiences. -

-

- - Medusa is released under the MIT license. - - - PRs welcome! - - Product Hunt - - Discord Chat - - - Follow @medusajs - -

+# Medusa Backend AWS Infrastructure setup +This repository contains the infrastructure as code (IaC) setup using Terraform and Docker for deploying Medusa e-commerce platform on AWS. -## Please note -This repo is managed by the Medusa Community. Medusa does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk. +## Architecture Overview -**This project is inteded for development only at this time.** +The infrastructure setup includes: +- ECS Fargate for container orchestration +- Application Load Balancer (ALB) for traffic distribution +- ElastiCache Redis for caching +- Neon Database (external) for PostgreSQL +- Route53 for DNS management +- ACM for SSL/TLS certificates +- ECR for container registry +- VPC with public subnets across multiple AZs -The files for both the Medusa server and the Storefront are loaded in Bind Mounts allowing you to change the server functionality and have the change be hot-reloaded onto your running containers. +## Prerequisites -

+- AWS CLI configured with appropriate credentials +- Terraform >= 1.0 +- Docker +- Domain name with Route53 hosted zone and certificate +- Database account and connection string eg. Neon DB ---- +## Quick Start -## Requirements +1. Clone this repository: +```bash +git clone +cd medusa-infrastructure +``` -To use Docker with Medusa, you should have created a Medusa project. Check out our [Quickstart](https://github.com/medusajs/medusa#quickstart) to get started. +2. Update `terraform.tfvars` with your values: +```hcl +aws_region = "your-region" +domain_name = "your-domain.com" +subdomain = "manage" +neon_db_url = "your-neon-db-url" +certificate_domain = "*.your-domain.com" +``` -Additionally, you should have `docker` and `docker-compose` installed on your system. +3. Initialize Terraform: +```bash +terraform init +``` -## Getting Started +4. Deploy the infrastructure: +```bash +terraform plan +terraform apply +``` -To set up Medusa in a development environment with Docker, you should copy files `docker-compose.yml`, `docker-compose.override.yml, `backend/develop.sh`, and `backend/Dockerfile` to your Medusa project. +5. Build and push Docker image: +```bash +# Login to ECR +aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com -Then build the images since they are not published on dockerhub. This is accomplished by adding the `--build` flag as shown below: +# Build image +docker build -t medusa-repo . -```bash -docker compose up --build -``` +# Tag image +docker tag medusa-repo:latest .dkr.ecr..amazonaws.com/medusa-repo:latest -Having already built the Docker images you can run docker compose without the `--build` flag. +# Create repo +aws ecr create-repository --repository-name your-repo-name ``` -docker compose up +# Push image +docker push .dkr.ecr..amazonaws.com/medusa-repo:latest + +## Infrastructure Components ``` -Your local Medusa setup is now running with each of the services occupying the following ports: +### Networking +- VPC with CIDR block 10.0.0.0/16 +- Two public subnets across different AZs +- Internet Gateway for public internet access +- Route tables for subnet routing -
    -
  • Medusa Server: 9000 -
  • Medusa Admin: 7000 -
  • Storefront: 8000 -
  • postgres: 5432 -
  • redis: 6379 -
+### Security +- Security groups for ALB, ECS tasks, and Redis +- IAM roles and policies for ECS tasks +- SSL/TLS certificate management -_Note: If you change the dependencies of your projects by adding new packages you can simply rebuild that package with the same tag `test` and run `docker compose up` once again to update your environment._ +### Container Infrastructure +- ECS Cluster with Fargate launch type +- Auto-scaling policies based on CPU and memory utilization +- ECR repository with lifecycle policies -### Seeding your Medusa store +### Load Balancing +- Application Load Balancer +- HTTPS listener with SSL certificate +- HTTP to HTTPS redirect +- Health checks configuration -To add seed data to your medusa store run this command in a seperate +### Caching +- Redis ElastiCache cluster +- Subnet group for Redis deployment +- Security group for Redis access -``` -docker exec medusa-server medusa seed -f ./data/seed.json -``` +## Environment Variables -## Running Medusa with docker in production +The following environment variables are configured in the ECS task definition: +- `REDIS_URL`: Auto-generated from ElastiCache, use this in .env file +- `NODE_ENV`: Set to "production" +- `DATABASE_URL`: Provided via terraform.tfvars -This repository and each of the services contain dockerfiles for both development and production, named `Dockerfile` and `Dockerfile.prod` respectively. The `Dockerfile.prod` copies the local files from disk and builds a production ready image based on your local development progress. Your specific needs for a production like container might differ from the `Dockerfile.prod` but it should provide a template and an idea of the requirements for each of the basic services. +## Monitoring and Logging -To run the services in a production state `docker compose` is simply run with the `docker-compose.production.yml` file as well as the basic `docker-compose.yml` file as seen below. If you wish to build the production ready images and then start them run `docker compose up` with the `--build` flag as described above. +- CloudWatch Log Groups for container logs +- Container Insights enabled on ECS cluster +- ALB access logs (optional) -``` -docker compose up -f docker-compose.yml -f docker-compose.production.yml up -``` +## Security Considerations -`docker-compose.production.yml` contains production relevant overrides to the services described in the `docker-compose.yml` development file. +- All resources are deployed within a VPC +- Security groups limit access to required ports only +- HTTPS enforced with HTTP to HTTPS redirect +- IAM roles follow principle of least privilege +- Redis access restricted to ECS tasks -## Try it out +## Cost Optimization -``` -curl -X GET localhost:9000/store/products | python -m json.tool -``` +- Fargate Spot can be used for cost savings +- Auto-scaling based on demand +- ECR lifecycle policies to manage image storage +- ElastiCache instance sized appropriately -After the seed script has run you will have the following things in you database: +## Maintenance -- a User with the email: admin@medusa-test.com and password: supersecret -- a Region called Default Region with the countries GB, DE, DK, SE, FR, ES, IT -- a Shipping Option called Standard Shipping which costs 10 EUR -- a Product called Cool Test Product with 4 Product Variants that all cost 19.50 EUR +### Updating the Application +1. Build new Docker image +2. Push to ECR +3. Update ECS service (automatic with latest tag) -Visit [docs.medusa-commerce.com](https://docs.medusa-comerce.com) for further guides. +### Infrastructure Updates +1. Update Terraform code +2. Run `terraform plan` to review changes +3. Apply changes with `terraform apply` -

- - Website - - | - - Notion Home - - | - - Twitter - - | - - Docs - -

+## Contributing + +1. Fork the repository +2. Create your feature branch +3. Commit your changes +4. Push to the branch +5. Create a new Pull Request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file diff --git a/Terraform/acm.tf b/Terraform/acm.tf new file mode 100644 index 00000000..f25490dd --- /dev/null +++ b/Terraform/acm.tf @@ -0,0 +1,23 @@ +data "aws_route53_zone" "medusa_zone" { + name = var.domain_name + private_zone = false +} + +# Fetch the existing ACM certificate +data "aws_acm_certificate" "existing_cert" { + domain = var.certificate_domain + statuses = ["ISSUED"] + most_recent = true +} + +resource "aws_route53_record" "medusa_domain" { + zone_id = data.aws_route53_zone.medusa_zone.zone_id + name = local.full_domain + type = "A" + + alias { + name = aws_lb.medusa_alb.dns_name + zone_id = aws_lb.medusa_alb.zone_id + evaluate_target_health = true + } +} \ No newline at end of file diff --git a/Terraform/alb.tf b/Terraform/alb.tf new file mode 100644 index 00000000..6d5316cb --- /dev/null +++ b/Terraform/alb.tf @@ -0,0 +1,117 @@ +resource "aws_lb" "medusa_alb" { + name = "medusa-alb" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.medusa_alb_sg.id] + subnets = [aws_subnet.medusa_subnet_1.id, aws_subnet.medusa_subnet_2.id] + + enable_deletion_protection = false + + tags = { + Name = "medusa-alb" + } +} + +resource "aws_lb_target_group" "medusa_tg" { + name = "medusa-tg" + port = 9000 + protocol = "HTTP" + vpc_id = aws_vpc.medusa_vpc.id + target_type = "ip" + + health_check { + path = "/health" + healthy_threshold = 2 + unhealthy_threshold = 10 + } +} + +resource "aws_lb_listener" "medusa_listener" { + load_balancer_arn = aws_lb.medusa_alb.arn + port = "443" + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-2016-08" + certificate_arn = data.aws_acm_certificate.existing_cert.arn + + default_action { + type = "forward" + target_group_arn = aws_lb_target_group.medusa_tg.arn + } +} + +resource "aws_lb_listener" "medusa_listener_http" { + load_balancer_arn = aws_lb.medusa_alb.arn + port = "80" + protocol = "HTTP" + + default_action { + type = "redirect" + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } +} +resource "aws_lb_listener_rule" "redirect_to_dashboard" { + listener_arn = aws_lb_listener.medusa_listener.arn + priority = 1 + + action { + type = "redirect" + redirect { + path = "/dashboard" + status_code = "HTTP_301" + } + } + + condition { + host_header { + values = [local.full_domain] + } + } + + condition { + path_pattern { + values = ["/"] + } + } +} + +resource "aws_security_group" "medusa_alb_sg" { + name_prefix = "medusa-alb-sg-" + description = "Security group for Medusa ALB" + vpc_id = aws_vpc.medusa_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"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + lifecycle { + create_before_destroy = true + } + + tags = { + Name = "medusa-alb-sg" + Environment = "production" + Project = "medusa" + Terraform = "true" + } +} \ No newline at end of file diff --git a/Terraform/ecr.tf b/Terraform/ecr.tf new file mode 100644 index 00000000..45169e3a --- /dev/null +++ b/Terraform/ecr.tf @@ -0,0 +1,47 @@ +resource "aws_ecr_repository" "medusa_repo" { + name = "medusa-repo" + image_tag_mutability = "MUTABLE" + + image_scanning_configuration { + scan_on_push = true + } + # This will prevent Terraform from trying to delete the repository if it already exists + lifecycle { + prevent_destroy = true + ignore_changes = [name] + } +} + +resource "aws_ecr_lifecycle_policy" "medusa_repo_policy" { + repository = aws_ecr_repository.medusa_repo.name + + policy = jsonencode({ + rules = [ + { + rulePriority = 2 + description = "Keep last 30 images" + selection = { + tagStatus = "any" + countType = "imageCountMoreThan" + countNumber = 30 + } + action = { + type = "expire" + } + }, + { + rulePriority = 1 + description = "Remove untagged images after 14 days" + selection = { + tagStatus = "untagged" + countType = "sinceImagePushed" + countUnit = "days" + countNumber = 14 + } + action = { + type = "expire" + } + } + ] + }) +} diff --git a/Terraform/ecs.tf b/Terraform/ecs.tf new file mode 100644 index 00000000..5790d500 --- /dev/null +++ b/Terraform/ecs.tf @@ -0,0 +1,151 @@ +resource "aws_ecs_cluster" "medusa_cluster" { + name = "medusa-cluster" + + setting { + name = "containerInsights" + value = "enabled" + } + tags = { + Name = "medusa-cluster" + Environment = "production" + Project = "medusa" + Terraform = "true" + } +} + +resource "aws_ecs_task_definition" "medusa_task" { + family = "medusa-task" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = "1024" + memory = "2048" + execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + + container_definitions = jsonencode([ + { + name = "medusa-container" + image = "${aws_ecr_repository.medusa_repo.repository_url}:latest" + essential = true + portMappings = [ + { + containerPort = 9000 + hostPort = 9000 + protocol = "tcp" + } + ] + environment = [ + { + name = "REDIS_URL" + value = "redis://${aws_elasticache_cluster.medusa_redis.cache_nodes.0.address}:${aws_elasticache_cluster.medusa_redis.port}" + }, + { + name = "NODE_ENV" + value = "production" + }, + { + name = "DATABASE_URL" + value = var.neon_db_url + } + ] + # secrets = [ + # { + # name = "REDIS_URL" + # valueFrom = aws_ssm_parameter.redis_url.arn + # } + # ] + logConfiguration = { + logDriver = "awslogs" + options = { + awslogs-group = aws_cloudwatch_log_group.medusa_logs.name + awslogs-region = var.aws_region + awslogs-stream-prefix = "ecs" + } + } + } + ]) +} + +resource "aws_ecs_service" "medusa_service" { + name = "medusa-service" + cluster = aws_ecs_cluster.medusa_cluster.id + task_definition = aws_ecs_task_definition.medusa_task.arn + desired_count = 1 + + capacity_provider_strategy { + capacity_provider = "FARGATE_SPOT" + weight = 100 + } + + deployment_controller { + type = "ECS" + } + + network_configuration { + subnets = [aws_subnet.medusa_subnet_1.id, aws_subnet.medusa_subnet_2.id] + security_groups = [aws_security_group.medusa_sg.id] + assign_public_ip = true + } + + load_balancer { + target_group_arn = aws_lb_target_group.medusa_tg.arn + container_name = "medusa-container" + container_port = 9000 + } + + deployment_minimum_healthy_percent = 100 + deployment_maximum_percent = 200 + + health_check_grace_period_seconds = 60 + + lifecycle { + ignore_changes = [desired_count] + } +} + +resource "aws_cloudwatch_log_group" "medusa_logs" { + name = "/ecs/medusa-logs" + retention_in_days = 30 +} + +resource "aws_appautoscaling_target" "medusa_target" { + max_capacity = 4 + min_capacity = 1 + resource_id = "service/${aws_ecs_cluster.medusa_cluster.name}/${aws_ecs_service.medusa_service.name}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_appautoscaling_policy" "medusa_cpu" { + name = "medusa-cpu" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.medusa_target.resource_id + scalable_dimension = aws_appautoscaling_target.medusa_target.scalable_dimension + service_namespace = aws_appautoscaling_target.medusa_target.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + target_value = 70.0 + scale_in_cooldown = 300 + scale_out_cooldown = 300 + } +} + +resource "aws_appautoscaling_policy" "medusa_memory" { + name = "medusa-memory" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.medusa_target.resource_id + scalable_dimension = aws_appautoscaling_target.medusa_target.scalable_dimension + service_namespace = aws_appautoscaling_target.medusa_target.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + target_value = 80.0 + scale_in_cooldown = 300 + scale_out_cooldown = 300 + } +} \ No newline at end of file diff --git a/Terraform/iam.tf b/Terraform/iam.tf new file mode 100644 index 00000000..cf0dda38 --- /dev/null +++ b/Terraform/iam.tf @@ -0,0 +1,52 @@ +resource "aws_iam_role" "ecs_task_execution_role" { + name = "ecs-task-execution-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" { + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} +resource "aws_iam_role_policy_attachment" "ecs_task_execution_ecr_policy" { + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" +} +# Add CloudWatch Logs policy +resource "aws_iam_role_policy_attachment" "ecs_task_execution_cloudwatch_policy" { + role = aws_iam_role.ecs_task_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" +} +resource "aws_iam_role" "ecs_task_role" { + name = "ecs-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) +} + +# Add CloudWatch Logs policy for task role +resource "aws_iam_role_policy_attachment" "ecs_task_cloudwatch_policy" { + role = aws_iam_role.ecs_task_role.name + policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess" +} diff --git a/Terraform/main.tf b/Terraform/main.tf new file mode 100644 index 00000000..75613869 --- /dev/null +++ b/Terraform/main.tf @@ -0,0 +1,6 @@ +provider "aws" { + region = var.aws_region +} +locals { + full_domain = "${var.subdomain}.${var.domain_name}" +} diff --git a/Terraform/network.tf b/Terraform/network.tf new file mode 100644 index 00000000..3a066e76 --- /dev/null +++ b/Terraform/network.tf @@ -0,0 +1,95 @@ +resource "aws_vpc" "medusa_vpc" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true + + tags = { + Name = "medusa-vpc" + Environment = "production" + Project = "medusa" + Terraform = "true" + } +} + + +resource "aws_subnet" "medusa_subnet_1" { + vpc_id = aws_vpc.medusa_vpc.id + cidr_block = "10.0.1.0/24" + availability_zone = "${var.aws_region}a" + map_public_ip_on_launch = true + tags = { + Name = "medusa-subnet-1" + } +} + +resource "aws_subnet" "medusa_subnet_2" { + vpc_id = aws_vpc.medusa_vpc.id + cidr_block = "10.0.2.0/24" + availability_zone = "${var.aws_region}b" + map_public_ip_on_launch = true + tags = { + Name = "medusa-subnet-2" + } +} + +resource "aws_internet_gateway" "medusa_igw" { + vpc_id = aws_vpc.medusa_vpc.id + + tags = { + Name = "medusa-igw" + } +} + +resource "aws_route_table" "medusa_route_table" { + vpc_id = aws_vpc.medusa_vpc.id + + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.medusa_igw.id + } + + tags = { + Name = "medusa-route-table" + } +} + +resource "aws_route_table_association" "medusa_rta_1" { + subnet_id = aws_subnet.medusa_subnet_1.id + route_table_id = aws_route_table.medusa_route_table.id +} + +resource "aws_route_table_association" "medusa_rta_2" { + subnet_id = aws_subnet.medusa_subnet_2.id + route_table_id = aws_route_table.medusa_route_table.id +} + +resource "aws_security_group" "medusa_sg" { + name_prefix = "medusa-app-sg-" + description = "Security group for Medusa application" + vpc_id = aws_vpc.medusa_vpc.id + + ingress { + from_port = 9000 + to_port = 9000 + protocol = "tcp" + security_groups = [aws_security_group.medusa_alb_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + lifecycle { + create_before_destroy = true + } + + tags = { + Name = "medusa-app-sg" + Environment = "production" + Project = "medusa" + Terraform = "true" + } +} \ No newline at end of file diff --git a/Terraform/redis.tf b/Terraform/redis.tf new file mode 100644 index 00000000..26fb3988 --- /dev/null +++ b/Terraform/redis.tf @@ -0,0 +1,52 @@ +resource "aws_elasticache_subnet_group" "medusa_redis_subnet_group" { + name = "medusa-redis-subnet-group" + subnet_ids = [aws_subnet.medusa_subnet_1.id, aws_subnet.medusa_subnet_2.id] +} + +resource "aws_security_group" "medusa_redis_sg" { + name = "medusa-redis-sg" + description = "Security group for Medusa Redis" + vpc_id = aws_vpc.medusa_vpc.id + depends_on = [aws_security_group.medusa_sg] + ingress { + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.medusa_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "medusa-redis-sg" + } +} + +resource "aws_elasticache_cluster" "medusa_redis" { + cluster_id = "medusa-redis" + engine = "redis" + engine_version = 6.2 + node_type = "cache.t3.micro" + num_cache_nodes = 1 + parameter_group_name = "default.redis6.x" + port = 6379 + subnet_group_name = aws_elasticache_subnet_group.medusa_redis_subnet_group.name + security_group_ids = [aws_security_group.medusa_redis_sg.id] + snapshot_retention_limit = 7 + apply_immediately = true + lifecycle { + prevent_destroy = true + ignore_changes = [engine_version] + } +} +# # Store Redis URL in SSM Parameter Store +# resource "aws_ssm_parameter" "redis_url" { +# name = "/medusa/REDIS_URL" +# type = "SecureString" +# value = "redis://${aws_elasticache_cluster.medusa_redis.cache_nodes[0].address}:${aws_elasticache_cluster.medusa_redis.port}" +# } \ No newline at end of file diff --git a/Terraform/terraform.tfvars b/Terraform/terraform.tfvars new file mode 100644 index 00000000..25514fbb --- /dev/null +++ b/Terraform/terraform.tfvars @@ -0,0 +1,6 @@ +aws_region = "eu-west-3" +domain_name = "yourdomain.com" +# this as examples uses neon db, it can be any other db URL +neon_db_url= "neon-db.cjxjxjxjxjxj.eu-west-3.rds.amazonaws.com" +subdomain ="" # could be any subdomain, like "app" or "manage" +certificate_domain="*.yourdomain.com" \ No newline at end of file diff --git a/Terraform/variables.tf b/Terraform/variables.tf new file mode 100644 index 00000000..9494ecdc --- /dev/null +++ b/Terraform/variables.tf @@ -0,0 +1,34 @@ +# Define the AWS region variable +variable "aws_region" { + description = "The AWS region to deploy resources" + type = string + default = "eu-west-3" # optional, default value if not provided in terraform.tfvars +} + +# Define the domain name variable +variable "domain_name" { + description = "The domain name to be used" + type = string +} +variable "subdomain" { + description = "The subdomain for the Medusa application" + type = string +} +variable "certificate_domain" { + description = "The domain name of the existing ACM certificate" + type = string +} +variable "neon_db_url" { + description = "The connection URL for Neon DB" + type = string +} +variable "common_tags" { + description = "Common tags to be applied to all resources" + type = map(string) + default = { + Environment = "production" + Project = "medusa" + Terraform = "true" + ManagedBy = "terraform" + } +} \ No newline at end of file diff --git a/admin/Dockerfile b/admin/Dockerfile deleted file mode 100644 index cee362e9..00000000 --- a/admin/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM node:latest - -WORKDIR /app/admin - -COPY . . - -RUN rm -rf node_modules - -RUN apt-get update - -RUN npm install -g npm@latest - -RUN npm install sharp - -RUN npm install -g gatsby-cli - -RUN npm install --loglevel=error - -RUN npm run build &> /dev/null - -CMD [ "gatsby", "develop", "-H", "0.0.0.0", "-p", "7000" ] \ No newline at end of file diff --git a/admin/Dockerfile.prod b/admin/Dockerfile.prod deleted file mode 100644 index 6b3fe704..00000000 --- a/admin/Dockerfile.prod +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:17.1.0 as builder - -WORKDIR /app/admin - -ENV NODE_OPTIONS=--openssl-legacy-provider - -COPY . . - -RUN rm -rf node_modules - -RUN apt-get update - -RUN yarn add sharp - -RUN yarn global add gatsby-cli - -RUN yarn install - -RUN gatsby build - -FROM nginx - -EXPOSE 80 - -COPY --from=builder /app/admin/public /usr/share/nginx/html - -ENTRYPOINT ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 9481f1de..00000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM node:17.1.0 - -WORKDIR /app/medusa - -COPY package.json . - -RUN apt-get update - -RUN apt-get install -y python - -RUN npm install -g npm@latest - -RUN npm install -g @medusajs/medusa-cli@latest - -RUN npm install --loglevel=error - -COPY . . - -ENTRYPOINT ["./develop.sh", "develop"] diff --git a/backend/Dockerfile.prod b/backend/Dockerfile.prod deleted file mode 100644 index 9e71631b..00000000 --- a/backend/Dockerfile.prod +++ /dev/null @@ -1,47 +0,0 @@ -FROM node:17.1.0 as builder - -WORKDIR /app/medusa - -COPY . . - -RUN rm -rf node_modules - -RUN apt-get update - -RUN apt-get install -y python - -RUN npm install -g npm@latest - -RUN npm install --loglevel=error - -RUN npm run build - - -FROM node:17.1.0 - -WORKDIR /app/medusa - -RUN mkdir dist - -COPY package*.json ./ - -COPY develop.sh . - -COPY .env . - -COPY medusa-config.js . - -RUN apt-get update - -RUN apt-get install -y python -# RUN apk add --no-cache python3 - -RUN npm install -g @medusajs/medusa-cli - -RUN npm i --only=production - -COPY --from=builder /app/medusa/dist ./dist - -EXPOSE 9000 - -ENTRYPOINT ["./develop.sh", "start"] \ No newline at end of file diff --git a/backend/develop.sh b/backend/develop.sh deleted file mode 100755 index 88ef918b..00000000 --- a/backend/develop.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -medusa migrations run - -medusa $1 diff --git a/docker-compose.override.yml b/docker-compose.override.yml deleted file mode 100644 index e00c394d..00000000 --- a/docker-compose.override.yml +++ /dev/null @@ -1,25 +0,0 @@ -version: "3.8" -services: - backend: - environment: - NODE_ENV: development - volumes: - - ./backend:/app/medusa - - backend_node_modules:/app/medusa/node_modules - ports: - - "9000:9000" - - admin: - ports: - - "7000:7000" - - storefront: - volumes: - - ./storefront:/app/storefront - - storefront_node_modules:/app/storefront/node_modules - ports: - - "8000:8000" - -volumes: - storefront_node_modules: - backend_node_modules: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml deleted file mode 100644 index ee435ba7..00000000 --- a/docker-compose.prod.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: "3.8" -services: - backend: - build: - dockerfile: Dockerfile.prod - image: backend:latest - container_name: medusa-server - restart: always - depends_on: - - postgres - - redis - environment: - DATABASE_URL: postgres://postgres:postgres@postgres:5432/medusa-docker - NODE_ENV: production - STORE_CORS: http://localhost - ports: - - "9000:9000" - - postgres: - restart: always - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: medusa-docker - - redis: - restart: always - - admin: - build: - context: ./admin - dockerfile: Dockerfile.prod - image: admin:latest - restart: always - depends_on: - - backend - container_name: medusa-admin - environment: - NODE_ENV: production - NODE_OPTIONS: --openssl-legacy-provider - ports: - - "7000:80" - - storefront: - build: - dockerfile: Dockerfile.prod - image: storefront:latest - container_name: medusa-storefront - restart: always - environment: - NODE_ENV: production - depends_on: - - backend - ports: - - "80:80" diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index a6255724..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: "3.8" -services: - backend: - build: - context: ./backend - dockerfile: Dockerfile - image: backend:test - container_name: medusa-server - depends_on: - - postgres - - redis - environment: - DATABASE_URL: postgres://postgres:postgres@postgres:5432/medusa-docker - REDIS_URL: redis://cache - NODE_ENV: development - JWT_SECRET: some_jwt_secret - COOKIE_SECRET: some_cookie_secret - PORT: 9000 - - postgres: - image: postgres:10.4 - ports: - - "5432:5432" - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: medusa-docker - - redis: - image: redis - container_name: cache - expose: - - 6379 - - admin: - build: - context: ./admin - dockerfile: Dockerfile - image: admin:test - depends_on: - - backend - container_name: medusa-admin - environment: - NODE_OPTIONS: --openssl-legacy-provider - - storefront: - build: - context: ./storefront - dockerfile: Dockerfile - image: storefront:test - container_name: medusa-storefront - depends_on: - - backend diff --git a/storefront/Dockerfile b/storefront/Dockerfile deleted file mode 100644 index a6450ce2..00000000 --- a/storefront/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM node:latest - -WORKDIR /app/storefront - -COPY . . - -RUN rm -rf node_modules - -RUN apt-get update - -RUN npm install -g npm@latest - -RUN npm install -g gatsby-cli - -RUN npm install sharp - -RUN npm install --loglevel=error - -ENTRYPOINT ["gatsby", "develop", "-H", "0.0.0.0", "-p", "8000" ] \ No newline at end of file diff --git a/storefront/Dockerfile.prod b/storefront/Dockerfile.prod deleted file mode 100644 index ca02bb2b..00000000 --- a/storefront/Dockerfile.prod +++ /dev/null @@ -1,25 +0,0 @@ -FROM node:17.1.0 as builder - -WORKDIR /app/storefront - -COPY . . - -RUN rm -rf node_modules - -RUN apt-get update - -RUN yarn global add gatsby-cli - -RUN yarn add sharp - -RUN yarn install - -RUN gatsby build - -FROM nginx - -EXPOSE 80 - -COPY --from=builder /app/storefront/public /usr/share/nginx/html - -ENTRYPOINT ["nginx", "-g", "daemon off;"] \ No newline at end of file From 51cca6a898cea20c3b38712a3fd9ddf23dafe56c Mon Sep 17 00:00:00 2001 From: brooktewabe <101641521+brooktewabe@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:42:09 +0300 Subject: [PATCH 2/2] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 44978415..b039afda 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This repository contains the infrastructure as code (IaC) setup using Terraform The infrastructure setup includes: - ECS Fargate for container orchestration - Application Load Balancer (ALB) for traffic distribution +- AWS Fargate instances with auto scaling - ElastiCache Redis for caching - Neon Database (external) for PostgreSQL - Route53 for DNS management @@ -154,4 +155,4 @@ The following environment variables are configured in the ECS task definition: ## License -This project is licensed under the MIT License - see the LICENSE file for details. \ No newline at end of file +This project is licensed under the MIT License - see the LICENSE file for details.