Overview
The IGAD Innovation Hub uses a hybrid infrastructure approach:
- AWS CDK (TypeScript) - For creating AWS resources (Cognito, DynamoDB, S3)
- AWS SAM (YAML) - For deploying Lambda functions and API Gateway
- Deployment Scripts - Bash scripts that orchestrate fullstack deployments
The application runs entirely on AWS serverless infrastructure with no traditional servers to manage.
Infrastructure Components
Frontend Hosting
- S3 Bucket for static files
- CloudFront CDN for global distribution
- CloudFront Function for SPA routing
Backend API
- Lambda Functions (API + Workers)
- API Gateway REST API
- Lambda Web Adapter layer
Database
- DynamoDB single-table design
- Global Secondary Index (GSI1)
- Pay-per-request billing
Authentication
- Cognito User Pool
- User Pool Client
- Custom attributes (org, role, country)
Storage
- S3 Bucket for documents
- S3 Vectors for embeddings
- Versioning and lifecycle rules
AI Services
- Bedrock (Claude Sonnet 4)
- Knowledge Base integration
- Timeout: 5 minutes for API, 15 for workers
AWS SAM Template
The main deployment template is igadapp/template.yaml:
Key Resources
1. API Lambda Function
ApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: backend/dist
Handler: bootstrap
Runtime: python3.11
MemorySize: 512
Timeout: 300 # 5 minutes
Architectures:
- arm64
Environment:
Variables:
PORT: 8080
AWSLAMBDAEXECWRAPPER: /opt/bootstrap
COGNITOCLIENTID: 7p11hp6gcklhctcr9qffne71vl
COGNITOUSERPOOLID: useast1IMi3kSuB8
TABLENAME: igadtestingmaintable
PROPOSALSBUCKET: !Ref ProposalDocumentsBucket
WORKERFUNCTIONNAME: !Ref AnalysisWorkerFunction
ENVIRONMENT: testing
CORSALLOWEDORIGINS: http://localhost:3000
Layers:
- !Sub "arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:25"
The Lambda Web Adapter layer allows FastAPI to run in Lambda without modification.
2. Worker Lambda Function
AnalysisWorkerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: backend/dist
Handler: app.tools.proposalwriter.workflow.worker.handler
Runtime: python3.11
MemorySize: 1024
Timeout: 900 # 15 minutes for long-running AI tasks
Architectures:
- arm64
Worker functions have a 15-minute timeout for complex AI operations. Ensure operations complete within this window.
3. API Gateway
ApiGateway:
Type: AWS::Serverless::Api
Properties:
StageName: prod
BinaryMediaTypes:
- 'multipart/formdata'
- 'application/pdf'
- 'application/octetstream'
4. CloudFront Distribution
WebsiteDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- DomainName: !GetAtt WebsiteBucket.RegionalDomainName
Id: S3Origin
OriginAccessControlId: !GetAtt CloudFrontOriginAccessControl.Id
- DomainName: !Sub "${ApiGateway}.executeapi.${AWS::Region}.amazonaws.com"
Id: ApiOrigin
OriginPath: "/prod"
DefaultCacheBehavior:
TargetOriginId: S3Origin
ViewerProtocolPolicy: redirecttohttps
CacheBehaviors:
- PathPattern: "/api/*"
TargetOriginId: ApiOrigin
DefaultTTL: 0 # No caching for API
5. S3 Document Bucket
ProposalDocumentsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub 'igadproposaldocuments${AWS::AccountId}'
VersioningConfiguration:
Status: Enabled
LifecycleConfiguration:
Rules:
- Id: DeleteOldVersions
Status: Enabled
NoncurrentVersionExpirationInDays: 30
AWS CDK Stacks
The CDK code is in igadapp/infrastructure/:
Stack Structure
// lib/igadhubstack.ts
import * as cdk from 'awscdklib'
import * as cognito from 'awscdklib/awscognito'
import * as dynamodb from 'awscdklib/awsdynamodb'
import * as s3 from 'awscdklib/awss3'
export class IgadHubStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: IgadHubStackProps) {
super(scope, id, props)
const { environment, resourcePrefix } = props
// Cognito User Pool
const userPool = new cognito.UserPool(this, 'UserPool', {
userPoolName: `${resourcePrefix}userpool`,
signInAliases: { email: true },
autoVerify: { email: true },
passwordPolicy: {
minLength: 8,
requireLowercase: true,
requireUppercase: true,
requireDigits: true,
requireSymbols: true,
},
removalPolicy: environment === 'production'
? cdk.RemovalPolicy.RETAIN
: cdk.RemovalPolicy.DESTROY
})
// DynamoDB Table
const table = new dynamodb.Table(this, 'MainTable', {
tableName: `${resourcePrefix}maintable`,
partitionKey: { name: 'PK', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'SK', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAYPERREQUEST,
pointInTimeRecovery: environment === 'production',
})
// GSI for alternative queries
table.addGlobalSecondaryIndex({
indexName: 'GSI1',
partitionKey: { name: 'GSI1PK', type: dynamodb.AttributeType.STRING },
sortKey: { name: 'GSI1SK', type: dynamodb.AttributeType.STRING },
})
}
}
Deploy CDK Stack
cd igadapp/infrastructure
npm install
npm run build
# Deploy to testing
npm run deploy:testing
# Deploy to production
npm run deploy:production
Deployment Scripts
The igadapp/scripts/ directory contains deployment automation:
Fullstack Deployment (Testing)
#!/bin/bash
# scripts/deployFullstacktesting.sh
set -e
export AWSPROFILE=IBDDEV
PROJECTROOT="$(dirname "$SCRIPTDIR")"
echo "🚀 IGAD Innovation Hub Testing Deployment"
# 1. Setup S3 Vectors
echo "🎯 Setting up S3 Vectors Infrastructure..."
python3 -c '
import boto3
s3vectors = boto3.client("s3vectors", regionname="useast1")
try:
s3vectors.listindexes(vectorBucketName="igadproposalsvectorstesting")
except:
s3vectors.createVectorBucket(
vectorBucketName="igadproposalsvectorstesting",
encryptionConfiguration={"sseType": "AES256"}
)
'
# 2. Build Backend
echo "🐍 Building Backend..."
cd "$PROJECTROOT/backend"
rm -rf dist
mkdir -p dist
cp -r app dist/
cp -r requirements.txt dist/
pip3 install -r requirements.txt -t dist/
cp bootstrap dist/
chmod +x dist/bootstrap
# 3. Deploy Backend with SAM
echo "☁️ Deploying Backend..."
cd "$PROJECTROOT"
sam build --template template.yaml
sam deploy \
--stackname igadtesting \
--s3bucket igaddeploymentbucket \
--capabilities CAPABILITYIAM \
--region useast1 \
--profile IBDDEV
# 4. Build Frontend
echo "⚛️ Building Frontend..."
cd "$PROJECTROOT/frontend"
npm install
npm run build
# 5. Deploy Frontend to S3
echo "📦 Deploying Frontend..."
S3BUCKET="igadtestingfrontend"
aws s3 sync dist/ s3://$S3BUCKET/ --delete --profile IBDDEV
# 6. Invalidate CloudFront Cache
CLOUDFRONTID=$(aws cloudformation describeStacks \
--stackname igadtesting \
--queryStacks[0].Outputs[?OutputKey=='CloudFrontDistributionId'].OutputValue \
--output text \
--profile IBDDEV)
aws cloudfront createinvalidation \
--distributionid $CLOUDFRONTID \
--paths "/*" \
--profile IBDDEV
echo "✅ Deployment Complete!"
Deploy Options
# Full deployment (frontend + backend)
./scripts/deployFullstacktesting.sh
# Backend only
./scripts/deployFullstacktesting.sh --backendonly
# Frontend only
./scripts/deployFullstacktesting.sh --frontendonly
# Production (requires confirmation)
./scripts/deployFullstackproduction.sh
Production deployments require manual confirmation and should only be run after thorough testing.
Environment Configuration
Environment-specific settings are in igadapp/config/:
Testing Configuration
// config/testing.json
{
"environment": "testing",
"resourcePrefix": "igadtesting",
"region": "useast1",
"cognito": {
"userPoolId": "useast1IMi3kSuB8",
"clientId": "7p11hp6gcklhctcr9qffne71vl"
},
"dynamodb": {
"tableName": "igadtestingmaintable"
},
"s3": {
"documentsBucket": "igadproposaldocuments123456",
"vectorsBucket": "igadproposalsvectorstesting"
},
"cors": {
"allowedOrigins": [
"http://localhost:3000",
"https://testigadhub.alliance.cgiar.org"
]
}
}
IAM Permissions
The Lambda functions require the following permissions:
API Function Permissions
Policies:
- AWSLambdaBasicExecutionRole
- Statement:
# Cognito
- Effect: Allow
Action:
- cognitoIdp:AdminInitiateAuth
- cognitoIdp:AdminGetUser
- cognitoIdp:ListUsers
Resource: '*'
# DynamoDB
- Effect: Allow
Action:
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
- dynamodb:Query
- dynamodb:Scan
Resource:
- !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/igadtestingmaintable'
- !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/igadtestingmaintable/index/*'
# Bedrock
- Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
Resource: '*'
# S3
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- !Sub '${ProposalDocumentsBucket.Arn}/*'
- !GetAtt ProposalDocumentsBucket.Arn
# S3 Vectors
- Effect: Allow
Action:
- s3vectors:PutVectors
- s3vectors:QueryVectors
- s3vectors:GetVectors
Resource:
- !Sub 'arn:aws:s3vectors:${AWS::Region}:${AWS::AccountId}:bucket/igadproposalsvectorstesting'
# Lambda (invoke workers)
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource: !GetAtt AnalysisWorkerFunction.Arn
Resource Naming
All resources follow a consistent naming pattern:
| Resource | Pattern | Example |
|---|
| DynamoDB Table | {env}maintable | igadtestingmaintable |
| Lambda Function | {stackname}{Function} | igadtestingApiFunction |
| S3 Bucket | {purpose}{accountid} | igadproposaldocuments123456 |
| Cognito Pool | {env}userpool | igadtestinguserpool |
| CloudFront | {stackname}distribution | igadtestingdistribution |
Monitoring and Logging
CloudWatch Logs
All Lambda functions log to CloudWatch:
# View API function logs
aws logs taillog \
--loggroupname /aws/lambda/igadtestingApiFunction \
--follow \
--profile IBDDEV
# View worker function logs
aws logs taillog \
--loggroupname /aws/lambda/igadtestingAnalysisWorkerFunction \
--follow \
--profile IBDDEV
CloudWatch Metrics
Key metrics to monitor:
- Lambda Invocations - Request volume
- Lambda Duration - Performance
- Lambda Errors - Error rate
- DynamoDB ConsumedReadCapacity - Database usage
- S3 Requests - Storage usage
- CloudFront Requests - Frontend traffic
Troubleshooting
- Check CloudWatch logs for the function
- Increase timeout in
template.yaml (max 900s for workers)
- Optimize AI prompts to reduce processing time
- Consider breaking operations into smaller chunks
- Check ConsumedReadCapacity metrics
- Verify using pagination for large queries
- Consider adding more GSIs for common queries
- Use
BatchGetItem for multiple items
- Verify bucket exists:
aws s3vectors listindexes --vectorBucketName igadproposalsvectorstesting
- Create if missing: Run
scripts/setupS3vectors.py
- Check IAM permissions for s3vectors actions
- Verify
CORSALLOWEDORIGINS environment variable
- Check CloudFront cache behavior for
/api/*
- Ensure frontend URL matches allowed origins
- Test with
curl to isolate frontend vs backend issues
CloudFront deployment not updating
- Build fails to upload: Check S3 bucket permissions
- Old content showing: Invalidate CloudFront cache
aws cloudfront createinvalidation \
--distributionid EDISTID \
--paths "/*" \
--profile IBDDEV
- Wait 35 minutes for invalidation to complete
Cost Optimization
Serverless Cost Tips
- Lambda: Pay per invocation, use ARM64 for 20% savings
- DynamoDB: Pay-per-request is cheaper for variable workloads
- S3: Use lifecycle rules to delete old document versions
- CloudFront: PriceClass100 (US/EU only) reduces costs
- Bedrock: Claude usage is the main cost driver - optimize prompts
Next Steps
Setup Guide
Return to local development setup
Frontend Guide
Learn about React frontend architecture
Backend Guide
Explore FastAPI backend patterns