re:Invent 2025 - The Shapeshifting Application: Architecture That Transforms Across AWS
When requirements change and your team needs to move an application from serverless to containers (or the reverse), the question is rarely about deployment scripts. It's about whether your code is structured to survive the move without a significant rewrite.
At re:Invent 2025 session CNS426, Sai Charan Teja Gopaluni, Senior Specialist Solutions Architect for Containers at AWS, and Leandro Cavalcante Damascena, Senior Specialist Solutions Architect for Powertools at AWS, demonstrated how Clean Architecture and the ports-and-adapters pattern let you write application code once and deploy it across AWS Lambda, Amazon Elastic Container Service (Amazon ECS), and Amazon Elastic Kubernetes Service (Amazon EKS) without changing your business logic. In this post, we'll walk through the architecture pattern they used, the specific code techniques that make runtime portability possible, and how a single container image ran the same application on both EKS and Lambda in a live demo.
The cost of coupling business logic to infrastructure
The session opened with a familiar scenario: teams typically pick a compute service based on their first use case. Lambda attracts early adoption because of its simple deployment model and scale-to-zero behavior. But as applications mature, execution time limits, memory constraints, or platform team standardization on containers push teams toward Amazon ECS or Amazon EKS. When that shift happens, the pain is rarely the deployment configuration. It's the application code itself.
Leandro walked through a Python FastAPI monolith where a save_user function did far more than save a user. It imported boto3, hard-coded a reference to Amazon DynamoDB as the database, read a table name from a specific environment variable format, and assumed a particular method for accessing configuration. Three separate concerns (business logic, database implementation, and infrastructure configuration) were collapsed into a single function. Testing it required either real AWS infrastructure or a local emulator, because there was no way to inject a mock dependency.
This coupling compounds as the application grows. An e-commerce application with users, orders, catalog, and checkout services means repeating the same infrastructure assumptions in every module. Moving to a new runtime means touching all of them. Even a well-intentioned helper function only obscures the problem rather than solving it.
Clean Architecture as the foundation for portability
The session's solution draws on Clean Architecture, which places core business logic at the center and treats all infrastructure as a pluggable concern. Three layers make this work in practice.
The domain layer defines what a user is: validation rules, field types, email format requirements, all written in pure Python with no external dependencies. This code has no knowledge of any database, SDK, or AWS service. The ports layer defines contracts as abstract interfaces. A UserRepository port describes what operations are possible (create a user, find by ID, delete) without specifying how they're implemented. A ConfigPort interface defines two methods: get(key) for retrieving a configuration value and get_table_name(entity) for resolving a table name. These contracts describe behavior but contain no implementation.
Adapters fulfill those contracts per runtime. A LambdaConfigAdapter implements ConfigPort by reading table names from Lambda environment variables. An ECSConfigAdapter implements the same interface by fetching values from AWS Systems Manager Parameter Store. An EKS adapter could read from a mounted configuration file. Each adapter satisfies the same contract; the application code calls the same interface regardless of which adapter is active.
A factory function ties everything together at startup. Before the FastAPI application initializes, it reads a RUNTIME environment variable, selects the correct adapters, and injects them into the use cases. The application itself contains no branching logic for runtime type. It receives already-resolved dependencies. Unit tests gain a significant benefit from this structure: you can pass a simple in-memory mock that satisfies the UserRepository interface without any database or AWS infrastructure in the loop.
Leandro acknowledged a fair trade-off raised by an audience member: this approach adds files and initial structure compared to a single-file monolith. For a prototype or a small startup application, a monolith on Lambda is a reasonable starting point. The value of the additional structure appears when you're maintaining the application across a team, need to swap a persistence layer without touching business logic, or face a platform migration that would otherwise require broad refactoring.
Deploying one application to multiple compute options
With the architecture in place, Sai demonstrated the deployment side using a single Dockerfile at the project root. The image builds once from the Clean Architecture codebase and supports both AMD64 and x86 architectures for Graviton and Intel-based instances. This single image was then deployed to both Amazon EKS and Lambda.
On Amazon EKS, the demo used a cluster running EKS Auto Mode, which automates data plane infrastructure management. A Kubernetes deployment manifest referenced the shared image, set RUNTIME=EKS, and a Kubernetes Service created a Network Load Balancer (NLB) to expose the application externally. Two pods came up on auto-provisioned nodes and transitioned to running status within minutes of the deployment command.
For Lambda, one structural change was required. Lambda's execution model does not support a persistent HTTP entrypoint. The team used Mangum, a Python library that converts any FastAPI or Flask application into a Lambda handler, and a Lambda-specific Dockerfile that places the handler at the task root and uses the Lambda Python base image. Setting RUNTIME=Lambda told the factory to activate the Lambda config adapter. An Application Load Balancer (ALB) fronted the function to make the endpoint accessible for the live demo, though in a production environment you can invoke the function directly from within a VPC using AWS Identity and Access Management (IAM) authentication.
Both deployments ran the same user registration and order processing logic. A user created through the EKS endpoint produced a user ID. An order submitted with that ID through the Lambda endpoint processed successfully against the same DynamoDB table. Leandro also noted that a mixed deployment is a natural extension of this pattern: an orders service with sharp scaling requirements might run on Lambda, while a user management service with longer-running operations runs on Amazon EKS. Both services share the same domain model and port contracts, with the compute choice made at the infrastructure layer rather than the code layer.
The session also touched on recent service additions that simplify data plane management across all three compute options. EKS Auto Mode automates node provisioning and lifecycle. Amazon ECS Managed Instances provides equivalent automation for ECS workloads. Lambda Managed Instances, announced at re:Invent 2025, brings a similar managed infrastructure model to Lambda. These features reduce the operational overhead of each compute option, making the portability story more practical for teams managing their own infrastructure.
The key insight from this session is that compute portability is a software design concern first. When your business logic has no direct dependency on any specific infrastructure, changing the runtime becomes a configuration decision rather than a refactoring effort. Three practices make this work: define domain entities with no infrastructure imports; express every external dependency (database, configuration, messaging) as an interface that the domain calls but does not implement; and write adapters that fulfill those interfaces per runtime, selected by a factory at startup.
The code from the session is available on GitHub and includes deployment scripts for Lambda, Amazon ECS, and Amazon EKS alongside the full Clean Architecture application. If you're building an application that may eventually span multiple compute options, or if you're facing a migration that seems to require touching the entire codebase, this pattern gives you a concrete structure to work toward.
Watch the full session: The Shapeshifting Application: Architecture That Transforms Across AWS (CNS426)
- Language
- English
