diff --git a/.gitignore b/.gitignore
index 856839462ccf382a13498c5fdce6b014bb43bcde..3377d94390180530dfaa2f15939b6f2ede90f68d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -49,8 +49,11 @@ dist/
 *.env
 .bundle/
 !Pipfile.lock
+!pdm.lock
+!Gemfile.lock
 .terraform
 .terraform.*
+!.terraform.lock.hcl
 tfplan
 *.tfstate*
 *.venv
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 833e902ada517548318a2fc9cfd1c1d4cd3514dd..8d09adde407c69b24da518d3c48a24fe4f7287d0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -2,3 +2,28 @@
 include:
   - project: shore/ci-stuff
     file: templates/pre-commit.yml
+  - project: shore/ci-stuff
+    file: templates/terraform.yml
+
+default:
+  before_script:
+    - apt-get update
+    - apt-get install -y terraform
+
+AWS Terraform plan:
+  extends: .tf_plan
+  stage: test
+  #rules: &aws_tf_rules
+  #  - changes:
+  #      - ${TF_ROOT}/
+  variables: &aws_tf_vars
+    TF_ROOT: Terraform/AWS
+
+AWS Terraform apply:
+  extends: .tf_apply
+  stage: deploy
+  #rules: *aws_tf_rules
+  needs:
+    - job: AWS Terraform plan
+      artifacts: true
+  variables: *aws_tf_vars
diff --git a/Ansible/roles/router/files/nsd/shore.co.il b/Ansible/roles/router/files/nsd/shore.co.il
index 6b987f394ca6f4c489ba8c5be75e53b5f7d712ed..ffae98d495aa20db44284bb65a70471046bc5dd0 100644
--- a/Ansible/roles/router/files/nsd/shore.co.il
+++ b/Ansible/roles/router/files/nsd/shore.co.il
@@ -2,7 +2,7 @@
 $TTL 1h
 $ORIGIN shore.co.il.
 @               IN      SOA     ns1     hostmaster (
-        2021051901
+        2022041003
         1h
         5m
         4w
@@ -85,3 +85,8 @@ host01._domainkey IN    TXT     ("v=DKIM1\; k=rsa\;"
 
 _adsp._domainkey        IN      TXT     "dkim=all;"
 _dmarc  IN      TXT     "v=DMARC1;p=quarantine;pct=100;sp=reject;fo=1;rua=mailto:postmaster@shore.co.il;ruf=mailto:postmaster@shore.co.il;adkim=s;aspf=s"
+
+aws     IN      NS      ns-117.awsdns-14.com.
+aws     IN      NS      ns-1352.awsdns-41.org.
+aws     IN      NS      ns-1664.awsdns-16.co.uk.
+aws     IN      NS      ns-750.awsdns-29.net.
diff --git a/Terraform/AWS/.terraform.lock.hcl b/Terraform/AWS/.terraform.lock.hcl
new file mode 100644
index 0000000000000000000000000000000000000000..c6c8a13e6ebc712af2f5b6e5a34b3b43fd5d8896
--- /dev/null
+++ b/Terraform/AWS/.terraform.lock.hcl
@@ -0,0 +1,22 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version     = "4.9.0"
+  constraints = "~> 4.0"
+  hashes = [
+    "h1:GtmIOZMkKmr9tMLWouHWiGXmKEL/diOTNar5XfOVLjs=",
+    "zh:084b83aef3335ad4f5e4b8323c6fe43c1ff55e17a7647c6a5cad6af519f72b42",
+    "zh:132e47ce69f14de4523b84b213cedf7173398acda14245b1ffe7747aac50f050",
+    "zh:2068baef7dfce3613f3b4f27314175e971f8db68d9cde9ec30b5659f80c68c6c",
+    "zh:63c6f489683d5f1ac55e82a0df387143ed22701d5f22c109a4d5c9924dd4e437",
+    "zh:8115fd21965954fa4568c09331e05bb29da967fab8d077419aed09954378e216",
+    "zh:8efdc95fde108f777ed9c79ae25dc17aea9771903250f5c5c8a4c726b90a345f",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:9d42a7bc34d84b70c1d1bcc215cabd63abbcbd0352b70bd84da6c3916634932f",
+    "zh:aacbcceb241aa475888c0869e87593182edeced3170c76a0c960dd9c905df449",
+    "zh:c7fe7904511052e4102870256819a1917177572cf684f0611ebf767f9c1fbaa8",
+    "zh:c8e07c3424663d1d0e7e32f4ade8099c19f6326d37c6da98104d90c986ff66fc",
+    "zh:e47cafbd38b56ef14fd8d727b4ffea847c166b1c684f585ee5fb78983b537248",
+  ]
+}
diff --git a/Terraform/AWS/main.tf b/Terraform/AWS/main.tf
new file mode 100644
index 0000000000000000000000000000000000000000..128ca23dff3b4936f19d2de0b08816d015133819
--- /dev/null
+++ b/Terraform/AWS/main.tf
@@ -0,0 +1,152 @@
+terraform {
+  backend "http" {}
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "~> 4.0"
+    }
+  }
+}
+
+locals {
+  env     = terraform.workspace == "default" ? "prod" : terraform.workspace
+  module  = basename(abspath(path.root))
+  name    = "${local.project}-${local.module}-${local.env}"
+  project = "homelab"
+  common_tags = {
+    Environment = local.env
+    Module      = local.module
+    Name        = local.name
+    Project     = local.project
+  }
+}
+
+output "env" {
+  description = "Environment (prod/dev etc.)."
+  value       = local.env
+}
+
+output "module" {
+  description = "The name of the Terraform module, used to tagging resources."
+  value       = local.module
+}
+
+output "project" {
+  description = "The name of the Git project, used to tagging resources."
+  value       = local.project
+}
+
+variable "region" {
+  default     = "us-east-1"
+  description = "AWS region."
+  type        = string
+}
+
+output "region" {
+  description = "AWS region."
+  value       = var.region
+}
+
+provider "aws" {
+  region = var.region
+  default_tags {
+    tags = local.common_tags
+  }
+}
+
+data "aws_caller_identity" "current" {}
+
+locals {
+  account_id = data.aws_caller_identity.current.account_id
+}
+
+output "account_id" {
+  description = "The AWS account ID."
+  value       = local.account_id
+}
+
+data "aws_iam_policy_document" "ec2_assume_policy" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+
+    principals {
+      type        = "Service"
+      identifiers = ["ec2.amazonaws.com"]
+    }
+  }
+}
+
+locals {
+  ec2_assume_policy = data.aws_iam_policy_document.ec2_assume_policy.json
+}
+
+output "ec2_assume_policy" {
+  value       = local.ec2_assume_policy
+  description = "IAM policy document for EC2 instance assuming a role."
+}
+
+data "aws_iam_policy_document" "task_assume_policy" {
+  statement {
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+
+    principals {
+      type        = "Service"
+      identifiers = ["ecs-tasks.amazonaws.com"]
+    }
+  }
+}
+
+locals {
+  task_assume_policy = data.aws_iam_policy_document.task_assume_policy.json
+}
+
+output "task_assume_policy" {
+  description = "IAM policy document for ECS tasks assuming a role."
+  value       = local.task_assume_policy
+}
+
+locals {
+  resource_group_query = {
+    ResourceTypeFilters = [
+      "AWS::AllSupported",
+    ]
+    TagFilters = [
+      {
+        Key    = "Environment"
+        Values = [local.env, ]
+      },
+      {
+        Key    = "Module"
+        Values = [local.module, ]
+      },
+      {
+        Key    = "Project"
+        Values = [local.project, ]
+      },
+    ]
+  }
+}
+
+resource "aws_resourcegroups_group" "group" {
+  name = local.name
+  resource_query {
+    query = jsonencode(local.resource_group_query)
+  }
+}
+
+locals {
+  resource_group_arn  = aws_resourcegroups_group.group.arn
+  resource_group_name = aws_resourcegroups_group.group.name
+}
+
+output "resource_group_arn" {
+  description = "ARN of the resource group."
+  value       = local.resource_group_arn
+}
+
+output "resource_group_name" {
+  description = "Name of the resource group."
+  value       = local.resource_group_name
+}
diff --git a/Terraform/AWS/route53.tf b/Terraform/AWS/route53.tf
new file mode 100644
index 0000000000000000000000000000000000000000..fc772abf1b443f05b826a63d871b5e0b3f4260d1
--- /dev/null
+++ b/Terraform/AWS/route53.tf
@@ -0,0 +1,52 @@
+resource "aws_route53_zone" "zone" {
+  name = "aws.shore.co.il"
+}
+
+locals {
+  dns_zone_arn          = aws_route53_zone.zone.arn
+  dns_zone_id           = aws_route53_zone.zone.zone_id
+  dns_zone_name         = aws_route53_zone.zone.name
+  dns_zone_name_servers = aws_route53_zone.zone.name_servers
+}
+
+output "dns_zone_arn" {
+  description = "ARN of the Route53 DNS zone."
+  value       = local.dns_zone_arn
+}
+
+output "dns_zone_id" {
+  description = "ID of the Route53 DNS zone."
+  value       = local.dns_zone_id
+}
+
+output "dns_zone_name" {
+  description = "Name of the Router53 DNS zone."
+  value       = local.dns_zone_name
+}
+
+output "dns_zone_name_servers" {
+  description = "List of name servers of the Route53 DNS zone."
+  value       = local.dns_zone_name_servers
+}
+
+locals {
+  aws_caa_domain_names = [
+    "amazon.com",
+    "amazontrust.com",
+    "awstrust.com",
+    "amazonaws.com",
+  ]
+}
+
+resource "aws_route53_record" "caa" {
+  name = local.dns_zone_name
+  records = [
+    "0 issue \"amazon.com\"",
+    "0 issue \"amazontrust.com\"",
+    "0 issue \"awstrust.com\"",
+    "0 issue \"amazonaws.com\"",
+  ]
+  ttl     = 86400
+  type    = "CAA"
+  zone_id = local.dns_zone_id
+}
diff --git a/Terraform/AWS/vpc.tf b/Terraform/AWS/vpc.tf
new file mode 100644
index 0000000000000000000000000000000000000000..a5691ffdcabfbd8dade1095c3ff6b4c38b539b8d
--- /dev/null
+++ b/Terraform/AWS/vpc.tf
@@ -0,0 +1,304 @@
+variable "cidr_block" {
+  default     = "172.31.0.0/16"
+  description = "CIDR block for the VPC."
+  type        = string
+}
+
+output "cidr_block" {
+  description = "CIDR block for the VPC."
+  value       = var.cidr_block
+}
+
+resource "aws_vpc" "vpc" {
+  cidr_block = var.cidr_block
+  tags = {
+    Name = local.env,
+  }
+}
+
+locals {
+  vpc_arn  = aws_vpc.vpc.arn
+  vpc_id   = aws_vpc.vpc.id
+  vpc_name = aws_vpc.vpc.tags.Name
+}
+
+output "vpc_arn" {
+  description = "ARN of the VPC."
+  value       = local.vpc_arn
+}
+
+output "vpc_id" {
+  description = "ID of the VPC."
+  value       = local.vpc_id
+}
+
+output "vpc_name" {
+  description = "Name of the VPC."
+  value       = local.vpc_name
+}
+
+resource "aws_internet_gateway" "gateway" {
+  vpc_id = local.vpc_id
+  tags = {
+    Name = local.env,
+  }
+}
+
+locals {
+  internet_gateway_arn  = aws_internet_gateway.gateway.arn
+  internet_gateway_id   = aws_internet_gateway.gateway.id
+  internet_gateway_name = aws_internet_gateway.gateway.tags["Name"]
+}
+
+output "internet_gateway_arn" {
+  description = "ARN of the internet gateway."
+  value       = local.internet_gateway_arn
+}
+
+output "internet_gateway_id" {
+  description = "ID of the internet gateway."
+  value       = local.internet_gateway_id
+}
+
+output "internet_gateway_name" {
+  description = "Name of the internet gateway."
+  value       = local.internet_gateway_name
+}
+
+variable "subnet_count" {
+  default     = 3
+  description = "Number of each private and public subnets."
+  type        = number
+}
+
+output "subnet_count" {
+  description = "Number of each private and public subnets."
+  value       = var.subnet_count
+}
+
+locals {
+  az_mapping = {
+    1 = "a"
+    2 = "b"
+    3 = "c"
+    4 = "d"
+    5 = "e"
+    6 = "f"
+    7 = "g"
+    8 = "h"
+    9 = "i"
+  }
+}
+
+resource "aws_subnet" "private" {
+  count                   = var.subnet_count
+  availability_zone       = "${var.region}${local.az_mapping[count.index + 1]}"
+  cidr_block              = cidrsubnet(var.cidr_block, 8, count.index)
+  map_public_ip_on_launch = false
+  vpc_id                  = local.vpc_id
+  tags = {
+    Name = "${local.env}-private-${local.az_mapping[count.index + 1]}"
+    Type = "private"
+  }
+}
+
+locals {
+  private_subnet_arns  = aws_subnet.private.*.arn
+  private_subnet_ids   = aws_subnet.private.*.id
+  private_subnet_names = [for i in aws_subnet.private.*.tags : i["Name"]]
+}
+
+output "private_subnet_arns" {
+  description = "List of private subnets ARNs."
+  value       = local.private_subnet_arns
+}
+
+output "private_subnet_ids" {
+  description = "List of private subnets IDs."
+  value       = local.private_subnet_ids
+}
+
+output "private_subnet_names" {
+  description = "List of private subnets names."
+  value       = local.private_subnet_names
+}
+
+resource "aws_subnet" "public" {
+  count                   = var.subnet_count
+  availability_zone       = "${var.region}${local.az_mapping[count.index + 1]}"
+  cidr_block              = cidrsubnet(var.cidr_block, 8, var.subnet_count + count.index)
+  map_public_ip_on_launch = true
+  vpc_id                  = local.vpc_id
+  tags = {
+    Name = "${local.env}-public-${local.az_mapping[count.index + 1]}"
+    Type = "public"
+  }
+}
+
+locals {
+  public_subnet_arns  = aws_subnet.public.*.arn
+  public_subnet_ids   = aws_subnet.public.*.id
+  public_subnet_names = [for i in aws_subnet.public.*.tags : i["Name"]]
+}
+
+output "public_subnet_arns" {
+  description = "List of public subnets ARNs."
+  value       = local.public_subnet_arns
+}
+
+output "public_subnet_ids" {
+  description = "List of public subnets IDs."
+  value       = local.public_subnet_ids
+}
+
+output "public_subnet_names" {
+  description = "List of public subnets names."
+  value       = local.public_subnet_names
+}
+
+resource "aws_eip" "nat_eip" {
+  count = var.subnet_count
+  vpc   = true
+  tags = {
+    Name = "${local.env}-${local.az_mapping[count.index + 1]}"
+  }
+  depends_on = [
+    aws_internet_gateway.gateway,
+  ]
+}
+
+locals {
+  nat_gateway_eip_ids   = aws_eip.nat_eip.*.id
+  nat_gateway_eip_names = [for i in aws_eip.nat_eip.*.tags : i["Name"]]
+}
+
+output "nat_gateway_eip_ids" {
+  description = "List of Elastic IP IDs for the NAT gateway."
+  value       = local.nat_gateway_eip_ids
+}
+
+output "nat_gateway_eip_names" {
+  description = "List of Elastic IP names for the NAT gateway."
+  value       = local.nat_gateway_eip_names
+}
+
+resource "aws_nat_gateway" "gateway" {
+  count         = var.subnet_count
+  allocation_id = local.nat_gateway_eip_ids[count.index]
+  subnet_id     = local.public_subnet_ids[count.index]
+  tags = {
+    Name = "${local.env}-${local.az_mapping[count.index + 1]}"
+  }
+}
+
+locals {
+  nat_gateway_ids   = aws_nat_gateway.gateway.*.id
+  nat_gateway_names = [for i in aws_nat_gateway.gateway.*.tags : i["Name"]]
+}
+
+output "nat_gateway_ids" {
+  description = "List of NAT gateway IDs."
+  value       = local.nat_gateway_ids
+}
+
+output "nat_gateway_names" {
+  description = "List of NAT gateway names."
+  value       = local.nat_gateway_names
+}
+
+resource "aws_route_table" "public" {
+  vpc_id = local.vpc_id
+  tags = {
+    Name = "${local.env}-public"
+  }
+
+  route {
+    cidr_block = "0.0.0.0/0"
+    gateway_id = local.internet_gateway_id
+  }
+}
+
+locals {
+  public_route_table_arn  = aws_route_table.public.arn
+  public_route_table_id   = aws_route_table.public.id
+  public_route_table_name = aws_route_table.public.tags["Name"]
+}
+
+output "public_route_table_arn" {
+  description = "ARN of the routing table for the public subnets."
+  value       = local.public_route_table_arn
+}
+
+output "public_route_table_id" {
+  description = "ID of the routing table for the public subnets."
+  value       = local.public_route_table_id
+}
+
+output "public_route_table_name" {
+  description = "Name of the routing table for the public subnets."
+  value       = local.public_route_table_name
+}
+
+resource "aws_route_table_association" "public" {
+  for_each       = toset(local.public_subnet_ids)
+  route_table_id = local.public_route_table_id
+  subnet_id      = each.key
+}
+
+locals {
+  public_route_table_association_ids = [for i in aws_route_table_association.public : i.id]
+}
+
+output "public_route_table_association_ids" {
+  description = "List of the route table associations IDs for the public subnets."
+  value       = local.public_route_table_association_ids
+}
+
+resource "aws_route_table" "private" {
+  for_each = toset(local.nat_gateway_ids)
+  vpc_id   = local.vpc_id
+  tags = {
+    Name = "${local.env}-private-${local.az_mapping[index(local.nat_gateway_ids, each.key) + 1]}"
+  }
+
+  route {
+    cidr_block     = "0.0.0.0/0"
+    nat_gateway_id = each.key
+  }
+}
+
+locals {
+  private_route_table_arns  = [for i in aws_route_table.private : i.arn]
+  private_route_table_ids   = [for i in aws_route_table.private : i.id]
+  private_route_table_names = [for i in aws_route_table.private : i.tags["Name"]]
+}
+
+output "private_route_table_arns" {
+  description = "List of ARNs of the routing tables for the private subnets."
+  value       = local.private_route_table_arns
+}
+
+output "private_route_table_ids" {
+  description = "List of IDs of the routing tables for the private subnets."
+  value       = local.private_route_table_ids
+}
+
+output "private_route_table_names" {
+  description = "List of names of the routing tables for the private subnets."
+  value       = local.private_route_table_names
+}
+
+resource "aws_route_table_association" "private" {
+  for_each       = zipmap(local.private_subnet_ids, local.private_route_table_ids)
+  route_table_id = each.value
+  subnet_id      = each.key
+}
+
+locals {
+  private_route_table_association_ids = [for i in aws_route_table_association.private : i.id]
+}
+
+output "private_route_table_association_ids" {
+  description = "List of the route table associations IDs for the private subnets."
+  value       = local.private_route_table_association_ids
+}