Prerequisites
Tools required:- Node.js 24+, npm
- AWS CLI v2 (configured with appropriate Identity and Access Management (IAM) permissions)
- Cloud Development Kit (CDK) CLI (
npx cdkor globally installed) - Docker or Finch (for container image builds)
- Access to a Cloudflare account (to configure Domain Name System (DNS) + proxy)
- TypeScript / Node.js development
- Basic AWS CDK familiarity (deploy, bootstrap)
- Cloudflare dashboard (DNS records, Secure Sockets Layer (SSL) settings)
- Additional tools:—
kubectl - Additional IAM permissions:—EKS, Elastic Container Registry (ECR), Virtual Private Cloud (VPC), Elastic Load Balancing (ELB) permissions on your AWS IAM role
- Additional knowledge assumed:—Basic Kubernetes concepts (pods, deployments, services, ingress)
Glossary
Architecture and component model
Every add-on deployment has four logical components in the request path.Default architecture (API Gateway + Lambda)
When using the default architecture, Alexa sends the HTTPS requests to the AWS API Gateway, which then invokes a Lambda function. Each operation the add-on can perform has handler for the Lambda entry point and operation code for the business logic.
Self-hosted architecture
For the self-hosted architecture, Alexa sends the HTTPS requests to your configured Edge Proxy (such as CloudFlare), which routes to your gateway (network load balancer). Operations are then handled via smithyServiceHandler.
The Edge Proxy, Gateway, and Compute layers are platform-specific. For the EKS example, they might look like the following.
Differences between default and self-hosted architectures
What stays the same across both architectures:- Alexa sends the same signed HTTPS request
verifyRequest()does the same cert-based authoperations/*.tsbusiness logic is identicalalexa-ai testbasic auth mechanism is unchanged
- You add an Edge Proxy (Cloudflare) — handles TLS so origin doesn’t need a valid cert
- Gateway changes from managed (API Gateway with OpenAPI routing) to self-managed (NLB + nginx)
- Compute changes from serverless (one Lambda per operation) to a long-running process (one server, all operations)
- Security boundary is unchanged — the auth mechanism (certificate verification) is identical code running in a different hosting layer. No new attack surface is introduced by the migration itself.
Make the code changes
Step 1: Create action/home-services/src/server.ts
This is the new Compute entry point — a single Node.js HTTP server that handles request authentication, operation routing, and business logic. This server replaces the Lambda handlers used in the default architecture.
Step 2: Create action/home-services/src/types.ts
This is a shared type used by server.ts and all operation files.
operations/*.ts imports to use the new type. You can use the following command in your terminal to change them all at one time.
Step 3: Remove Lambda artifacts and update dependencies
The file deletions and most dependency changes apply to all platforms. The following sections call out EKS-specific dependency changes separately. Delete the old Lambda files.package.json to remove the Lambda dependencies. For EKS, you add the new EKS dependency.
Remove (all platforms):
@alexa-ai/category-cdk-constructs@aws-smithy/server-apigateway@types/aws-lambda
- Move
@aws-smithy/server-nodefrom devDependencies to dependencies (it is needed at runtime in the self-hosted server)"^1.0.0-alpha.5" @types/node"^24.10.1"(devDependencies)
@aws-cdk/lambda-layer-kubectl-v32"^2.1.0"(dependencies)aws-cdk-lib→"^2.260.0"— EKS L2 constructs require ≥2.260aws-cdk→"^2.260.0"— must matchaws-cdk-lib
npm install.
Verify: At this point, npm run build should complete without errors.
Step 4: Update action/home-services/scripts/bundle/index.js
Bundle only server.ts — the single production entry point (no Lambda handlers):
npm run clean && npm run bundle should produce only action/build/dist/server.js. (The clean script should already exist in your template’s package.json.)
Step 5: Create Dockerfile (project root)
This is a container image for the Compute component.
docker build --platform linux/amd64 -t addon-service . should complete successfully.
Step 6: Provision gateway + compute infrastructure
Delete the old Lambda CDK stack:package.json, tsconfig.json, and cdk.json — no separate package needed. Dependencies were already added in Step 3: Remove Lambda artifacts and update dependencies.
Root cdk.json (verify it points to the correct path):
action/infrastructure/cdk/bin/app.ts:
action/infrastructure/cdk/lib/eks-addon-infra-stack.ts:
Provisions the Gateway and Compute infrastructure: VPC, EKS cluster, NLB (restricted to Cloudflare IPs), nginx ingress controller, ECR repo, and Kubernetes deployment/service/ingress manifests.
npx cdk synth should produce a CloudFormation template without errors.
Step 7: Update config_builder.ts
Point the add-on endpoint to your Edge Proxy (Cloudflare) URL. This is the public entry point Alexa calls when sending requests to your add-on:
Deploy your add-on
In the following steps, you deploy your add-on so that you can verify that traffic flows through the full path from Alexa -> Edge Proxy -> Gateway -> Compute.Step 1: Build and bundle
Run the following command to build and bundle the add-on.Step 2: Deploy infrastructure
Provision your load balancer and container runtime using your platform’s tooling. Your goal at the end of this step is a load balancer DNS hostname to use as the Cloudflare CNAME target when you configure your Edge Proxy later. For the EKS example, you do this with the following commands:EcrRepoUri— needed for pushing container imagesClusterName— needed forkubectlaccessKubeconfigCommand— run this to configurekubectl
aws codeartifact login, verify with aws sts get-caller-identity before proceeding.
Step 3: Push the container image to registry
You can use any container registry accessible from your compute platform, such as Elastic Container Registry (ECR). The EKS example uses ECR.Step 4: Deploy pods and start compute
This example shows usingkubectl for EKS. Replace with your platform’s equivalent.
Step 5: Configure your Edge Proxy
To complete this step, you need your load balancer DNS hostname from the earlier Step 2: Deploy infrastructure. In the Cloudflare dashboard:-
Get your load balancer DNS hostname:
- EKS:
kubectl get svc -n ingress-nginx(copy theEXTERNAL-IPvalue) - Other platforms: retrieve from your platform’s console or CLI.
- EKS:
-
Add a CNAME record:
your-subdomain→<load balancer DNS hostname>. - Set Proxy status: Proxied (orange cloud).
- Set SSL/TLS mode: Full (not Strict — the origin does not need a valid public certificate).
curl command. This should return {"status":"healthy"} once DNS propagates.
Step 6: Deploy add-on manifest to Alexa
Run the following command to deploy your add-on manifest to Alexa.Verify your add-on end-to-end in the Alexa simulator
Open the Alexa web simulator and invoke your add-on to confirm traffic flows through the full path (Alexa → Edge Proxy → Gateway → Compute). For details about using the web simulator, see Test with the Web Simulator. Make sure you start a new simulator session to avoid issues with caching. Older sessions might still route to a stale endpoint. Invoke operations like “get services” or “list professionals” and confirm you get valid responses from your service. For a detailed comparison, see Summary of what changes.Summary of what changes vs. what stays the same
The following table shows the differences between the default Lambda architecture and the self-hosted solution.Directory structure
A self-hosted add-on EKS-hosted example has the following project structure.Known issues
Cloudflare Web Application Firewall (WAF) blocks curl/local requests
If Cloudflare WAF/bot protection is active, direct curl requests from your laptop are blocked (403). Alexa’s server-to-server traffic passes through fine. This is expected — test via the Alexa Simulator, not curl.Running server.ts locally (CJS/ESM conflict on Node.js 24)
server.ts cannot be run directly with ts-node or tsx due to a CJS/ESM conflict in node-forge (used by @alexa-ai/category-request-authorizer). Use the bundled output for local testing:
local/server.ts (which skips verifyRequest) still works with tsx for alexa-ai test purposes.

