01What actually changed
Upload a zip of Python source. No Dockerfile. Get a running container on EKS. That is Elastic Beanstalk Cluster Mode, launched this week — Beanstalk apps now run on EKS instead of EC2 instances you own. Cloud Native Buildpacks handle the containerization: I uploaded four files (app.py, requirements.txt, Procfile, runtime.txt), Beanstalk detected Python, built the image, pushed it to ECR, and ran it.
In Standard Mode, Beanstalk provisions EC2 instances in your account, installs a platform on them, and manages the Auto Scaling group. You can SSH in. You can see the instances. That is still there and still fully supported.
Cluster Mode is different. You pick Cluster as the deployment type, hand over source code, a Dockerfile, or a container image, and Beanstalk creates and operates an EKS cluster underneath. The first deployment for a given set of subnets triggers cluster creation. Subsequent deployments reuse it.
The console makes the tradeoff unusually explicit — Standard is labelled free-tier eligible with full instance-level access; Cluster is labelled best for multiple apps that share infrastructure, powered by EKS.
02Setting it up
Environment details are unchanged from Standard — application name, environment name, domain prefix.
The application bundle is four files, zipped flat. No Dockerfile — that is deliberate, so Cloud Native Buildpacks has to do the containerization.
# app.py (excerpt)
@app.route("/healthz")
def healthz():
return "ok", 200
@app.route("/burn")
def burn():
blob = bytearray(400 * 1024 * 1024) # ~400MB
return f"allocated {len(blob)} bytes on {socket.gethostname()}"
/healthz gives the load balancer something real to hit. /burn allocates 400MB on demand, which is how we test memory limits later.
03The Builder image field is the one to understand
A Cloud Native Buildpacks builder is a container image bundling two things: a set of buildpacks (language detectors and compilers) and a base run image. Beanstalk hands it your source, it detects the language from files like requirements.txt, installs the runtime, and emits a runnable OCI image. That mechanism is the whole "no Dockerfile required" promise.
Leave it blank and you get the default. But read the supported builders carefully, because the language coverage is not uniform:
| Builder | Languages | Architecture |
|---|---|---|
paketobuildpacks/builder-jammy-base | Go, Node.js, Java, Python, Ruby, .NET, NGINX, Apache HTTP Server | amd64 only |
paketobuildpacks/builder-jammy-full | Same, plus PHP and common C libraries | amd64 only |
paketobuildpacks/ubuntu-noble-builder | Java, Node.js, .NET, NGINX, Apache HTTP Server | amd64 and arm64 |
The non-obvious trap: the only arm64-capable builder does not list Python, Ruby, Go or PHP. So if you pick Graviton nodes and deploy Python source code, buildpacks has nothing to detect with. Graviton is still available to you — but via a Dockerfile or a prebuilt arm64 container image, not the source-code path. "Bring your source, we handle the rest" quietly means amd64 for half the supported languages.
In my case the field would not stay blank. Leaving the default gave me nothing that would create; I filled in paketobuildpacks/builder-jammy-base and only then did the environment create. That value is consistent with everything else I observed: jammy-base covers Python, and it is amd64-only.
That is also why the architecture choice sits further down the same page, well away from the builder field that constrains it.
04Container attributes are Kubernetes requests and limits
Two things matter here. The plain CPU and Memory fields are reservations — what the scheduler sets aside, and what your bin-packing and bill are based on. The limit fields are ceilings. CPU over its limit is throttled: slow, alive. Memory over its limit is terminated. Not throttled. Killed.
The second thing: autoscaling targets are percentages of the request, not the limit. An 80% CPU target against a 250m request means replicas are added at 200m of real usage.
Note the info text on Memory limit: if left empty, it defaults to the Memory value. So a blank limit is not "no limit" — it is a limit equal to your request.
05Networking
Cluster subnets carry a warning worth reading twice: environments with the same subnets share an EKS cluster, and you cannot change subnets after creating the environment. That single field decides both your blast radius and whether the next environment gets a ten-minute cluster build or a fast deploy.
06Observability is on by default
Logs and metrics ship to CloudWatch without configuration. Traces are opt-in. Auto-instrumentation injects OpenTelemetry for supported languages without touching your code — I set it to Python to see what arrives that I never wrote.
Max surge 1 with max unavailable 0 means a new replica comes up before the old one goes away — zero downtime, but it briefly needs capacity for two replicas even when your minimum is one.
07Three IAM roles, created for you
The console creates all three. Worth knowing what each is for, because in CLI or IaC you supply these ARNs yourself.
08What actually happens after you hit Create
This is the part the announcement glosses over. The event stream is a genuinely good teaching artifact.
Then, in sequence:
- 17:22:55 — createEnvironment starts
- 17:23:14 — default VPC public subnets resolved (four of them)
- 17:23:25 — cluster assignment begins
- 17:23:37 — image build started for version v1
- 17:23:43 — a CodeBuild execution is launched
That CodeBuild project is the first real surprise. The "managed" build is a CodeBuild project created in your account, with a buildspec you can read.
Two details worth pulling out of that buildspec. It logs into both your private ECR and ECR Public, and it uses docker buildx with --cache-from and --cache-to pointed at a docker-cache tag in your own repository. Your build cache is a tag in your ECR repo. That is a line item, and it is also why second and subsequent builds get faster.
The output lands in a Beanstalk-named private ECR repository:
A Flask app with two dependencies produces a 150MB image. That is the buildpack base image, not your code. Reasonable, but worth knowing before you reason about pull times or ECR storage across a hundred applications.
09Then you wait for CloudFormation
With the image pushed at 17:25:03, the environment still is not up. Beanstalk is now waiting on a CloudFormation stack named beanstalk-cluster-<uuid>.
That stack is the EKS cluster. Eight minutes in, it is still in Creating.
The polling runs every two minutes, and it runs for a while. The cluster assignment finally completes at 17:36:17 — the CloudFormation wait alone accounted for just under eleven minutes. Platform components come up seven seconds later, and application resources are submitted to the cluster at 17:36:32.
So the honest end-to-end number for a first environment: 13 minutes 37 seconds from clicking Create to application resources being submitted. Of that, roughly 2 minutes was building a container image and about 11 minutes was waiting for an EKS cluster to exist. Your application is not the slow part. The cluster is.
The cluster name is generated, the Kubernetes version is chosen for you, and the support window is visible in the EKS console — which means someone, eventually, has to care about that upgrade. The whole point of Cluster Mode is that it should be AWS. Worth verifying against a real cluster in a year rather than taking it on faith.
10It launched at 16m 11s
Application resources hit the cluster at 17:36:32, health checks passed just over two minutes later, and Beanstalk called the environment launched at 17:39:07. The console reports the whole run as 16m 11s.
And the app is there:
That hostname — deployment-demo-new-eks-env-555964b898-nn4mw — is the tell. Deployment name, ReplicaSet hash, pod suffix. The abstraction is real but it is thin, and anything in your app that logs a hostname is now logging Kubernetes pod identity. Useful for correlation, mildly surprising if you were promised you would never think about Kubernetes.
And the control case for the memory test: with a 1Gi limit against a 512Mi request, a 400MB allocation succeeds.
11What the metrics actually show
Those metric names are Kubernetes cAdvisor metrics, surfaced directly in the Beanstalk console. Again: thin abstraction, and in this case a helpful one.
The numbers are the more interesting part. The app uses 76MB against a 512Mi request and 0.08 cores against a 250m request. That is roughly 15% memory and 32% CPU utilisation of what is reserved — and reservation is what drives bin-packing and therefore cost. On a single environment this is invisible. Across a portfolio it is the entire economics of Cluster Mode: every over-provisioned request is capacity the scheduler cannot give to anything else. Right-sizing requests matters more here than it ever did on Standard.
There is also an "Investigate with AI" entry point on the metrics panel, which is a different thing from the log AI analysis below.
12Logs, and the limit of the AI troubleshooting
Logs are pull-based: you click Request logs, Beanstalk retrieves the last 100 lines or the full set, and the retrieved file expires after 15 minutes.
Worth noticing at the top of that log: an ImportError for libsqlite3.so.0. The buildpack run image does not carry it. Nothing in this app needs sqlite, so it is harmless here — but it is a concrete illustration of why the builder choice matters. The jammy-full builder exists precisely for dependencies that need common C libraries. On jammy-base, you find out at runtime.
Now the finding I most wanted from this exercise. The AI Analysis button is greyed out, with a tooltip:
That is a reasonable product decision and a real constraint worth stating plainly. The AI troubleshooting is a reactive tool, not an inspection tool. You cannot ask it to explain a healthy-but-odd environment, sanity-check a config, or investigate slow-but-alive behaviour. It waits for the health check to fail. In Cluster Mode, where you do not have kubectl and the logs are pull-based and expire in fifteen minutes, that narrows your options for the whole class of problems that never trip a health check.
13Teardown
Terminating asks you to type the environment name, which is the right amount of friction. Read the event it emits, though: "Starting to delete application resources." Application resources.
Terminating the environment does not delete the EKS cluster. I terminated my only environment, and the cluster was still there. I had to go to the EKS console and delete it by hand — typing the cluster name to confirm.
This follows logically from the design — the cluster is shared across every environment on those subnets, created by its own CloudFormation stack, and described by the service as a one-time operation. It should not vanish when one tenant leaves. But the consequence is that the documented teardown path leaves a billing EKS control plane behind. Anyone who terminates their environments, sees the Beanstalk console go empty, and assumes they are done will keep paying the control plane fee for a cluster running nothing.
And read the second warning in that dialog, because it survives both teardowns: associated scrapers are not deleted with the cluster. If you enabled metrics collection — which is on by default — there is an Amazon Managed Prometheus scraper billing separately. Click View scrapers before you close that tab.
So the real teardown checklist, in order:
- Terminate every Beanstalk environment
- Delete the associated scrapers (Amazon Managed Prometheus)
- Delete the EKS cluster manually
- Delete the orphaned
beanstalk-cluster-*CloudFormation stack - Check EC2 for a surviving load balancer
- Delete the ECR repository — it holds both your image and the
docker-cachetag the buildspec writes, so two billed artifacts per application - Delete the
/aws/elasticbeanstalk/...CloudWatch log groups
Deleting the application itself is a separate type-to-confirm step after the environment is gone:
Seven steps, one of which the service does for you. For a product whose entire proposition is "AWS takes operational responsibility," the exit is remarkably manual — and it is the one part of the lifecycle where getting it wrong costs money quietly, forever.
14The complete event log
Every event from create to terminate, in order. This is the most useful artifact of the whole exercise — it is the service narrating its own internals.
17:22:55 createEnvironment is starting.
17:23:14 Using default VPC public subnets. subnets='[subnet-0b503c7e5bc6a5580,
subnet-0335d39e8bd9ff495, subnet-0846724ffcecf449d, subnet-07d980776fa7582a4]'
17:23:25 Starting cluster assignment for environment: Demo-New-Eks-env
17:23:37 Image build started for version v1
17:23:43 Started image build CodeBuild execution: arn:aws:codebuild:us-west-2:...
17:24:02 Creating CloudFormation stack for cluster infrastructure named:
beanstalk-cluster-182eeccb-... This is a one-time operation and
generally takes about 10 minutes.
17:24:10 Environment health has transitioned to No Data.
17:25:17 Image build completed for version v1 with image URI
...elasticbeanstalk-demo-new-eks-7a910c:v1@sha256:67aea994...
17:25:33 Waiting for CloudFormation stack 'beanstalk-cluster-182eeccb-...'
17:27:34 Waiting for CloudFormation stack 'beanstalk-cluster-182eeccb-...'
17:29:35 Waiting for CloudFormation stack 'beanstalk-cluster-182eeccb-...'
17:31:36 Waiting for CloudFormation stack 'beanstalk-cluster-182eeccb-...'
17:33:37 Waiting for CloudFormation stack 'beanstalk-cluster-182eeccb-...'
17:36:17 Successfully completed cluster assignment for cluster:
arn:aws:eks:us-west-2:...:cluster/beanstalk-cluster-182eeccb-...
17:36:24 Waiting for platform components to become ready
17:36:32 Application resources submitted to EKS cluster. Waiting for them to become ready.
17:36:47 Creating load balancer named: 2ba46c89eadf3ae4-demo-new-eks-en.
This may take a few minutes.
17:38:32 Application replica ready: deployment-demo-new-eks-env-555964b898-nn4mw
17:38:32 Deployment ready: deployment-demo-new-eks-env
17:38:47 Load balancer ready: 2ba46c89eadf3ae4-demo-new-eks-en
17:38:47 Deployment passed health checks
17:38:56 Application available at Demo-New-Eks-env.eba-dpcr9y8z.us-west-2.elasticbeanstalk.com
17:39:07 Successfully launched environment: Demo-New-Eks-env
17:40:10 Environment health has transitioned from No Data to Ok.
Insufficient request rate to determine application health.
17:43:25 requestEnvironmentInfo is starting.
17:43:26 Starting diagnostic log collection.
17:43:33 Diagnostic logs collected successfully.
17:45:18 Starting to terminate environment: Demo-New-Eks-env
17:45:30 Starting to delete application resources
Four things in there are worth pulling out.
AWS tells you the cost up front. At 17:24:02: creating the CloudFormation stack for cluster infrastructure is "a one-time operation and generally takes about 10 minutes." It took just over twelve. The service is explicit that this is paid once — which is the whole shared-infrastructure argument, stated in an INFO event rather than a marketing page.
The build and the cluster run in parallel. The image build completed at 17:25:17, while the CloudFormation stack was still being created. Beanstalk is not serialising these. So a slower build — a Java service with a real dependency tree — would likely cost you nothing extra on a first deploy, because it is hiding inside the cluster wait. On a second deploy, with no cluster to build, your build time becomes the entire deploy time.
Health reporting has its own lag. The environment launched at 17:39:07 but health only moved from No Data to Ok at 17:40:10 — a further minute — with the note "Insufficient request rate to determine application health." A freshly launched environment with no traffic cannot be assessed. Combined with AI Analysis only unlocking when unhealthy, the first minutes of a new environment are the least observable.
Log collection is an explicit, audited operation. Requesting logs shows up as three events: requestEnvironmentInfo, collection starting, collection succeeded. Logs are not streaming to you by default — you ask, the service goes and gets them, and the retrieved file expires in fifteen minutes.
15The shape of the timeline
| Stage | Elapsed |
|---|---|
| Submit → subnets resolved | 19s |
| → CodeBuild running | 48s |
| → image pushed to ECR | 2m 08s |
| → EKS cluster assigned | 13m 22s |
| → app resources submitted | 13m 37s |
| → health checks passed | 15m 52s |
| → environment launched | 16m 11s |
| → health reporting Ok | 17m 15s |
The build is not the bottleneck. The cluster is — about eleven of those sixteen minutes. And that cost is paid once per set of subnets, which is exactly why Cluster Mode's economics depend on you running more than one application.
Deploy a second environment onto the same subnets and you should skip the entire CloudFormation wait. That delta is the whole business case, and it is the number worth measuring yourself.
16Cost, honestly
There is no charge for Cluster Mode itself. The underlying resources are another story: the EKS control plane fee, EKS Auto Mode compute at roughly a 12% premium over EC2 instance cost, ECR (including that build cache), CodeBuild minutes, and CloudWatch. It is not Free Tier eligible.
AWS is unusually direct about where the line sits. Workloads under $500/month are called out as a poor fit — the control plane fee and Auto Mode premium add overhead a single application cannot offset through bin-packing. Also staying on Standard: single-environment use cases, Windows and .NET Framework on IIS, and anything that cannot be containerized.
17What Cluster Mode cannot do yet
Going through the Actions menu on a Cluster environment turns up a consistent tooltip: Not supported for EKS environments. Several familiar Beanstalk operations are simply absent.
Collectively that is: no restore of a terminated environment, no rebuild, no clone, no restart of app servers, no domain swap. Some of these are conceptually odd under Kubernetes — "restart app servers" does not map cleanly onto a Deployment you do not control, and rebuild means something different when the infrastructure is shared. Others are real gaps. Clone is how a lot of teams stand up a staging copy of production. Swap environment domain is how a lot of teams do blue/green.
Traffic-splitting deployments with automatic rollback are listed as a Cluster Mode capability, so the blue/green story exists — it just is not the domain-swap mechanic Beanstalk users have muscle memory for. If your runbooks contain the words "clone the environment" or "swap the CNAME," they need rewriting before you migrate, and that is a bigger task than changing a radio button.
18What I would actually do
Set a budget alert before you create anything, and write the teardown checklist down before you need it. The cluster outliving the environment is the failure mode that will actually cost someone money.
Right-size your requests before anything else. 76MB of real usage against a 512Mi reservation is the kind of gap that costs nothing on one environment and costs real money across forty.
Do not migrate. Deploy something new into Cluster Mode — a low-stakes internal service — and watch the bill for a month with only one application on the cluster. That is your worst case. Then add a second and see whether the sharing math holds.
And check the builder matrix before you promise anyone Graviton — and know that the AI troubleshooting will not talk to you until something is already broken.
Beanstalk was always the service you picked when you did not want to think about the compute. Cluster Mode keeps that promise while quietly making EKS the default answer for teams with a portfolio. Whether that is simplification or abstraction debt depends entirely on how many applications you are running.
Reference: AWS News Blog — AWS Elastic Beanstalk introduces Cluster Mode · Cluster Mode documentation