AWS Load Balancer Controller: Ingress, Service and Gateway API on EKS
A Kubernetes controller for Elastic Load Balancers
At a glance
- What is it?
- The controller reconciles Kubernetes Ingress, Service and Gateway resources into ALBs and NLBs. It is a solid default for AWS clusters, but the IAM policy and the CRD scope are where adopters get stuck.
- Who is it for?
- Adopt it if you run Kubernetes on AWS and want Ingress and Service objects to produce real ALBs and NLBs without a separate provisioning pipeline. Do not adopt it if you are not on AWS, or if you need a load balancer that is not an ELB.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the controller does that kubectl alone cannot
A Kubernetes Ingress object is a specification with no implementation. Nothing in a stock cluster reads it and creates a load balancer. On AWS that gap is filled by this controller, which watches Ingress resources and provisions Application Load Balancers, watches Service resources and provisions Network Load Balancers, and watches Gateway API resources and provisions either type depending on the listener protocol.
The audience is narrow and specific: platform and infrastructure engineers running Kubernetes on AWS who want the cluster's own API objects to be the source of truth for edge traffic. If your load balancers are created by Terraform and referenced from Kubernetes by annotation or by a fixed target group, this controller is not the tool you are looking for. It wants to own the ELB lifecycle, not consume an ELB someone else built.
The project descends from the AWS ALB Ingress Controller, which Ticketmaster and CoreOS originated, and was donated to Kubernetes SIG-AWS in 2018. The README states the rename explicitly: "This project was formerly known as AWS ALB Ingress Controller". That history matters when you read older blog posts, because the annotation set and the Helm chart name changed with the rebrand.
How reconciliation actually flows through the AWS APIs
The controller is a Go binary built around the AWS SDK for Go v2. The go.mod file lists the service clients it depends on directly: elasticloadbalancingv2 for the load balancers and target groups, ec2 for subnets and security groups, acm for certificates, route53 for DNS records, wafv2 and wafregional for web ACL association, shield for protection, servicediscovery for Cloud Map, globalaccelerator, appmesh, ecr, sts and resourcegroupstaggingapi.
That dependency list is the clearest description of the controller's reach. It does not just call CreateLoadBalancer. It resolves subnets from cluster tags, attaches security groups, validates certificates in ACM, optionally writes Route 53 records, and can bind a WAF web ACL to the ALB. Each of those is a separate AWS API surface with its own IAM permission requirement.
The reconciler model is the standard Kubernetes controller pattern: desired state comes from the Kubernetes objects, observed state comes from AWS API reads, and the controller issues the calls needed to close the gap. It also registers webhooks, visible in the top-level webhooks/ directory, which is how it validates and mutates the custom resources it defines under apis/ and config/crd/. The practical consequence is that the controller needs both cluster-wide read access to Ingress, Service and Gateway objects and broad write access to ELB, EC2 and often Route 53.
Installing the controller and getting one Ingress served
The project ships a Helm chart under helm/aws-load-balancer-controller, and the Makefile shows the published image reference used for v3.5.0: public.ecr.aws/eks/aws-load-balancer-controller:v3.5.0. The chart is the documented path; the live docs site is at kubernetes-sigs.github.io/aws-load-balancer-controller.
Before the chart, the cluster needs the IAM permissions the controller will assume. The README does not inline the policy document, so take it from the live docs rather than reconstructing it from the go.mod service list, which tells you which APIs are touched but not which actions are required.
Building the controller from source is done through the Makefile, which sets the image URL and the base image used for the container build:
IMG ?= public.ecr.aws/eks/aws-load-balancer-controller:v3.5.0
GOLANG_VERSION ?= $(shell cat .go-version)
BUILD_IMAGE ?= public.ecr.aws/docker/library/golang:$(GOLANG_VERSION)
BASE_IMAGE ?= public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-nonroot:2026-07-27-1785178906.2023
IMG_PLATFORM ?= linux/amd64,linux/arm64The Dockerfile compiles the binary with the version package path that the release tooling injects build metadata into:
ENV VERSION_PKG=sigs.k8s.io/aws-load-balancer-controller/v3/pkg/version
RUN --mount=type=bind,target=. \
--mount=type=cache,target=/root/.cache/go-build \
GIT_VERSION=$(git describe --tags --dirty --always) && \
GIT_COMMIT=$(git rev-parse HEAD) && \
BUILD_DATE=$(date +%Y-%m-%dT%H:%M:%S%z) && \
GOOS=${TARGETOS} GOARCH=${TARGETARCH} GO111MODULE=on \
go build -buildmode=pie -tags 'osusergo,netgo,static_build' -ldflags="-s -w -linkmode=external -extldflags '-static-pie' -X ${VERSION_PKG}.GitVersion=${GIT_VERSION} -X ${VERSION_PKG}.GitCommit=${GIT_COMMIT} -X ${VERSION_PKG}.BuildDate=${BUILD_DATE}" -mod=readonly -a -o /out/controller main.goThe result is copied into the runtime image and started directly:
COPY --from=build /out/controller /controller
ENTRYPOINT ["/controller"]What you should see after installing the chart is an ALB appear in the AWS console with the cluster's tag, and the Ingress object gain an address once the load balancer is active. Provisioning is not instant: the controller waits on the ELB API, so an Ingress that shows no address for a minute or two is normal rather than broken.
Where the controller is the wrong tool
The controller assumes it owns the load balancer. If your organization creates ALBs through Terraform with a fixed naming scheme, security group set and listener configuration, handing lifecycle control to a Kubernetes controller will fight your existing pipeline. You can point the controller at a pre-existing load balancer with annotations, but at that point you are using it as a target-group registrar, and the annotation surface is doing more work than the CRD model.
It is also AWS-only by construction. The SDK clients in go.mod are all AWS services. There is no provider abstraction and no path to a non-AWS load balancer.
A quieter failure mode is IAM drift. Because the controller calls EC2, ACM, Route 53, WAF and Shield depending on which features you enable, a policy that is sufficient for a plain internet-facing ALB will fail the moment someone adds a certificate annotation or a WAF annotation. The error surfaces as a reconciliation failure on the Ingress, not as an install-time validation, so the feedback loop is slower than it should be. The README does not document this failure class; it is a consequence of the service list in go.mod.
The Gateway API support is the newest surface. The Makefile moves gateway.k8s.aws CRDs into config/crd/gateway/ and copies a combined gateway-crds.yaml into the Helm chart, which tells you the Gateway CRDs are shipped alongside the chart. If those CRDs are not installed in the cluster, the Gateway resources the controller watches will not exist as objects at all, and nothing will reconcile.
Compared to running the AWS Load Balancer Controller from a GitOps pipeline
The realistic alternative is not a competing controller. It is keeping ELB provisioning in Terraform and using the Kubernetes AWS Load Balancer Controller only for target group binding, or skipping the controller entirely and pointing a Kubernetes Service of type LoadBalancer at a pre-provisioned NLB through the cloud provider's own integration.
The difference in approach is where the desired state lives. With this controller, the Ingress or Service object is authoritative: change the annotation, the load balancer changes. With Terraform, the .tf file is authoritative and Kubernetes only registers endpoints. The first model is faster to iterate on and keeps application teams self-service. The second model gives you a reviewable diff, a plan step, and a single audit trail for every load balancer in the account.
There is no clean hybrid. Once the controller creates an ALB, Terraform will either ignore it, fight it, or import it and then fight it. Teams that try to manage the same ELB from both sides tend to end up with listener rules that flap. Pick one owner per load balancer and keep it.
Version pinning, upgrade cost and licence terms
Releases are frequent. The recent tags are v3.5.0 on 2026-08-03, v3.4.3 on 2026-07-29 and v3.4.2 on 2026-07-13. The last push to the default branch was on 2026-09-23, so the repository is not archived and is being worked on.
That cadence has an operational cost. The Makefile pins the published image to a specific tag, and the go.mod module path is versioned as sigs.k8s.io/aws-load-balancer-controller/v3, so major version bumps are import-level changes. If you vendor or build the controller yourself, a v3 to v4 transition will require updating the module path, not just a tag. If you consume the Helm chart, the upgrade is a chart version bump plus a controller restart, but the IAM policy should be re-checked against the docs for the target release, because the service list in go.mod grows between versions and permissions follow.
The licence is Apache-2.0, per the LICENSE file and the badge in the README. That is a permissive licence with an explicit patent grant and a requirement to preserve notices. It does not impose copyleft obligations on your own code. This is a description of the licence text, not legal advice; if you redistribute a modified controller, read the LICENSE file yourself.
One more upgrade consideration: the CRDs under config/crd/ are part of the release. Upgrading the controller without applying the corresponding CRD updates is a common way to get a controller that starts but cannot reconcile the newer fields.
Editorial conclusion
Adopt it if you run Kubernetes on AWS and want Ingress and Service objects to produce real ALBs and NLBs without a separate provisioning pipeline. Do not adopt it if you are not on AWS, or if you need a load balancer that is not an ELB. Before installing, verify three things: that your IAM policy matches the current documented policy for v3.5.0, that the Gateway API CRDs are present if you intend to use Gateway resources, and that your cluster version is supported by the release you are pinning.
Frequently asked questions
What is the AWS Load Balancer Controller?
It is a Kubernetes controller that manages Elastic Load Balancers for a cluster. It satisfies Ingress resources with Application Load Balancers, Service resources with Network Load Balancers, and Gateway resources with either type.
How do I install the AWS Load Balancer Controller?
The project ships a Helm chart under helm/aws-load-balancer-controller, and the live docs at kubernetes-sigs.github.io/aws-load-balancer-controller describe the setup. The cluster needs an IAM role with the documented policy, typically bound to a service account, before the chart is installed.
How does the AWS Load Balancer Controller work?
It watches Kubernetes Ingress, Service and Gateway objects, reads the current state from the AWS APIs, and issues the calls needed to reconcile the two. The go.mod file shows the AWS SDK clients it uses, including elasticloadbalancingv2, ec2, acm, route53, wafv2 and shield.
How do I upgrade the AWS Load Balancer Controller?
Releases are frequent, with v3.5.0, v3.4.3 and v3.4.2 among the recent tags. The README does not document a rollback procedure, so it is silent on how to revert a controller upgrade.
What is an alternative to the AWS Load Balancer Controller?
The alternative is keeping ELB provisioning in Terraform and letting Kubernetes only register endpoints, which moves the authoritative desired state out of the Ingress or Service object and into the .tf file.
How do I deploy the AWS Load Balancer Controller?
Deployment goes through the Helm chart under helm/aws-load-balancer-controller, with the cluster name and service account settings supplied as chart values, after the IAM role and policy are in place.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/kubernetes-sigs-aws-load-balancer-controller)