Networking & Content Delivery

Introducing the LBC Ingress-to-Gateway API migration toolkit

Migrating your AWS Load Balancer Controller (LBC) Ingress resources to the Gateway API by hand is tedious and error prone. You need to rewrite annotations, path rules, and TLS configuration, and a mistake can disrupt the production traffic. The Ingress-to-Gateway API migration toolkit for LBC removes that risk by giving you a guided, validated path you can take at your own pace.

Earlier this year, we walked through migrating NGINX Ingress Controller annotations to AWS Load Balancer Controller (LBC) Ingress resources. That work translated your routing rules, TLS termination, and backend configuration smoothly to ALB-native constructs. The result was a stable, fully AWS-integrated Ingress layer. Now, we want to help you take the next step.

The toolkit starts with lbc-migrate, a CLI tool. It translates LBC Ingress resources to Gateway API equivalents. It is paired with Migration Console, a local web UI bundled into the lbc-migrate binary for reviewing dry-run plans before you migrate. In this post, we translate an Ingress manifest that uses TLS termination and a URI rewrite into equivalent Gateway API manifests, preview the result with a dry run, and verify the migrated configuration. For the Console UI, refer to the Migration Console reference linked in the tooling overview section.

Why migrate to Gateway API

The Kubernetes project now recommends the Gateway API as the successor to the Ingress API, which was frozen in March 2026. The Ingress API remains generally available and stable, with no plans for removal. It receives no further feature work, and new routing capabilities will land in the Gateway API instead. The Gateway API takes a different design approach. It provides a typed, structured extension model and a standard for traffic splitting. It also adds built-in multi-tenancy support and defines behavior through typed resources. Gateway API uses a role-oriented resource model. Infrastructure administrators manage GatewayClass and Gateway resources, while application developers own HTTPRoutes. Type-safe CRDs add schema validation at admission time. Weighted traffic splitting is first-class and supports canary deployments. Cross-namespace routing works through explicit ReferenceGrant permissions. Typed Policy resources let you extend behavior as your needs evolve. If your team runs workloads on Amazon Elastic Kubernetes Service (Amazon EKS), Gateway API project is a recommended long-term routing interface.

Migration tooling

The toolkit includes two complementary tools for a safe, validated migration from Ingress to Gateway API. One tool translates your resources. The other lets you review the result before it reaches production.

  • lbc-migrate CLI: A standalone binary that translates your LBC Ingress resources into equivalent Gateway API YAML manifests. It handles annotations, path rules, and IngressGroup configurations for you. You can point it at static YAML files, or directly at a live cluster with –from-cluster. Your existing Deployments and Services are reused as-is, so the tool generates only routing-layer resources.
  • Migration Console: A local web UI bundled within the lbc-migrate binary (lbc-migrate –console). It gives you a read-only, side-by-side view. You can compare Ingress and Gateway dry-run plans and review them with confidence before any production change. Launching the Console is optional, and the walkthrough in this post uses the CLI dry-run flow instead.

For installation instructions, CLI flags, output formats, and the annotation support table, refer to the lbc-migrate CLI Reference.

For the console UI guide and RBAC requirements, refer to Migration Console.

Migration guidance

The migration follows six steps, and you stay in control at each one. Every step is safe to pause at, so you can pause and resume whenever it suits you. First, you translate your Ingress resources into Gateway API manifests. Next, you preview the result with a dry run and confirm the plans match. You then apply the manifests. This creates new ALBs alongside your existing Ingress ALBs. After that, you verify the new Gateway ALBs are healthy. When you are confident, you shift traffic from the Ingress ALBs to the Gateway ALBs. Finally, you clean up the old Ingress resources. The CLI drives translation, application, and cleanup (Steps 1, 3, and 6). The optional Migration Console supports the Step 2 review. You can also do that review from the CLI dry-run output, as this walkthrough does.

The following table summarizes each step, what happens, and how to roll it back.

