AWS Serverless · Crossplane Actuator · Event-Driven · Browser-Based
This is a small but real serverless system to explore how configuration authority, schemas, Crossplane actuation, and human–agent boundaries should work together—especially for irreversible actions like destroying production data. The goal isn’t the app; it’s learning how to design self-healing infrastructure/application platforms that remain safe, understandable, and governable as automation and AI scale.
serverless-message-wall-demo is a simple, event-driven serverless application designed to be:
- Easy to understand
- Easy to demo in a browser
- Fully deployed on AWS managed services
- Provisioned and reconciled via Crossplane running in Kubernetes
- Free of any application runtime inside Kubernetes (Kubernetes is actuator only)
The demo intentionally keeps the application logic small while still exercising real cloud-native patterns:
- Events instead of tight coupling
- Immutable infrastructure
- Declarative provisioning
- Clear separation between authoring, control, and execution
This repository is also structured to support ConfigHub integration as a follow-on step, enabling policy enforcement, bulk configuration changes, and drift control.
The application is a single static web page that shows:
- Total visitor count
- The most recent messages (e.g. last 5)
- A text box with a “Post Message” button
Users interact entirely through the browser.
- A user loads the page.
- The page fetches a JSON snapshot (
state.json) from S3. - When a user posts a message:
- The browser sends a POST request to a Lambda Function URL.
- The Lambda updates DynamoDB.
- The Lambda emits an EventBridge event.
- EventBridge triggers a second Lambda that:
- Reads the latest data from DynamoDB.
- Writes an updated
state.jsonsnapshot to S3.
- The browser refreshes the view using the updated snapshot.
Browser
|
| POST /message
v
Lambda Function URL
|
| PutItem / UpdateItem
v
DynamoDB
|
| PutEvents
v
EventBridge
|
| Rule match
v
Snapshot Lambda
|
| PutObject
v
S3 (state.json)
Browser ───────────────► GET state.json
- Event-driven: No direct Lambda-to-Lambda calls
- Snapshot-based UI: Browser reads from S3, not live databases
- Actuator-only Kubernetes: No app code runs in the cluster
- Minimal moving parts: No queues, retries, or orchestration layers
| Service | Purpose |
|---|---|
| AWS Lambda | Application logic |
| Lambda Function URL | Browser-accessible API |
| DynamoDB | Visitor count + messages |
| EventBridge | Event routing |
| S3 | Static website + state.json |
| IAM | Least-privilege execution roles |
| CloudWatch Logs | Observability |
serverless-message-wall-demo/
├── README.md
├── beads/
│ └── backlog.jsonl # Epics and issues (Beads format)
├── platform/
│ └── crossplane/
│ ├── install.yaml
│ ├── provider-aws.yaml
│ └── providerconfig.yaml
├── infra/
│ ├── base/
│ │ ├── s3.yaml
│ │ ├── dynamodb.yaml
│ │ ├── iam.yaml
│ │ ├── lambda-api.yaml
│ │ ├── lambda-snapshot.yaml
│ │ ├── eventbridge.yaml
│ └── envs/
│ └── dev/
│ ├── kustomization.yaml
│ └── patches.yaml
├── app/
│ ├── web/
│ │ ├── index.html
│ │ └── app.js
│ ├── api-handler/
│ │ ├── handler.py
│ │ └── build.sh
│ ├── snapshot-writer/
│ │ ├── handler.py
│ │ └── build.sh
│ └── artifacts/
│ ├── api-handler.zip
│ └── snapshot-writer.zip
└── scripts/
├── bootstrap-kind.sh
├── bootstrap-crossplane.sh
├── deploy-dev.sh
├── smoke-test.sh
└── cleanup.sh
Kubernetes is used only to run Crossplane and its controllers.
- No application containers
- No services, ingresses, or pods for the app
- Kubernetes credentials are never exposed to users
Crossplane is responsible for:
- Creating AWS resources
- Reconciling desired state
- Reporting readiness and failures
./scripts/check-prerequisites.shRequired tools: Docker, kind, kubectl, helm, AWS CLI
./scripts/setup.shThe wizard will:
- Prompt for your AWS account ID (auto-detected from credentials)
- Ask for region, resource prefix, and environment name
- Generate all configuration files
- Offer to run the full deployment
Or, deploy everything in one command:
./scripts/setup.sh --deploy./scripts/smoke-test.sh./scripts/cleanup.shFor more control, you can run each step manually:
./scripts/setup.sh# Create permission boundary
aws iam create-policy \
--policy-name MessageWallRoleBoundary \
--policy-document file://platform/iam/messagewall-role-boundary.json
# Create Crossplane user and attach policy
# See docs/setup-actuator-cluster.md for full instructions./scripts/bootstrap-kind.sh
./scripts/bootstrap-crossplane.sh
./scripts/bootstrap-aws-providers.sh./scripts/deploy-dev.sh./scripts/finalize-web.shThis retrieves the Lambda Function URL and updates index.html.
./scripts/smoke-test.shOr manually:
- Open the S3 static site URL in a browser
- Post a message
- Refresh the page and observe updated state
- Docker (running)
kind- Kubernetes in Dockerkubectl- Kubernetes CLIhelm- Kubernetes package managerawsCLI (with valid credentials)
Run ./scripts/check-prerequisites.sh to verify.
- An AWS account
- IAM credentials with permission to create:
- S3 buckets
- DynamoDB tables
- Lambda functions
- EventBridge rules
- IAM roles/policies
This demo is intentionally designed to evolve.
- Bulk Configuration Changes: See
docs/bulk-changes-and-change-management.mdfor detailed scenarios including security patching, risk mitigation strategies, and change management procedures - ConfigHub Integration: See
docs/decisions/005-confighub-integration-architecture.mdfor how ConfigHub fits into the deployment flow - ConfigHub + Crossplane Narrative: See
docs/confighub-crossplane-narrative.mdfor a complete walkthrough of how ConfigHub, Crossplane, and AWS work together—including controlled rollouts, bulk changes, and break-glass recovery
- Add ConfigHub to manage resolved configuration
- Enforce IAM and security policies at the config layer
- Demonstrate bulk configuration changes via ConfigHub functions
- Add multiple environments (dev / stage / prod)
- Add CloudFront or WAF
- Add drift detection and reconciliation
This repository exists to answer a simple question:
What does a modern, event-driven, serverless application look like when infrastructure is treated as data and Kubernetes is only the actuator?
The answer is: • Simple • Explicit • Observable • Evolvable