Terraform code that moves a microservices platform from on-premise to AWS.
The platform runs 8 microservices (Java/Node.js) behind an Nginx load balancer, with a PostgreSQL database. This project recreates that setup on AWS using Infrastructure as Code, with full CI/CD via GitHub Actions.
The full design document is in docs/architecture_document.html.
In short:
- VPC with 2 Availability Zones for high availability
- ALB (Application Load Balancer) in the public subnets, with Route 53 DNS
- EC2 Auto Scaling Group in the private subnets, running Docker containers
- RDS PostgreSQL in Multi-AZ mode, encrypted, with 7-day backups
- IAM Roles for both GitHub Actions (OIDC) and the EC2 instances
- Security Groups wired with least-privilege rules between every layer
- Secrets Manager holds the DB password (never in code)
- CloudWatch Log Groups and Alarms (CPU > 80%, ALB 5xx > 1%)
To run anything locally you need:
- Terraform 1.5 or newer (download)
- Git to clone the repo
- (optional) AWS CLI if you want to apply against a real account
Nothing else needs to be set up for terraform plan to work — the AWS provider
is configured with mock credentials and skip_* flags so plan runs offline.
git clone https://github.com/Eytan123123/AWSdevops.git
cd AWSdevops/terraform
terraform init -backend=false
terraform planThat's it. Terraform will show you everything it would create in AWS.
Why -backend=false? The repo defines the S3 bucket and DynamoDB table
that would hold Terraform state, but does not wire them as the active backend
(they ship "configured, not applied" per the spec). The flag tells init to
skip backend setup so plan runs purely offline. In a real deployment you'd
apply those two resources first, then add a backend "s3" block and re-init
without the flag.
Two workflows in .github/workflows/:
Runs on every push to any branch, and on every PR to main.
It validates the code without touching AWS:
terraform fmt -check— formattingtflint— best practicestfsec— security scanterraform init -backend=falseterraform validate— config correctnessterraform plan— full plan (offline, no AWS)- If the run is for a PR, the plan is posted as a comment on the PR
Runs only on push to main (i.e. after a PR merge).
- Runs
terraform plan -out=tfplan - Uploads the binary
tfplanas a workflow artifact (retained 30 days) - Posts a readable summary on the Actions job page
CD never runs terraform apply — applying is left to humans, per the project spec.
Just git push. The workflows pick it up automatically based on the trigger.
To see a run:
- Go to the repo on GitHub
- Click the Actions tab
- Pick a workflow on the left
AWSdevops/
├── architecture.png # Stage 1 diagram
├── README.md # This file
├── docs/ # Architecture doc + assignment PDF
├── terraform/
│ ├── main.tf # Root: wires modules together
│ ├── variables.tf # All configurable values
│ ├── outputs.tf # Key resource IDs (VPC, ALB, RDS)
│ └── modules/
│ ├── vpc/ # VPC + subnets + IGW + NAT + route tables
│ ├── iam/ # OIDC + IAM Roles + SGs + Secrets Manager
│ ├── rds/ # PostgreSQL Multi-AZ
│ ├── alb/ # ALB + Target Group + Listener + Route 53
│ └── ec2/ # Launch Template + ASG + Log Groups + Alarms
└── .github/
└── workflows/
├── ci.yml # Validate, plan, comment on PR
└── cd.yml # Plan, archive artifact (no apply)
A few resources must exist in AWS before the project can be applied properly. They break the chicken-and-egg problem of "Terraform needs AWS to create AWS":
- OIDC Identity Provider for GitHub — defined in
modules/iam/main.tf, needed so the CI workflow can assume an IAM Role via OIDC instead of long-lived access keys. github-actions-terraformIAM Role — also inmodules/iam/main.tf, with read-only permissions forterraform plan.- S3 bucket
aws-migration-tfstate-eytan— defined at the root interraform/main.tf, would hold the Terraform state. - DynamoDB table
terraform-state-lock— also at the root, would provide state locking so two concurrent runs cannot corrupt the state.
All four are written as Terraform resources in the repo. They show up in
terraform plan so anyone reading the code can see the intended bootstrap,
but the project never runs apply — these are "configured, not applied" per
the project spec.
In a real deployment they would be applied once, manually, by an admin with their own access keys. After that, the rest of the project runs through CI without any long-lived credentials anywhere.
# 1. Configure the admin's AWS credentials locally (one-time)
aws configure
# 2. Apply only the bootstrap resources (S3 bucket + DynamoDB + IAM/OIDC)
cd terraform
terraform init
terraform apply -target=aws_s3_bucket.tfstate \
-target=aws_dynamodb_table.tfstate_lock \
-target=module.iam.aws_iam_openid_connect_provider.github \
-target=module.iam.aws_iam_role.github_actions
# 3. Add a backend "s3" block to main.tf:
#
# terraform {
# backend "s3" {
# bucket = "aws-migration-tfstate-eytan"
# key = "terraform/state.tfstate"
# region = "eu-west-1"
# dynamodb_table = "terraform-state-lock"
# encrypt = true
# }
# }
# 4. Move the local state into S3
terraform init -migrate-state
# 5. Copy the github_actions_role_arn output into the repo as a Secret
# (Settings → Secrets and variables → Actions → AWS_ROLE_ARN)From this point on CI/CD uses OIDC and the S3 backend — no more manual steps.
terraform applyis never run by the pipelines. The assignment requires a green plan, not an actual deployment.- The AWS provider uses mock credentials and
skip_*flags so plan succeeds without contacting AWS. - All secrets live in either AWS Secrets Manager or GitHub Actions Secrets — never in source code.
- All resources are tagged with
NameandEnvironment.