Step Action What Happens Rollback
1 Translate lbc-migrate converts Ingress YAML to Gateway API YAML Delete generated files
2 Dry-run Preview Turn on the IngressPlanAnnotation=true feature gate at the LBC level. This adds dry-run plan annotations to existing Ingress resources. The Gateway manifests from Step 1 already include the gateway.k8s.aws/dry-run: “true” annotation by default. Apply them. Review the Ingress and Gateway dry-run plans, either from the CLI output or in the optional Migration Console. No AWS resources are created. Delete dry-run Gateway
3 Apply Regenerate with –dry-run=false, apply manifests. LBC creates new ALBs alongside existing Ingress-managed ALBs. Delete Gateway resources
4 Verify Confirm Gateway status Programmed: True. Validate target group health. Monitor Amazon CloudWatch metrics on the new ALB. N/A
5 Shift Traffic Gradually move traffic from Ingress ALBs to Gateway ALBs. You have two options. The first, Amazon Route 53 weighted routing, is DNS-based and adds no extra cost. The second, AWS Global Accelerator, splits per request and needs no domain. Shift traffic back
6 Cleanup Delete Ingress resources. LBC removes old ALBs, target groups, and listener rules automatically. N/A

For the detailed step-by-step walkthrough, including CLI commands, feature gate configuration, console usage, and traffic shifting instructions, refer to the official migration guide.

Example walkthrough

Let’s walk through a real example together. We’ll take a single ALB Ingress that does TLS termination and a URI rewrite. We translate it with lbc-migrate, preview the result with a dry run, and confirm you get the same behavior on a Gateway-managed ALB. Let’s start with the prerequisites.

Prerequisites

  • Amazon EKS cluster with a managed node group
  • AWS Load Balancer Controller v3.4.0 Installed. Gateway API support turns on automatically when the CRDs are present.
  • Gateway API standard CRDs (v1.5.0) and the LBC Gateway CRDs installed:
# Standard Gateway API CRDs
kubectl apply --server-side=true \
  -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.5.0/standard-install.yaml

# LBC Gateway CRDs (LoadBalancerConfiguration, TargetGroupConfiguration, ...)
kubectl apply \
  -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v3.4.0/config/crd/gateway/gateway-crds.yaml
  • Confirm the ALB Gateway controller is live:
kubectl -n kube-system logs deploy/aws-load-balancer-controller | grep "gateway.k8s.aws/alb"
# ...Starting EventSource ... "controller":"gateway.k8s.aws/alb" ...

Step 0: The starting point: a working ALB Ingress

If you don’t already have a sample app behind the LBC, no problem. The echo server walkthrough deploys echoserver behind an ALB Ingress and verifies access in several kubectl apply commands. Complete the steps through its “Verify that you can access the service” step.

We’ll build on that base, adding the two annotations this post is about so a single Ingress carries both use cases:

  • TLS termination: listen-ports (80/443) + certificate-arn + ssl-redirect.
  • URI rewrite: transforms.echoserver strips the /api prefix before the request reaches the backend.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: demo
  namespace: demo
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
    alb.ingress.kubernetes.io/ssl-redirect: '443'
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:<acct>:certificate/<id>
    alb.ingress.kubernetes.io/healthcheck-path: /healthz
    alb.ingress.kubernetes.io/transforms.echoserver: >
      [{"type":"url-rewrite","urlRewriteConfig":{"rewrites":[
        {"regex":"^\\/api\\/(.+)$","replace":"/$1"}]}}]
spec:
  ingressClassName: alb
  rules:
    - host: demo.lbc-migrate.example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service: { name: echoserver, port: { number: 80 } }

Confirm that the configuration is correct before you migrate, since the tool assumes your Ingress is valid and currently serving:

