A lightweight AWS service emulator written in Go.
Works as both a CI/CD testing tool and a local development server with optional data persistence.
- No authentication required - Perfect for CI environments
- Single binary - Easy to distribute and deploy
- Docker support - Run as a container
- Lightweight - Fast startup, minimal resource usage
- AWS SDK v2 compatible - Works seamlessly with Go AWS SDK v2
- Optional data persistence - Survive restarts with
KUMO_DATA_DIR
| Service | Description |
|---|---|
| DynamoDB | NoSQL database |
| DynamoDB Streams | DynamoDB change data capture |
| EBS | Block storage |
| ElastiCache | In-memory caching |
| Glacier | Archive storage |
| MemoryDB | Redis-compatible database |
| S3 | Object storage |
| S3 Control | S3 account-level operations |
| S3 Tables | S3 table buckets |
| Service | Description |
|---|---|
| Batch | Batch computing |
| EC2 | Virtual machines |
| Elastic Beanstalk | Application deployment |
| Lambda | Serverless functions |
| Service | Description |
|---|---|
| ECR | Container registry |
| ECS | Container orchestration |
| EKS | Kubernetes service |
| Service | Description |
|---|---|
| DocumentDB | MongoDB-compatible database |
| Neptune | Graph database |
| RDS | Relational database service |
| Redshift | Data warehousing |
| Service | Description |
|---|---|
| EventBridge | Event bus |
| Firehose | Data delivery |
| Kinesis | Real-time streaming |
| MQ | Message broker (ActiveMQ/RabbitMQ) |
| MSK (Kafka) | Managed streaming for Kafka |
| Pipes | Event-driven integration |
| SNS | Pub/Sub messaging |
| SQS | Message queuing |
| Service | Description |
|---|---|
| ACM | Certificate management |
| Cognito | User authentication |
| IAM | Identity and access management |
| KMS | Key management |
| Macie | Data security and privacy |
| STS | Security token service |
| Secrets Manager | Secret storage |
| Security Lake | Security data lake |
| Verified Permissions | Fine-grained authorization (Cedar) |
| Service | Description |
|---|---|
| Amazon Managed Service for Prometheus | Managed Prometheus workspaces with data-plane passthrough |
| CloudTrail | API audit logging |
| CloudWatch | Metrics and alarms |
| CloudWatch Logs | Log management |
| X-Ray | Distributed tracing |
| Service | Description |
|---|---|
| API Gateway | API management (REST API) |
| API Gateway v2 | API management (HTTP/WebSocket API) |
| App Mesh | Service mesh |
| CloudFront | CDN |
| ELBv2 | Load balancing |
| Global Accelerator | Network acceleration |
| Location | Location-based services |
| Route 53 | DNS service |
| Route 53 Resolver | DNS resolver |
| Service | Description |
|---|---|
| Amplify | Full-stack application hosting |
| AppSync | GraphQL API |
| Pinpoint SMS Voice v2 | SMS messaging |
| SES | Email service |
| SES v2 | Email service (v2 API) |
| Scheduler | Task scheduling |
| Step Functions | Workflow orchestration |
| Service | Description |
|---|---|
| Backup | Centralized backup service |
| Cloud Control API | Unified CRUD API for cloud resources |
| CloudFormation | Infrastructure as code |
| CodeConnections | Source code connections |
| Config | Resource configuration |
| Organizations | Multi-account management |
| SSM | Systems Manager |
| Service Quotas | Service limit management |
| Service | Description |
|---|---|
| Athena | SQL query service |
| Comprehend | NLP service |
| Data Exchange | Data marketplace |
| Entity Resolution | Entity matching |
| Forecast | Time-series forecasting |
| Glue | ETL service |
| Rekognition | Image/video analysis |
| SageMaker | Machine learning |
| Service | Description |
|---|---|
| CodeGuru Profiler | Application profiling |
| CodeGuru Reviewer | Automated code review |
| Service | Description |
|---|---|
| Cost Explorer | Cost analysis |
| DLM | Data lifecycle manager |
| Directory Service | Microsoft AD |
| EMR Serverless | Big data processing |
| FinSpace | Financial data management |
| GameLift | Game server hosting |
| Resilience Hub | Application resilience |
docker run -p 4566:4566 ghcr.io/sivchari/kumo:latestWith data persistence:
docker run -p 4566:4566 \
-e KUMO_DATA_DIR=/data \
-v kumo-data:/data \
ghcr.io/sivchari/kumo:latest# Build
make build
# Run
./bin/kumo
# Run with data persistence
KUMO_DATA_DIR=./data ./bin/kumoservices:
kumo:
image: ghcr.io/sivchari/kumo:latest
ports:
- "4566:4566"With data persistence:
services:
kumo:
image: ghcr.io/sivchari/kumo:latest
ports:
- "4566:4566"
environment:
- KUMO_DATA_DIR=/data
volumes:
- kumo-data:/data
volumes:
kumo-data:package main
import (
"context"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
)
func main() {
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("test", "test", "")),
)
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
o.UsePathStyle = true
})
// Create bucket
client.CreateBucket(context.TODO(), &s3.CreateBucketInput{
Bucket: aws.String("my-bucket"),
})
// Put object
client.PutObject(context.TODO(), &s3.PutObjectInput{
Bucket: aws.String("my-bucket"),
Key: aws.String("hello.txt"),
Body: strings.NewReader("Hello, World!"),
})
}package main
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/sqs"
)
func main() {
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("test", "test", "")),
)
client := sqs.NewFromConfig(cfg, func(o *sqs.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
})
// Create queue
result, _ := client.CreateQueue(context.TODO(), &sqs.CreateQueueInput{
QueueName: aws.String("my-queue"),
})
// Send message
client.SendMessage(context.TODO(), &sqs.SendMessageInput{
QueueUrl: result.QueueUrl,
MessageBody: aws.String("Hello from SQS!"),
})
// Receive message
messages, _ := client.ReceiveMessage(context.TODO(), &sqs.ReceiveMessageInput{
QueueUrl: result.QueueUrl,
})
for _, msg := range messages.Messages {
fmt.Println(*msg.Body)
}
}package main
import (
"context"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
)
func main() {
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("test", "test", "")),
)
client := dynamodb.NewFromConfig(cfg, func(o *dynamodb.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
})
// Create table
client.CreateTable(context.TODO(), &dynamodb.CreateTableInput{
TableName: aws.String("users"),
KeySchema: []types.KeySchemaElement{
{AttributeName: aws.String("id"), KeyType: types.KeyTypeHash},
},
AttributeDefinitions: []types.AttributeDefinition{
{AttributeName: aws.String("id"), AttributeType: types.ScalarAttributeTypeS},
},
BillingMode: types.BillingModePayPerRequest,
})
// Put item
client.PutItem(context.TODO(), &dynamodb.PutItemInput{
TableName: aws.String("users"),
Item: map[string]types.AttributeValue{
"id": &types.AttributeValueMemberS{Value: "user-1"},
"name": &types.AttributeValueMemberS{Value: "Alice"},
},
})
}package main
import (
"context"
"fmt"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/secretsmanager"
)
func main() {
cfg, _ := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("us-east-1"),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("test", "test", "")),
)
client := secretsmanager.NewFromConfig(cfg, func(o *secretsmanager.Options) {
o.BaseEndpoint = aws.String("http://localhost:4566")
})
// Create secret
client.CreateSecret(context.TODO(), &secretsmanager.CreateSecretInput{
Name: aws.String("my-secret"),
SecretString: aws.String(`{"username":"admin","password":"secret123"}`),
})
// Get secret
result, _ := client.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{
SecretId: aws.String("my-secret"),
})
fmt.Println(*result.SecretString)
}Environment variables:
| Variable | Default | Description |
|---|---|---|
KUMO_HOST |
0.0.0.0 |
Server bind address |
KUMO_PORT |
4566 |
Server port |
KUMO_LOG_LEVEL |
info |
Log level (debug, info, warn, error) |
KUMO_DATA_DIR |
(unset) | Directory for persistent storage. When unset, data is in-memory only. |
kumo logs all requests with structured fields. The log level controls the amount of detail:
Each request is logged with method, path, status, duration, and API action name:
level=INFO msg=request method=POST path=/ status=200 duration=61µs request_id=... target=secretsmanager.CreateSecret
level=INFO msg=request method=PUT path=/my-bucket pattern=/{bucket} status=200 duration=30µs request_id=...
target-- appears for JSON/Query protocol services (Secrets Manager, DynamoDB, SQS, etc.)action-- appears for Query protocol services (EC2, SNS, etc.) when Action is in the URL query string
In addition to INFO output, the full request body is logged:
level=DEBUG msg="request body" request_id=... body={"Name":"my-secret","SecretString":"..."}
Enable with:
KUMO_LOG_LEVEL=debug ./bin/kumoBy default kumo runs as a pure in-memory emulator -- all data is lost when the process stops. This is ideal for CI/CD pipelines where each test run starts from a clean state.
For local development, set KUMO_DATA_DIR to enable persistent storage:
KUMO_DATA_DIR=./data ./bin/kumoWhen enabled:
- On startup, each service loads its previous state from
$KUMO_DATA_DIR/{service}.json. - On graceful shutdown (SIGTERM/SIGINT), each service saves its current state.
- The data directory is created automatically if it does not exist.
- Writes are atomic (tmp file + rename) to prevent corruption on crash.
- Ephemeral state (SQS in-flight messages, S3 multipart uploads) is not persisted.
$KUMO_DATA_DIR/
s3.json
sqs.json
dynamodb.json
iam.json
...
kumo provides additional endpoints under the /kumo/ prefix for testing purposes. These are not part of any AWS API but are useful for verifying application behavior in tests.
| Method | Path | Description |
|---|---|---|
| GET | /kumo/ses/v2/sent-emails |
Retrieve a list of emails sent via the SES v2 SendEmail and SendBulkEmail APIs |
| GET | /kumo/pinpointsmsvoicev2/sent-messages |
Retrieve a list of SMS messages sent via the Pinpoint SMS Voice v2 SendTextMessage API |
SES v2 SendEmail and SendBulkEmail render stored templates selected by
TemplateName in the captured subject, text, and HTML bodies. Rendering supports
scalar {{name}} placeholders and nested paths such as {{contact.firstName}},
{{this.name}}, and array indexes ({{items.0}} or {{items.[0]}}), without HTML
escaping. A JSON null value renders as an empty string. Use \{{ to emit a
literal {{ (for example in CSS or JavaScript); a {{ that is not escaped must
be closed with }}. Bulk entries use replacement template data when provided
(including {}), otherwise the default template data; replacement data replaces
the whole object, and an empty string fails the entry.
Advanced Handlebars helpers, inline partials, inline templates, and template ARN
resolution are not supported. Rendering errors are reported synchronously as
BadRequestException for single sends or FAILED for individual bulk entries;
failed messages are not recorded. This differs from AWS's asynchronous rendering
failure notifications.
curl http://localhost:4566/kumo/ses/v2/sent-emailsResponse:
{
"SentEmails": [
{
"MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"FromEmailAddress": "[email protected]",
"Destination": {
"ToAddresses": ["[email protected]"]
},
"Subject": "Hello",
"Body": "Hello, World!",
"SentAt": "2025-01-01T00:00:00Z"
}
]
}curl http://localhost:4566/kumo/pinpointsmsvoicev2/sent-messagesResponse:
{
"SentTextMessages": [
{
"MessageId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"DestinationPhoneNumber": "+1234567890",
"OriginationIdentity": "+0987654321",
"MessageBody": "Hello!",
"SentAt": "2025-01-01T00:00:00Z"
}
]
}# Run tests
make test
# Run integration tests
make test-integration
# Lint
make lint
# Build
make buildContributions are welcome! Please see the issues for planned features and improvements.
MIT License
