Skip to content

AWS Deployment

This guide covers deploying EvidentSource on AWS using DynamoDB for storage and SNS for event streaming. The deployment uses CloudFormation templates for infrastructure as code and supports auto-scaling for production workloads.

graph TB
subgraph "Public Subnet"
ALB[Application Load Balancer]
end
subgraph "Private Subnet"
ASG[Auto Scaling Group]
EC2[EC2 Instances<br/>EvidentSource Servers]
end
subgraph "AWS Services"
DDB[(DynamoDB)]
SNS[SNS Topic]
S3[(S3 Bucket)]
CW[CloudWatch]
end
subgraph "Optional Components"
Lambda[Lambda Functions]
SQS[SQS Queues]
end
ALB --> EC2
EC2 --> DDB
EC2 --> SNS
EC2 --> S3
EC2 --> CW
SNS --> Lambda
SNS --> SQS
  • AWS Account with appropriate permissions
  • AWS CLI configured with credentials
  • Docker (for building container images)
  • ACM certificate for your domain
  • Route53 hosted zone (or equivalent DNS management)

EvidentSource requires proper DNS configuration for both external client access and internal service communication (particularly for the MCP server).

RecordTypeTargetPurpose
api.evidentsource.example.comA (Alias)Application Load BalancerExternal and internal API access

When deploying with MCP enabled (EnableMcp=true), the MCP server must connect to the EvidentSource API via the internal ALB. This requires the Host parameter to resolve from within the VPC.

Option 1: Route53 Private Hosted Zone

Terminal window
# Create private hosted zone associated with your VPC
aws route53 create-hosted-zone \
--name example.com \
--vpc VPCRegion=us-east-1,VPCId=vpc-xxxxx \
--caller-reference $(date +%s) \
--hosted-zone-config PrivateZone=true
# Add alias record pointing to the internal ALB
# (Get ALB DNS name from CloudFormation outputs after deployment)

Option 2: Split-Horizon DNS

If you already have a public hosted zone, create a matching private hosted zone with the same domain. The VPC resolver will prefer the private zone for internal queries.

After deployment, EvidentSource exposes:

EndpointPortDescription
https://{Host}443REST API, gRPC API
https://{Host}:80008000MCP server (AI agent integration)

For detailed parameter documentation, see CloudFormation Parameters.

The easiest way to deploy EvidentSource is through the AWS Marketplace:

🚀 Coming Soon: AWS Marketplace

EvidentSource will be available on AWS Marketplace for one-click deployment.

Subscribe on AWS Marketplace
Terminal window
# Set your AWS region
export AWS_REGION=us-east-1
# Create S3 bucket for CloudFormation templates
aws s3 mb s3://my-evident-deployment-$AWS_REGION
# Download CloudFormation templates from customer portal
# https://customers.evidentsource.com/downloads/cloudformation
Terminal window
# Create ECR repository
aws ecr create-repository --repository-name evident-source-server
# Get login token
aws ecr get-login-password --region $AWS_REGION | \
docker login --username AWS --password-stdin \
$(aws sts get-caller-identity --query Account --output text).dkr.ecr.$AWS_REGION.amazonaws.com
# Pull official image
docker login registry.evidentsource.com
docker pull registry.evidentsource.com/evident-source-server:latest
# Tag and push to your ECR
docker tag registry.evidentsource.com/evident-source-server:latest \
$(aws sts get-caller-identity --query Account --output text).dkr.ecr.$AWS_REGION.amazonaws.com/evident-source-server:latest
docker push \
$(aws sts get-caller-identity --query Account --output text).dkr.ecr.$AWS_REGION.amazonaws.com/evident-source-server:latest
Terminal window
# Deploy VPC (optional, if you don't have one)
aws cloudformation deploy \
--template-file infrastructure/cloudformation/vpc.yaml \
--stack-name evident-vpc \
--parameter-overrides \
AvailabilityZones=us-east-1a,us-east-1b
# Deploy EvidentSource
aws cloudformation deploy \
--template-file infrastructure/cloudformation/evident-stack.yaml \
--stack-name evident-stack \
--parameter-overrides \
VpcId=vpc-xxxxx \
PrivateSubnetIds=subnet-xxxxx,subnet-yyyyy \
PublicSubnetIds=subnet-aaaaa,subnet-bbbbb \
InstanceType=m6i.large \
MinSize=2 \
MaxSize=10 \
DesiredCapacity=3 \
--capabilities CAPABILITY_IAM