ALB=$(kubectl get ingress demo -n demo -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')

# TLS termination: HTTP is redirected to HTTPS
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  -H "Host: demo.lbc-migrate.example.com" "http://$ALB/api/hello"
# 301 -> https://demo.lbc-migrate.example.com:443/api/hello

# URI rewrite: client asks for /api/hello, backend receives /hello
curl -sk -H "Host: demo.lbc-migrate.example.com" "https://$ALB/api/hello" | grep "real path"
#   real path=/hello

Step 1: Install lbc-migrate

You build lbc-migrate from the controller repo and install it once:

git clone https://github.com/kubernetes-sigs/aws-load-balancer-controller.git
cd aws-load-balancer-controller
make lbc-migrate          # binary lands at bin/lbc-migrate
make install-lbc-migrate  # optional: symlink onto your PATH

Step 2: Convert the Ingress

Now point the tool at your live cluster. The tool is read-only and never modifies your cluster. It reads the Ingress, Service, IngressClass, and IngressClassParams, and writes out Gateway API manifests:

lbc-migrate --from-cluster --namespaces demo --output-dir ./gateway-output
Found 1 Ingress(es), 1 Service(s), 1 IngressClass(es), 1 IngressClassParams
WARNING: url-rewrite transform with capture group reference "/$1" cannot be represented in Gateway API — skipping.
Generated 6 resource(s) in gateway-output/gateway-resources.yaml

Note: For details on which annotations are fully, partially, or not yet supported, refer to Limited Support for Certain Ingress Annotations.

The generated output maps directly to the Gateway API:

Ingress annotation Becomes
ingressClassName: alb GatewayClass aws-alb (controllerName: gateway.k8s.aws/alb)
scheme, certificate-arn LoadBalancerConfiguration (scheme, listenerConfigurations[].defaultCertificate)
listen-ports 80/443 Gateway with http-80 and https-443 listeners
ssl-redirect: ‘443’ HTTPRoute with a RequestRedirect filter (HTTP → 301 → HTTPS)
host + path → service HTTPRoute with PathPrefix: /api → echoserver
healthcheck-path, target-type TargetGroupConfiguration

TLS termination converts fully and automatically, and the generated TLS half is shown next:

apiVersion: gateway.k8s.aws/v1beta1
kind: LoadBalancerConfiguration
metadata: { name: demo-lb-config, namespace: demo }
spec:
  scheme: internet-facing
  listenerConfigurations:
    - protocolPort: HTTPS:443
      defaultCertificate: arn:aws:acm:us-west-2:<acct>:certificate/<id>
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute   # the ssl-redirect, as a portable Gateway API filter
metadata: { name: demo-redirect, namespace: demo }
spec:
  parentRefs: [{ name: demo-gateway, sectionName: http-80 }]
  rules:
    - filters:
        - type: RequestRedirect
          requestRedirect: { scheme: https, port: 443, statusCode: 301 }

The tool also emits a second HTTPRoute, the one carrying your host + path → service rule. The generated HTTPRoute is shown next. The /api match comes across, but there is no rewrite filter, the capture-group gap the warning called out. We’ll finish this route in Step 4 and hand-edit with rewrite filter.

apiVersion: gateway.networking.k8s.io/v1
 kind: HTTPRoute         # the path route - generated as-is, rewrite still missing
 metadata: { name: demo-route, namespace: demo }
 spec:
   hostnames: [ demo.lbc-migrate.example.com ]
   parentRefs: [{ name: demo-gateway, sectionName: https-443 }]
   rules:
     - matches:
         - path: { type: PathPrefix, value: /api }
       backendRefs:
         - { name: echoserver, port: 80 }

Step 3: Preview with a dry run (optional, recommended)

By default, lbc-migrate stamps generated Gateways with gateway.k8s.aws/dry-run: “true”. Applying a dry-run Gateway lets the controller validate and model the AWS resources without provisioning an ALB, a safe look before you commit. Go ahead and apply the dry-run manifests. Review and then proceed to the next step.

kubectl apply -f ./gateway-output/gateway-resources.yaml

Note: if you apply the dry-run annotation to a Gateway that is already provisioned, the controller leaves your live ALB untouched. It logs Ignoring dry-run annotation on an already-provisioned Gateway. Use dry run for previewing new Gateways, not for pausing running ones.

Step 4: Finish the URI rewrite

Recall the warning we saw in Step 2. Our Ingress used a regex with a capture group (replace: /$1). The URLRewrite filter only does static path replacement. So lbc-migrate skips the dynamic part rather than translate it incorrectly and warns you when it does.

The intent (to strip the /api prefix) is what ReplacePrefixMatch expresses natively. Take the demo-route HTTPRoute from Step 2 and add the one filter it was missing:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata: { name: demo-route, namespace: demo }
spec:
  hostnames: [ demo.lbc-migrate.example.com ]
  parentRefs: [{ name: demo-gateway, sectionName: https-443 }]
  rules:
    - matches:
        - path: { type: PathPrefix, value: /api }
      filters:
        - type: URLRewrite   # <-- the rewrite, done the Gateway API way
          urlRewrite:
            path: { type: ReplacePrefixMatch, replacePrefixMatch: / }
      backendRefs:
        - { name: echoserver, port: 80 }

Step 5: Verification

Regenerate without dry run (or just delete the annotation) and apply:

lbc-migrate --from-cluster --namespaces demo --output-dir ./gateway-live --dry-run=false
# (add the URLRewrite filter from Step 4 to ./gateway-live/gateway-resources.yaml)
kubectl apply -f ./gateway-live/gateway-resources.yaml

Now wait for the Gateway to be programmed. It provisions a new ALB alongside your existing Ingress ALB:

kubectl get gateway demo-gateway -n demo
# NAME           CLASS     ADDRESS                                              PROGRAMMED
# demo-gateway   aws-alb   k8s-demo-demogate-....us-west-2.elb.amazonaws.com    True

Test both use cases against the Gateway-managed ALB:

GW=$(kubectl get gateway demo-gateway -n demo -o jsonpath='{.status.addresses[0].value}')

# TLS termination
curl -s -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
  -H "Host: demo.lbc-migrate.example.com" "http://$GW/api/hello"
# 301 -> https://demo.lbc-migrate.example.com:443/api/hello

# URI rewrite
curl -sk -H "Host: demo.lbc-migrate.example.com" "https://$GW/api/hello" | grep "real path"
#   real path=/hello

Same behavior as the Ingress, now expressed in Gateway API.

Step 6: Clean up ingress

Because the new ALB runs alongside the old one, cutover is a DNS and traffic-shift decision that stays in your hands. When you are ready, point your record at the Gateway ALB, monitor it, then remove the Ingress:

kubectl delete ingress demo -n demo

Clean up resources

If instead you want to roll back the migration entirely, or you are done with the demo, remove the Gateway resources you created. Deleting them avoids ongoing AWS charges. Delete the Gateway, HTTPRoutes, LoadBalancerConfiguration, and TargetGroupConfiguration, and then delete the demo namespace:

kubectl delete -f ./gateway-output/gateway-resources.yaml
kubectl delete namespace demo

Recommendations

  1. Start with a dry run. The Migration Console helps you catch annotation mappings that need a manual look before you touch production.
  2. Understand your traffic path before shifting. Identify how clients reach the Ingress ALB. They may use a custom domain (e.g., api.example.com) backed by Amazon Route 53 or another DNS provider or reach it directly via the ALB DNS name. If clients use the ALB DNS directly, you cannot shift traffic without updating every client, so plan accordingly.
  3. Shift traffic gradually where your environment supports it. How you shift gradually depends on your DNS provider, load balancing service, and client configuration. Whichever mechanism you choose, make sure you can roll back quickly if anything looks off. We do not walk through a specific traffic-shifting procedure here, but for one example of CRD-based traffic management, refer to the LBC Global Accelerator documentation.
  4. Keep Ingress and Gateway running in parallel until you have validated. The dual-ALB architecture supports this kind of coexistence. Validate thoroughly and only delete Ingress resources once you have seen sustained, error-free operation on the Gateway path.
  5. Plan for Gateway API as your long-term routing interface. For new workloads, reach for HTTPRoute directly. You can reference the lbc-migrate annotation support table for current compatibility.

Conclusion

This guide helps you move to Gateway API with confidence. Gateway API is the long-term routing standard for Kubernetes. The lbc-migrate tool is here to make the transition safe and mostly automated. It translates the bulk of your LBC Ingress configuration into Gateway API manifests and gives you dry-run validation before you commit. Some annotations need a manual edit, such as the URI rewrite in Step 4. The tool flags each one so nothing is missed. Because you keep the existing Ingress ALB in place while you validate the new Gateway ALB, you can cut over on your own schedule, with confidence.

Combined with our earlier guide on migrating from NGINX Ingress to LBC Ingress, you now have a complete, validated migration path from earlier ingress controllers. It runs all the way to the Gateway API routing standard on AWS. We look forward to seeing what you build.

To get started, refer to the lbc-migrate CLI Reference, the official migration guide, and the Gateway API documentation.

About the authors

Sai Charan Teja Gopaluni

Sai Charan Teja Gopaluni

Sai Charan is a Senior Specialist SA at Amazon Web Services (AWS), focusing on container networking and Agentic AI. He brings deep expertise in Kubernetes, AWS container services, GPU-accelerated computing, and scalable model inference. Outside of work, he enjoys photography, traveling, and playing tennis.

Shuqi Zhang

Shuqi Zhang

Shuqi is a Software Development Engineer on the Elastic Load Balancing team. She works on the control plane (CloudFormation) and Kubernetes integrations that power ELB products, with a focus on IaC, Kubernetes integrations, and cloud-native networking. Outside of work, she plays badminton and enjoys hands-on crafts.

Ikenna Izugbokwe

Ikenna Izugbokwe

Ikenna is a Principal Solutions Architect at AWS specializing in networking, containers, and AI infrastructure. He guides model providers through scaling their training and inference systems while enabling rapid deployment of evolving frontier models on AWS. His work increasingly spans agentic AI – building reliable, cost-efficient multi-agent systems and the inference infrastructure behind them in production.