Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/ecr-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 'main'
- 'integration'

jobs:
ecr-publish:
Expand All @@ -25,7 +26,13 @@ jobs:

- name: Build and push Docker image
run: |
docker build -t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/microhealthllc/bo:latest .
# Build image from docker/app/Dockerfile
docker build \
-f docker/app/Dockerfile \
-t ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/microhealthllc/bo:latest \
docker/app

# Push image
docker push ${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.${{ secrets.AWS_REGION }}.amazonaws.com/microhealthllc/bo:latest

- name: Force ECS Service Update
Expand Down
58 changes: 21 additions & 37 deletions config/puma.rb
Original file line number Diff line number Diff line change
@@ -1,49 +1,33 @@
require 'yaml'
# frozen_string_literal: true
require "yaml"

# Load optional YAML config
if File.exist?('./puma_config.yml')
yaml_data = YAML.load_file('./puma_config.yml')
yaml_data.each do |key, value|
ENV[key] = value if value
end
if File.exist?("./puma_config.yml")
yaml_data = YAML.safe_load(File.read("./puma_config.yml")) || {}
yaml_data.each { |k, v| ENV[k.to_s] = v.to_s if v }
end

rails_env = ENV.fetch('RAILS_ENV')
puma_port = Integer(ENV.fetch('PUMA_PORT'))
rails_env = ENV.fetch("RAILS_ENV", "production")
# Support either PUMA_PORT or PORT, fallback to 8443
puma_port = Integer(ENV["PUMA_PORT"] || ENV["PORT"] || 8443)

max_threads_count = ENV.fetch('RAILS_MAX_THREADS', 5)
min_threads_count = ENV.fetch('RAILS_MIN_THREADS', max_threads_count)
max_threads_count = Integer(ENV.fetch("RAILS_MAX_THREADS", 5))
min_threads_count = Integer(ENV.fetch("RAILS_MIN_THREADS", max_threads_count))

workers Integer(ENV.fetch('WEB_CONCURRENCY', 2))
workers Integer(ENV.fetch("WEB_CONCURRENCY", 2))
threads min_threads_count, max_threads_count

environment rails_env

# Optional logging for debug
puts "[Puma] ENV PUMA_PORT=#{puma_port}"
puts "[Puma] ENV RAILS_ENV=#{rails_env}"

if rails_env == 'production'
pidfile ENV.fetch('PUMA_PIDFILE')

# Commented out SSL bind since Nginx handles TLS
# begin
# ssl_bind(
# ENV.fetch('PUMA_SSL_HOST'),
# puma_port,
# key: ENV.fetch('PUMA_SSL_KEY_FILE'),
# cert: ENV.fetch('PUMA_SSL_CERT_FILE'),
# verify_mode: ENV.fetch('PUMA_SSL_VERIFY_MODE')
# )
# puts "[Puma] SSL bind successful on #{ENV['PUMA_SSL_HOST']}:#{puma_port}"
# rescue KeyError => e
# warn "[Puma] Missing SSL ENV variable: #{e.message}"
# exit 1
# end

port puma_port
else
port puma_port
end

STDOUT.puts "[Puma] ENV PORT=#{ENV['PORT']}"
STDOUT.puts "[Puma] ENV PUMA_PORT=#{ENV['PUMA_PORT']}"
STDOUT.puts "[Puma] RAILS_ENV=#{rails_env}"

# PID file
pidfile ENV.fetch("PUMA_PIDFILE", File.join(Dir.pwd, "tmp/pids/server.pid"))

# We’re terminating TLS at ALB
bind "tcp://0.0.0.0:#{puma_port}"

plugin :tmp_restart
94 changes: 94 additions & 0 deletions deployment/deployment.drawio
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<mxfile host="65bd71144e">
<diagram id="8Qfl-K0iTt6Qi5x8oi-c" name="Page-1">
<mxGraphModel dx="558" dy="555" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="33" value="" style="edgeStyle=none;html=1;" parent="1" source="2" target="4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="35" value="" style="edgeStyle=none;html=1;" parent="1" source="2" target="4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="37" value="" style="edgeStyle=none;html=1;" parent="1" source="2" target="4" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="2" value="Users" style="shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top;html=1;outlineConnect=0;" parent="1" vertex="1">
<mxGeometry x="35" y="10" width="30" height="60" as="geometry"/>
</mxCell>
<mxCell id="3" value="" style="rounded=0;whiteSpace=wrap;html=1;" parent="1" vertex="1">
<mxGeometry x="10" y="170" width="500" height="440" as="geometry"/>
</mxCell>
<mxCell id="4" value="" style="outlineConnect=0;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;shape=mxgraph.aws3.cloud_2;fillColor=#F58534;gradientColor=none;" parent="1" vertex="1">
<mxGeometry x="10" y="140" width="33" height="30" as="geometry"/>
</mxCell>
<mxCell id="7" value="Amazon ECS" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.ecs;" parent="1" vertex="1">
<mxGeometry x="40" y="450" width="60" height="90" as="geometry"/>
</mxCell>
<mxCell id="8" value="mPATH BO" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="40" y="420" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="12" value="" style="edgeStyle=none;html=1;" parent="1" source="11" target="8" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="11" value="Application Load Balancer" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.application_load_balancer;" parent="1" vertex="1">
<mxGeometry x="35" y="280" width="70" height="110" as="geometry"/>
</mxCell>
<mxCell id="19" value="" style="edgeStyle=none;html=1;" parent="1" source="13" target="18" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="13" value="Application Load Balancer" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.application_load_balancer;" parent="1" vertex="1">
<mxGeometry x="155" y="280" width="70" height="110" as="geometry"/>
</mxCell>
<mxCell id="17" value="Amazon ECS" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.ecs;" parent="1" vertex="1">
<mxGeometry x="165" y="450" width="60" height="90" as="geometry"/>
</mxCell>
<mxCell id="18" value="mPATH DOS" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="160" y="420" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="29" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="22" target="23" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="22" value="Application Load Balancer" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.application_load_balancer;" parent="1" vertex="1">
<mxGeometry x="265" y="280" width="70" height="110" as="geometry"/>
</mxCell>
<mxCell id="23" value="mPATH DHA" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="270" y="420" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="24" value="Amazon ECS" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.ecs;" parent="1" vertex="1">
<mxGeometry x="270" y="450" width="60" height="90" as="geometry"/>
</mxCell>
<mxCell id="46" style="edgeStyle=none;html=1;" edge="1" parent="1" source="34" target="13">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="47" style="edgeStyle=none;html=1;" edge="1" parent="1" source="34" target="22">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="49" style="edgeStyle=none;html=1;" edge="1" parent="1" source="34" target="38">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="34" value="Amazon VPC" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.vpc;" parent="1" vertex="1">
<mxGeometry x="210" y="170" width="80" height="100" as="geometry"/>
</mxCell>
<mxCell id="43" style="edgeStyle=none;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="1" source="38" target="39" edge="1">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
<mxCell id="38" value="Application Load Balancer" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.application_load_balancer;" parent="1" vertex="1">
<mxGeometry x="380" y="280" width="70" height="110" as="geometry"/>
</mxCell>
<mxCell id="39" value="mPATH DOS-QA" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" parent="1" vertex="1">
<mxGeometry x="385" y="420" width="60" height="30" as="geometry"/>
</mxCell>
<mxCell id="40" value="Amazon ECS" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;strokeColor=#ffffff;fillColor=#232F3E;dashed=0;verticalLabelPosition=middle;verticalAlign=bottom;align=center;html=1;whiteSpace=wrap;fontSize=10;fontStyle=1;spacing=3;shape=mxgraph.aws4.productIcon;prIcon=mxgraph.aws4.ecs;" parent="1" vertex="1">
<mxGeometry x="380" y="450" width="60" height="90" as="geometry"/>
</mxCell>
<mxCell id="44" value="mPATH VPC" style="dashed=0;html=1;fillColor=#F0F2F5;strokeColor=none;align=center;rounded=1;arcSize=10;fontColor=#596780;fontStyle=1;fontSize=11;shadow=0" parent="1" vertex="1">
<mxGeometry x="210" y="170" width="80" height="20" as="geometry"/>
</mxCell>
<mxCell id="45" style="edgeStyle=none;html=1;entryX=0.12;entryY=0.227;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="1" source="34" target="3">
<mxGeometry relative="1" as="geometry"/>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
Binary file added deployment/ecs.zip
Binary file not shown.
41 changes: 41 additions & 0 deletions deployment/ecs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.DS_Store
*.zip
.sql
# Local .terraform directories
**/.terraform/*

.terraform*
.terraform.lock.hcl
# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc


6 changes: 3 additions & 3 deletions deployment/ecs/backend-prod.hcl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
bucket = "healthmetricsai-pub-terraform-state"
key = "state/terraform.tfstate"
bucket = "mpath-terraform-state"
key = "mpath/ecs/vpc/terraform.tfstate"
region = "us-east-1"
encrypt = true
dynamodb_table = "healthmetricsai-pub-terraform-state"
use_lockfile = true
5 changes: 5 additions & 0 deletions deployment/ecs/envs/bo/backend.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
bucket = "mpath-terraform-state"
key = "mpath/ecs/bo/terraform.tfstate"
region = "us-east-1"
encrypt = true
use_lockfile = true
100 changes: 100 additions & 0 deletions deployment/ecs/envs/bo/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 5.0" }
}
backend "s3" {} # init with: terraform init -backend-config=backend.hcl
}

provider "aws" { region = var.aws_region }

locals {
app_name = "mpath"
env = "bo"
}

# Pull shared network from the ROOT stack
data "terraform_remote_state" "root" {
backend = "s3"
config = {
bucket = "mpath-terraform-state"
key = "mpath/root/terraform.tfstate"
region = var.aws_region
encrypt = true
}
}

locals {
vpc_id = data.terraform_remote_state.root.outputs.vpc_id
private_subnet_ids = data.terraform_remote_state.root.outputs.private_subnet_ids
public_subnet_ids = data.terraform_remote_state.root.outputs.public_subnet_ids

tags = {
Project = local.app_name
Environment = local.env
ManagedBy = "Terraform"
}
}

module "ecs_service" {
source = "../../modules/ecs"


cluster_name = "${local.app_name}-${local.env}"
service_name = "${local.app_name}-app-${local.env}"

vpc_id = local.vpc_id
subnet_ids = local.private_subnet_ids # ECS tasks -> private subnets

# Container / service
container_image = var.container_image
container_port = var.container_port # set to 8443 in terraform.tfvars
desired_count = var.desired_count
cpu = var.cpu
memory = var.memory
health_check_path = var.health_check_path

# Create ALB/TG/listeners *inside* the module (per-env ALB)
create_alb = true
public_subnet_ids = local.public_subnet_ids
alb_name = "${local.app_name}-${local.env}-alb"
alb_deletion_protection = true
acm_certificate_arn = var.acm_certificate_arn
ssl_policy = var.ssl_policy

# Ops & deployments
platform_version = var.platform_version
deployment_maximum_percent = var.deployment_maximum_percent
deployment_minimum_healthy_percent = var.deployment_minimum_healthy_percent
deployment_circuit_breaker_enabled = var.deployment_circuit_breaker_enabled
deployment_circuit_breaker_rollback = var.deployment_circuit_breaker_rollback
container_insights_enabled = true
log_retention_days = var.log_retention_days
assign_public_ip = false

tags = local.tags
}

output "bo_alb_dns_name" {
value = module.ecs_service.alb_dns_name
}

output "bo_target_group_arn" {
value = module.ecs_service.target_group_arn_effective
}

output "bo_ecs_service_sg" {
value = module.ecs_service.security_group_id
}

# Discover this env’s ALB (created by module.ecs_service)
data "aws_lb" "bo_alb" {
name = "${local.app_name}-${local.env}-alb"
depends_on = [module.ecs_service]
}

# Associate the root WAF to this ALB
resource "aws_wafv2_web_acl_association" "mpath_web_acl_assoc" {
resource_arn = data.aws_lb.bo_alb.arn
web_acl_arn = data.terraform_remote_state.root.outputs.waf_web_acl_arn
}

39 changes: 39 additions & 0 deletions deployment/ecs/envs/bo/terraform.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
waf_alb_arns = [
aws_lb.mpath_production_alb.arn
]

aws_region = "us-east-1"
environment = "bo" # your env code uses local.env = "bo"

# Container / service
container_image = "295669632222.dkr.ecr.us-east-1.amazonaws.com/microhealthllc/mpath-bo:latest"
container_port = 8443
desired_count = 2
cpu = 1024
memory = 2048

# Healthcheck
health_check_path = "/health"

# TLS / ALB
acm_certificate_arn = "" # ACM ARN
ssl_policy = "ELBSecurityPolicy-TLS-1-2-2017-01"
alb_deletion_protection = true

# ECS deployment knobs
platform_version = "LATEST"
deployment_maximum_percent = 200
deployment_minimum_healthy_percent = 50
deployment_circuit_breaker_enabled = true
deployment_circuit_breaker_rollback = true
log_retention_days = 30


waf_allowed_countries = ["US"]

# Tags
tags = {
Owner = "DevOps Team"
CostCenter = "Engineering"
Application = "mpath"
}
Loading
Loading