The main template creates:

Resources:
# DynamoDB Tables
EventsTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
PointInTimeRecoverySpecification:
PointInTimeRecoveryEnabled: true
StreamSpecification:
StreamViewType: NEW_AND_OLD_IMAGES
IndexesTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PAY_PER_REQUEST
# SNS Topic for Events
EventTopic:
Type: AWS::SNS::Topic
Properties:
KmsMasterKeyId: alias/aws/sns
# Auto Scaling Group
AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref LaunchTemplate
TargetGroupARNs:
- !Ref TargetGroup
HealthCheckType: ELB
HealthCheckGracePeriod: 300
# Application Load Balancer
LoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Type: application
Scheme: internet-facing

Create the launch configuration:

#!/bin/bash
# User data script for EC2 instances
# Install CloudWatch agent
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
rpm -U ./amazon-cloudwatch-agent.rpm
# Configure EvidentSource
cat > /etc/evident-source/config.toml <<EOF
[server]
host = "0.0.0.0"
port = 8080
scheme = "http"
[storage]
adapter = "dynamodb"
[dynamodb]
events_table = "${EventsTable}"
indexes_table = "${IndexesTable}"
databases_table = "${DatabasesTable}"
[sns]
topic_arn = "${EventTopicArn}"
enable_raw_delivery = true
[observability]
metrics_endpoint = "cloudwatch"
traces_endpoint = "xray"
EOF
# Start EvidentSource
docker run -d \
--name evident-source \
--restart always \
-p 8080:8080 \
-v /etc/evident-source:/config \
-e AWS_REGION=${AWS_REGION} \
${ECR_URI}/evident-source-server:latest \
--config /config/config.toml
# CPU-based scaling
CPUScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AutoScalingGroupName: !Ref AutoScalingGroup
PolicyType: TargetTrackingScaling
TargetTrackingConfiguration:
PredefinedMetricSpecification:
PredefinedMetricType: ASGAverageCPUUtilization
TargetValue: 70.0
# Custom metric scaling (events per second)
EventRateScalingPolicy:
Type: AWS::AutoScaling::ScalingPolicy
Properties:
AutoScalingGroupName: !Ref AutoScalingGroup
PolicyType: TargetTrackingScaling
TargetTrackingConfiguration:
CustomizedMetricSpecification:
MetricName: EventsPerSecond
Namespace: EvidentSource
Statistic: Average
TargetValue: 1000.0
# Production DynamoDB configuration
EventsTable:
Type: AWS::DynamoDB::Table
Properties:
BillingMode: PROVISIONED
ProvisionedThroughput:
ReadCapacityUnits: 5000
WriteCapacityUnits: 5000
GlobalSecondaryIndexes:
- IndexName: by-stream
Keys:
- AttributeName: database_name
KeyType: HASH
- AttributeName: stream_revision
KeyType: RANGE
Projection:
ProjectionType: ALL
ProvisionedThroughput:
ReadCapacityUnits: 1000
WriteCapacityUnits: 1000
Terminal window
# Register scalable targets
aws application-autoscaling register-scalable-target \
--service-namespace dynamodb \
--resource-id "table/evident-events" \
--scalable-dimension "dynamodb:table:ReadCapacityUnits" \
--min-capacity 5 \
--max-capacity 40000
# Create scaling policy
aws application-autoscaling put-scaling-policy \
--service-namespace dynamodb \
--resource-id "table/evident-events" \
--scalable-dimension "dynamodb:table:ReadCapacityUnits" \
--policy-name "evident-events-read-scaling" \
--policy-type "TargetTrackingScaling" \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "DynamoDBReadCapacityUtilization"
}
}'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchGetItem",
"dynamodb:BatchWriteItem"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/evident-*",
"arn:aws:dynamodb:*:*:table/evident-*/index/*"
]
},
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:*:*:evident-events"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::evident-data/*"
}
]
}
# Security group for ALB
ALBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
# Security group for instances
InstanceSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref ALBSecurityGroup
# ALB HTTPS listener
HTTPSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref LoadBalancer
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref Certificate
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroup
# ACM Certificate
Certificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: evident.example.com
ValidationMethod: DNS
DomainValidationOptions:
- DomainName: evident.example.com
HostedZoneId: !Ref HostedZone

Create a dashboard for monitoring:

{
"widgets": [
{
"type": "metric",
"properties": {
"metrics": [
["EvidentSource", "EventsPerSecond", {"stat": "Average"}],
[".", "QueryLatency", {"stat": "p99"}],
[".", "ActiveConnections", {"stat": "Average"}]
],
"period": 300,
"stat": "Average",
"region": "us-east-1",
"title": "EvidentSource Metrics"
}
}
]
}
HighLatencyAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
MetricName: QueryLatency
Namespace: EvidentSource
Statistic: Average
Period: 300
EvaluationPeriods: 2
Threshold: 100
ComparisonOperator: GreaterThanThreshold
AlarmActions:
- !Ref SNSAlertTopic
DynamoDBThrottleAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
MetricName: UserErrors
Namespace: AWS/DynamoDB
Dimensions:
- Name: TableName
Value: evident-events
Statistic: Sum
Period: 60
EvaluationPeriods: 1
Threshold: 10
ComparisonOperator: GreaterThanThreshold

Enable distributed tracing:

Terminal window
# Configure X-Ray daemon
cat > /etc/systemd/system/xray.service <<EOF
[Unit]
Description=AWS X-Ray Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/xray -o -n us-east-1
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
EOF
systemctl enable xray
systemctl start xray
  1. Use On-Demand for Variable Workloads

    BillingMode: PAY_PER_REQUEST
  2. Enable Auto-scaling for Predictable Workloads

    • Set appropriate min/max capacity
    • Use scheduled scaling for known patterns
  3. Configure DynamoDB Accelerator (DAX)

    DAXCluster:
    Type: AWS::DAX::Cluster
    Properties:
    NodeType: dax.r3.large
    ReplicationFactor: 3
  1. Use Graviton Instances

    InstanceType: m6g.large # ARM-based, 20% better price/performance
  2. Spot Instances for Non-Critical Workloads

    MixedInstancesPolicy:
    InstancesDistribution:
    OnDemandPercentageAboveBaseCapacity: 30
    SpotAllocationStrategy: capacity-optimized
  3. Reserved Instances

    • Purchase RIs for baseline capacity
    • Use Savings Plans for flexibility
Terminal window
# Enable continuous backups
aws dynamodb update-continuous-backups \
--table-name evident-events \
--point-in-time-recovery-specification \
PointInTimeRecoveryEnabled=true
# Create on-demand backup
aws dynamodb create-backup \
--table-name evident-events \
--backup-name evident-events-$(date +%Y%m%d)
GlobalTable:
Type: AWS::DynamoDB::GlobalTable
Properties:
TableName: evident-events-global
Replicas:
- Region: us-east-1
- Region: eu-west-1
- Region: ap-southeast-1

High Latency

  • Check DynamoDB throttling metrics
  • Verify EC2 instance CPU/memory usage
  • Review CloudWatch logs for errors

Connection Errors

  • Verify security group rules
  • Check IAM permissions
  • Ensure DynamoDB tables exist

Event Processing Delays

  • Monitor SNS metrics
  • Check Lambda function errors (if using)
  • Verify dead letter queue
# Optimal configuration for high throughput
[performance]
connection_pool_size = 100
batch_size = 25
write_buffer_size = "10MB"
compression = "snappy"
[dynamodb]
max_retries = 3
retry_delay_ms = 100
consistent_reads = false

For AWS-specific deployment support: