Skip to content

Latest commit

Β 

History

68 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

CloudCare HMS β€” an AWS DevOps showcase

A small Hospital Management System used as the vehicle to build, operate, and document a real production-style AWS environment with Terraform and GitHub Actions. The interesting work happens under terraform/ and .github/workflows/ β€” not in the web UI.

Region ap-south-1 (Mumbai) Β· IaC Terraform 1.9+ Β· Backend Python 3.12 + FastAPI Β· Frontend React 18 + Vite Β· Database PostgreSQL 16 on RDS Β· CI/CD GitHub Actions + OIDC


Scope β€” what this repo is and isn't

This project is intentionally focused on the DevOps and SRE side: infrastructure-as-code, networking, IAM, deployment pipelines, observability, and cost control. The application logic (patients, appointments) is the simplest possible CRUD that gives the infrastructure something to host.

UI/UX is out of scope. The web frontend exists to prove the stack is wired up end-to-end (CloudFront β†’ S3 for assets, CloudFront β†’ ALB β†’ EC2 β†’ RDS for the API). It is not designed as a product, and visual polish was not a goal. To evaluate this project, read the Terraform stacks, the workflows, and the architecture sections below.


Tech stack

Twenty-one tools, grouped by what they do in the system.

Cloud & IaC

AWS Terraform S3+DDB state

Infrastructure declared once, applied from anywhere; remote state with locking.

Network

VPC Subnets NACLs Security Groups

Custom VPC across 2 AZs; defense-in-depth via SG chain plus stateless NACL backstops.

Compute

EC2 ASG ALB ECR Docker

Auto-healing ASG of t3.micro instances behind an ALB; container images in ECR.

Data

RDS PostgreSQL DynamoDB Secrets Manager

Relational data on private RDS; audit events on DynamoDB; DB password generated into Secrets Manager.

Serverless

Lambda API Gateway SES

HTTP APIs in front of Lambda; SES for transactional email from the contact form.

Edge

CloudFront S3 static

One HTTPS origin for the whole app; static SPA from private S3 via Origin Access Control.

Observability

CloudWatch SNS X-Ray

Metrics, logs, and alarms across every tier; SNS fan-out; X-Ray traces the serverless paths.

Identity & CI/CD

IAM + OIDC GitHub Actions

Least-privilege IAM with condition keys; keyless CI auth via OIDC federation.

Application

FastAPI React Vite

Python 3.12 backend, React 18 SPA built with Vite β€” the simplest CRUD needed to wire the stack.

Engineering practices demonstrated

The ten disciplines this project deliberately exercises. These are the things to grep the repo for, not "features":

# Practice Where to find it
1 Remote state with locking β€” Terraform safe to run from multiple machines / CI terraform/bootstrap/ (S3 + DynamoDB)
2 Per-stack state isolation β€” cross-stack reads via terraform_remote_state, never re-declaration every stack's backend.s3.key
3 Least-privilege IAM β€” resource-scoped policies, condition keys (e.g. ses:FromAddress, aws:SourceArn) terraform/{compute,serverless-contact,cdn}/iam.tf
4 Multi-AZ networking β€” public / private app / private DB subnets across two AZs terraform/network/{subnets,routing}.tf
5 Immutable image deploys β€” ECR push + ASG start-instance-refresh rollout .github/workflows/backend.yml
6 Static assets through CloudFront β€” private S3 via OAC, cache invalidation on every deploy terraform/cdn/ + .github/workflows/frontend.yml
7 Workflow concurrency β€” prevents state-lock and refresh races on rapid pushes .github/workflows/terraform.yml concurrency:
8 Observability across three pillars β€” metrics, logs, traces (X-Ray) terraform/observability/, Lambda tracing_config
9 Cost controls β€” Budgets, billing alarm, free-tier-aware sizing, NAT instance over Gateway Doc 03, terraform/compute/nat.tf
10 Keyless GitHub Actions auth β€” OIDC federation, sub claim pinned to repo + refs terraform/cicd/oidc.tf

Table of contents


Architecture at a glance

Users hit CloudFront. Static React assets come from an S3 bucket via Origin Access Control. API requests (/api/*) are forwarded to an Application Load Balancer, which routes them to an Auto Scaling Group of EC2 instances running the FastAPI backend container pulled from ECR. The backend talks to a private RDS PostgreSQL instance using credentials fetched at boot from Secrets Manager via the instance's IAM role.

Side flows run on serverless: a contact form posts to API Gateway β†’ Lambda β†’ SES, and audit events go to API Gateway β†’ Lambda β†’ DynamoDB with X-Ray tracing. CloudWatch collects metrics and logs across every tier, with alarms fanning out through SNS to email.


Architecture diagram

The full system at a glance. Public traffic enters through CloudFront; the three-tier path sits in a VPC; two serverless features hang off API Gateway.

flowchart TB
    User([Users])

    subgraph Edge["AWS Edge"]
        CF["Amazon CloudFront<br/>(HTTPS, free *.cloudfront.net cert)"]
    end

    S3["S3 bucket<br/>(React SPA, private + OAC)"]

    subgraph VPC["VPC 10.0.0.0/16 β€” ap-south-1"]

        IGW{{Internet Gateway}}

        subgraph Pub["Public subnets (AZ-a, AZ-b)"]
            ALB["Application<br/>Load Balancer"]
            NAT["NAT Instance<br/>t3.micro"]
        end

        subgraph AppT["Private app subnets (AZ-a, AZ-b)"]
            ASG["Auto Scaling Group<br/>EC2 t3.micro<br/>FastAPI in Docker"]
        end

        subgraph DBT["Private DB subnets (AZ-a, AZ-b)"]
            RDS[("RDS PostgreSQL<br/>encrypted, single-AZ")]
        end
    end

    subgraph AWSsvc["AWS Services"]
        ECR[("ECR<br/>Docker images")]
        SM[("Secrets Manager<br/>DB credentials")]
        CW["CloudWatch<br/>logs Β· metrics Β· alarms"]
    end

    subgraph SL["Serverless slices"]
        APIa["API Gateway<br/>Audit"]
        Laud["Lambda<br/>audit-handler"]
        DDB[("DynamoDB<br/>audit events")]
        APIc["API Gateway<br/>Contact"]
        Lcon["Lambda<br/>contact-handler"]
        SES["Amazon SES"]
        XR["AWS X-Ray"]
    end

    User -- HTTPS --> CF
    CF -- "/*" --> S3
    CF -- "/api/*" --> ALB

    IGW --- ALB
    IGW --- NAT
    ALB -- ":8000" --> ASG
    ASG -- ":5432" --> RDS
    ASG -.->|pull image| NAT
    NAT --> IGW
    ASG -. pull .-> ECR
    ASG -. GetSecretValue .-> SM
    ASG -. logs/metrics .-> CW

    User -- HTTPS --> APIa
    User -- HTTPS --> APIc
    APIa --> Laud
    Laud --> DDB
    Laud -.-> XR
    APIc --> Lcon
    Lcon --> SES
Loading

The data tier is unreachable from anywhere except the app tier: it sits in a private subnet, has no public route, and its security group only trusts the app security group. Three independent locks.


Request flow

A typical "load the patients page, then add a patient" sequence, with first-time boot calls included:

sequenceDiagram
    actor User
    participant CF as CloudFront
    participant S3 as S3 (SPA)
    participant ALB as ALB
    participant EC2 as EC2 (FastAPI)
    participant SM as Secrets Manager
    participant RDS as RDS PostgreSQL

    rect rgb(238,245,255)
    Note over User,RDS: First page load (static)
    User->>CF: GET /
    CF->>S3: GET index.html (OAC, SigV4)
    S3-->>CF: 200 OK
    CF-->>User: 200 (cached at edge)
    end

    rect rgb(238,255,238)
    Note over User,RDS: First boot of an instance
    EC2->>SM: GetSecretValue(cloudcare/db/credentials)
    SM-->>EC2: { username, password, host, ... }
    end

    rect rgb(255,245,235)
    Note over User,RDS: API call
    User->>CF: POST /api/patients { ... }
    CF->>ALB: forward (origin: alb-api)
    ALB->>EC2: forward to healthy target :8000
    EC2->>RDS: INSERT INTO patients ...
    RDS-->>EC2: row
    EC2-->>ALB: 201 Created
    ALB-->>CF: 201
    CF-->>User: 201 Created
    end
Loading

Network topology

Six subnets across two AZs, three tiers, two route tables:

flowchart TB
    Internet((Internet))
    IGW{{Internet Gateway}}

    subgraph VPC["VPC β€” 10.0.0.0/16"]

        subgraph AZa["Availability Zone ap-south-1a"]
            PubA["Public subnet<br/>10.0.0.0/24<br/>(ALB, NAT)"]
            AppA["Private app subnet<br/>10.0.10.0/24<br/>(EC2)"]
            DbA["Private db subnet<br/>10.0.20.0/24<br/>(RDS)"]
        end

        subgraph AZb["Availability Zone ap-south-1b"]
            PubB["Public subnet<br/>10.0.1.0/24<br/>(ALB, standby)"]
            AppB["Private app subnet<br/>10.0.11.0/24<br/>(EC2)"]
            DbB["Private db subnet<br/>10.0.21.0/24<br/>(RDS standby)"]
        end

        RTpub["Public route table<br/>0.0.0.0/0 β†’ IGW"]
        RTpriv["Private route table<br/>0.0.0.0/0 β†’ NAT instance"]
    end

    Internet --- IGW
    IGW --- PubA
    IGW --- PubB

    PubA -.assoc.-> RTpub
    PubB -.assoc.-> RTpub
    AppA -.assoc.-> RTpriv
    AppB -.assoc.-> RTpriv
    DbA  -.assoc.-> RTpriv
    DbB  -.assoc.-> RTpriv
Loading
CIDR Tier Public? Purpose
10.0.0.0/24, 10.0.1.0/24 Public βœ… (route β†’ IGW) ALB, NAT instance
10.0.10.0/24, 10.0.11.0/24 App (private) ❌ (egress via NAT) EC2 ASG
10.0.20.0/24, 10.0.21.0/24 DB (private) ❌ (local only) RDS PostgreSQL

The DB subnets have no NAT route either β€” the database has zero egress. Only the app subnets route through the NAT instance, and only for outbound connections initiated from inside.


Security architecture

Defense in depth β€” the security-group chain

Each tier accepts traffic only from the tier directly in front of it, by referencing the upstream security group, not an IP range:

flowchart LR
    I([Internet]) -- ":80, :443" --> ALB["alb-sg"]
    ALB -- ":8000<br/>(SG reference)" --> A["app-sg"]
    A -- ":5432<br/>(SG reference)" --> D["db-sg"]

    style ALB fill:#dbeafe,stroke:#1d4ed8,color:#000
    style A   fill:#fef3c7,stroke:#b45309,color:#000
    style D   fill:#fee2e2,stroke:#b91c1c,color:#000
Loading
SG Ingress Source Egress
alb-sg 80, 443 (TCP) 0.0.0.0/0 all
app-sg 8000 (TCP) alb-sg all
db-sg 5432 (TCP) app-sg all

NACLs sit one layer below as stateless subnet guards (allow VPC-internal, allow ephemeral return ports on public). They're coarse on purpose β€” the SGs do the precise work.

IAM principles applied

  • No long-lived AWS keys in GitHub β€” CI authenticates via GitHub OIDC β†’ sts:AssumeRoleWithWebIdentity β†’ 1-hour creds per job, scoped via the sub claim to repo:owner/name:ref:refs/heads/main and pull_request.
  • No credentials on EC2 β€” instances use an IAM instance profile with secretsmanager:GetSecretValue scoped to the exact DB secret ARN.
  • Contact-form Lambda cannot impersonate other senders β€” ses:SendEmail is conditioned on ses:FromAddress = <our verified sender>.
  • CloudFront-only S3 access β€” S3 bucket policy allows reads from cloudfront.amazonaws.com only when aws:SourceArn matches this distribution's ARN.
  • IMDSv2 enforced on EC2 (http_tokens = "required") to block SSRF-based credential theft.

Data model

erDiagram
    PATIENTS ||--o{ APPOINTMENTS : has

    PATIENTS {
        int id PK
        string full_name
        date date_of_birth
        string phone
        datetime created_at
    }

    APPOINTMENTS {
        int id PK
        int patient_id FK
        datetime scheduled_for
        string reason
        string status
    }

    AUDIT_EVENTS {
        string event_id PK
        string ts
        string entity_type
        string entity_id
        string action
        string actor
    }
Loading

PATIENTS and APPOINTMENTS live in RDS PostgreSQL (the relational, joined data). AUDIT_EVENTS lives in DynamoDB (high-volume, write-heavy, simple key access) β€” exactly the split DynamoDB and a relational DB exist for.


Repository structure

cloud-care/
β”œβ”€β”€ README.md                       ← this file
β”œβ”€β”€ docs/                           ← 21 numbered teaching docs (00–20)
β”‚   └── 00-roadmap.md
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ backend/                    ← FastAPI + Dockerfile + docker-compose
β”‚   β”‚   β”œβ”€β”€ app/{main,config,database,models,schemas}.py
β”‚   β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”‚   β”œβ”€β”€ docker-compose.yml
β”‚   β”‚   └── requirements.txt
β”‚   └── frontend/                   ← React + Vite
β”‚       β”œβ”€β”€ src/{main,App,api}.jsx
β”‚       β”œβ”€β”€ index.html
β”‚       └── package.json
β”œβ”€β”€ terraform/                      ← 9 independent stacks (own state key each)
β”‚   β”œβ”€β”€ bootstrap/                  ← S3 state bucket + DynamoDB lock table
β”‚   β”œβ”€β”€ network/                    ← VPC, subnets, IGW, route tables, NACLs, SGs
β”‚   β”œβ”€β”€ database/                   ← RDS + Secrets Manager
β”‚   β”œβ”€β”€ compute/                    ← ALB, ASG, ECR, NAT, IAM
β”‚   β”œβ”€β”€ cdn/                        ← S3 + CloudFront + OAC
β”‚   β”œβ”€β”€ serverless-audit/           ← API Gateway + Lambda + DynamoDB + X-Ray
β”‚   β”œβ”€β”€ serverless-contact/         ← API Gateway + Lambda + SES
β”‚   β”œβ”€β”€ observability/              ← Dashboard + alarms + SNS
β”‚   └── cicd/                       ← GitHub OIDC provider + deploy role
β”œβ”€β”€ .github/workflows/              ← terraform.yml Β· backend.yml Β· frontend.yml
└── resourse_images/                ← reference AWS architecture diagrams

Infrastructure modules

Each Terraform stack owns its own state key in the shared backend (s3:// cloudcare-tfstate-<account>/<stack>/terraform.tfstate). Stacks consume each other's outputs via terraform_remote_state, never by redeclaration.

flowchart TB
    BS["bootstrap<br/>S3 state + DynamoDB lock"]

    NET["network<br/>VPC Β· subnets Β· IGW Β· RTs Β· NACLs Β· SGs"]
    DB["database<br/>RDS Β· subnet group Β· Secrets Manager"]
    CMP["compute<br/>ALB Β· ASG Β· ECR Β· NAT Β· IAM"]
    CDN["cdn<br/>S3 Β· CloudFront Β· OAC"]
    SA["serverless-audit<br/>API Gateway Β· Lambda Β· DynamoDB"]
    SC["serverless-contact<br/>API Gateway Β· Lambda Β· SES"]
    OBS["observability<br/>Dashboard Β· Alarms Β· SNS"]
    CICD["cicd<br/>OIDC provider Β· deploy IAM role"]

    BS -.->|hosts state for all| NET
    NET --> DB
    NET --> CMP
    DB --> CMP
    CMP --> CDN
    CMP --> OBS
    DB --> OBS
    SA --> OBS
    SC --> OBS
Loading
Stack State key Reads from Free-tier risk
bootstrap bootstrap/terraform.tfstate (local) β€” βœ… ~cents/mo
network network/... β€” βœ… free
database database/... network ⚠️ RDS hours
compute compute/... network, database ⚠️ ALB + 2Γ— t3.micro hours
cdn cdn/... compute βœ… free within tier
serverless-audit serverless/audit/... β€” βœ… free within tier
serverless-contact serverless/contact/... β€” βœ… free within tier
observability observability/... compute, database, both serverless βœ… free within tier
cicd cicd/... β€” βœ… free

Prerequisites

  • AWS account with a non-root IAM admin user, MFA enabled, and budgets configured (see docs/03)
  • AWS CLI v2 authenticated as that admin (aws sts get-caller-identity succeeds)
  • Terraform >= 1.5
  • Docker + Docker Compose
  • Node.js 20+ (for the frontend)
  • Python 3.12 (for local backend dev, optional β€” Docker is enough)
  • A GitHub repo if you want CI/CD (Phase 8)

Quick start β€” deploy from scratch

The stacks must be applied in dependency order. Each phase has a dedicated doc with full explanation; below is the minimal command sequence.

1. Bootstrap the Terraform state backend

export AWS_PROFILE=cloudcare
export AWS_REGION=ap-south-1

cd terraform/bootstrap
terraform init
terraform apply -var="state_bucket_name=cloudcare-tfstate-$(aws sts get-caller-identity --query Account --output text)"

2. Network β†’ Database β†’ Compute

for stack in network database compute; do
  ( cd "terraform/$stack" && terraform init && terraform apply -auto-approve )
done

3. Push the backend image to ECR

REGION=ap-south-1
ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
REPO=$(cd terraform/compute && terraform output -raw ecr_repository_url)

aws ecr get-login-password --region "$REGION" \
  | docker login --username AWS --password-stdin "$ACCOUNT.dkr.ecr.$REGION.amazonaws.com"

( cd app/backend && docker build -t "$REPO:latest" . && docker push "$REPO:latest" )

# Roll the ASG so instances pull the freshly-pushed image:
aws autoscaling start-instance-refresh \
  --auto-scaling-group-name "$(cd terraform/compute && terraform output -raw asg_name)"

4. CDN β†’ upload the frontend β†’ invalidate

( cd terraform/cdn && terraform init && terraform apply -auto-approve )

BUCKET=$(cd terraform/cdn && terraform output -raw frontend_bucket)
DIST=$(cd terraform/cdn && terraform output -raw cloudfront_distribution_id)

( cd app/frontend && npm ci && npm run build )
aws s3 sync app/frontend/dist/ "s3://$BUCKET/" --delete
aws cloudfront create-invalidation --distribution-id "$DIST" --paths "/*"

5. Serverless + observability + CI/CD (optional)

for stack in serverless-audit serverless-contact observability cicd; do
  ( cd "terraform/$stack" && terraform init && terraform apply )
done

6. Verify

CF=$(cd terraform/cdn && terraform output -raw cloudfront_domain_name)
echo "Open https://$CF/  β€” that's CloudCare."

curl "https://$CF/health"
curl "https://$CF/api/patients"

Local development

Run the backend and a throwaway Postgres locally with Docker Compose:

cd app/backend
docker compose up --build
# API on http://localhost:8000   |  Swagger UI on http://localhost:8000/docs

Then run the frontend (Vite dev server, hot reload):

cd app/frontend
npm install
npm run dev
# Open http://localhost:5173

Configure the frontend's API base via an env file:

# app/frontend/.env.local
VITE_API_URL=http://localhost:8000

To point the local frontend at the deployed API:

VITE_API_URL="https://$(cd ../../terraform/cdn && terraform output -raw cloudfront_domain_name)" npm run dev

CI/CD pipeline

GitHub Actions authenticates to AWS via OIDC β€” no long-lived AWS keys are ever stored in GitHub. Each workflow only runs when files in its scope change.

flowchart LR
    Dev([Developer]) -->|push / PR| GH[GitHub Repo]

    subgraph GHA["GitHub Actions"]
        TF["terraform.yml<br/>plan on PR<br/>apply on main"]
        BE["backend.yml<br/>build Β· push Β· roll ASG"]
        FE["frontend.yml<br/>build Β· sync Β· invalidate"]
    end

    GH --> TF
    GH --> BE
    GH --> FE

    TF -- "OIDC<br/>AssumeRoleWithWebIdentity" --> STS[(AWS STS)]
    BE -- "OIDC<br/>AssumeRoleWithWebIdentity" --> STS
    FE -- "OIDC<br/>AssumeRoleWithWebIdentity" --> STS

    STS --> Role["IAM role:<br/>cloudcare-github-deploy<br/>(trust: repo:owner/name<br/>refs: main Β· PR)"]

    Role --> ECR[ECR push]
    Role --> ASG[ASG instance refresh]
    Role --> S3O[S3 sync]
    Role --> CFI[CloudFront invalidation]
    Role --> TFA[terraform apply]
Loading
Trigger Workflow What runs
PR touches terraform/** terraform.yml terraform plan for every stack
Push to main, terraform/** terraform.yml terraform apply for every stack (dependency-ordered)
Push to main, app/backend/** backend.yml docker build/push + start-instance-refresh
Push to main, app/frontend/** frontend.yml npm run build + s3 sync + CloudFront invalidate

Observability

A single CloudWatch dashboard (cloudcare-overview) shows ALB traffic & errors, healthy host count, RDS CPU/connections/storage, and Lambda invocations & errors β€” at a glance.

Alarms publish to one SNS topic (cloudcare-ops-alerts) which fans out to email today and can fan out to Slack/PagerDuty later without changing any alarm:

Alarm Threshold Why this threshold
cloudcare-alb-5xx β‰₯ 5 5xx in 5 min Single error is noise; sustained is signal
cloudcare-alb-no-healthy-hosts < 1 healthy for 2 min The site is down β€” page immediately
cloudcare-rds-cpu-high > 80% avg over 10 min Brief spikes are normal; sustained means trouble
cloudcare-rds-storage-low < 2 GB free Lead time to expand before writes fail
cloudcare-rds-connections-high > 80 conns avg over 10 min db.t3.micro caps near 100
cloudcare-audit-lambda-errors β‰₯ 1 in 5 min Lambda errors should be 0
cloudcare-contact-lambda-errors β‰₯ 1 in 5 min Same
cloudcare-ddb-throttled β‰₯ 1 throttle in 5 min On-demand shouldn't ever throttle at our scale

Cost telemetry: Budgets (Doc 03) for tripwire alerts, Cost Explorer for attribution by Project = cloudcare tag, Compute Optimizer for right-sizing recommendations.


Cost

Designed to live inside the AWS Free Tier when run for ≀ 750 hours/month of each free-tier-eligible resource. The key habits:

  • One t3.micro app instance (desired = 1); scale to 2 only briefly
  • Single-AZ RDS db.t3.micro (Multi-AZ written but false by default)
  • A NAT instance, not a NAT Gateway (~$32/mo saved)
  • Frontend on CloudFront's always-free tier (1 TB out + 10M HTTPS requests/mo)
  • Lambda + DynamoDB + X-Ray always-free quotas dwarf lab usage
  • Destroy after each lab β€” only network/ and bootstrap/ are left running

The roadmap doc tracks a four-month part-time learning pace; the only things intentionally left running are nearly free. A surprise bill should be impossible β€” Doc 03's budgets, billing alarm, and free-tier alerts all email you long before any real spend.


Teardown

Destroy in reverse-dependency order to return to ~$0:

for stack in observability cdn compute database serverless-contact \
             serverless-audit cicd network; do
  ( cd "terraform/$stack" && terraform destroy -auto-approve )
done

Leave bootstrap/ alone β€” it holds the state for everything else and costs cents per month. Bring the whole stack back with one apply loop in reverse order (see Quick start or docs/20 for the complete recipe).


Documentation & learning path

This repository was built incrementally as a complete teaching project for AWS SRE/DevOps fundamentals. The 21 docs in docs/ walk through every phase with the what, why, and how β€” full Terraform code, design trade-offs, AWS console verification steps, and interview-relevant framing.

Start at the roadmap for the full 8-phase plan, or jump to any phase below:

Phase Topic Docs
0 Foundations Β· account Β· tooling Β· Terraform Β· state backend 00–06
1 Networking Β· VPC Β· SGs Β· NACLs 07, 08
2 Compute Β· ASG Β· ALB 09, 10
3 Database Β· RDS Β· Secrets Manager 11
4 Application Β· FastAPI Β· EC2 deploy Β· React 12, 13, 14
5 Content delivery Β· S3 Β· CloudFront 15
6 Serverless Β· Lambda Β· DynamoDB Β· SES Β· X-Ray 16, 17
7 Observability & cost 18
8 CI/CD Β· teardown Β· the interview story 19, 20

Architecture references: AWS Well-Architected Framework Β· AWS Skill Builder "Optimizing a cloud architecture" (original diagrams under resourse_images/).

About

A production-style, AWS-native Hospital Management System demonstrating the AWS Well-Architected Framework. Built entirely in Terraform across nine isolated stacks, shipped through GitHub Actions with OIDC federation, and designed to live inside the AWS Free Tier

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages