From bd130568d67f4d7c980aca9def858f59d9d90417 Mon Sep 17 00:00:00 2001 From: Victor Skvortsov Date: Mon, 13 Apr 2026 15:02:52 +0500 Subject: [PATCH] Set up HTTP ALB listener for ACM gateway --- docs/docs/concepts/gateways.md | 2 +- .../_internal/core/backends/aws/compute.py | 27 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/docs/docs/concepts/gateways.md b/docs/docs/concepts/gateways.md index cbe6311e9..ca15a7996 100644 --- a/docs/docs/concepts/gateways.md +++ b/docs/docs/concepts/gateways.md @@ -119,7 +119,7 @@ If you disable [public IP](#public-ip) (e.g. to make the gateway private) or if `dstack` supports the following certificate types: * `lets-encrypt` (default) — Automatic certificates via [Let's Encrypt](https://letsencrypt.org/). Requires a [public IP](#public-ip). - * `acm` — Certificates managed by [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/). AWS-only. TLS is terminated at the load balancer, not at the gateway. + * `acm` — Certificates managed by [AWS Certificate Manager](https://aws.amazon.com/certificate-manager/). AWS-only. TLS is terminated at the load balancer, not at the gateway, and HTTP requests are redirected to HTTPS by the ALB. Requires a VPC with at least two subnets in different availability zones to provision a load balancer. If `public_ip: False`, subnets must be private and have a route to NAT gateway. * `null` — No certificate. Services will use HTTP. diff --git a/src/dstack/_internal/core/backends/aws/compute.py b/src/dstack/_internal/core/backends/aws/compute.py index 225f4243c..d78b1762a 100644 --- a/src/dstack/_internal/core/backends/aws/compute.py +++ b/src/dstack/_internal/core/backends/aws/compute.py @@ -89,6 +89,7 @@ class AWSGatewayBackendData(CoreModel): lb_arn: str tg_arn: str listener_arn: str + http_listener_arn: Optional[str] = None # None for old gateways class AWSVolumeBackendData(CoreModel): @@ -594,7 +595,7 @@ def create_gateway( ) logger.debug("Registered ALB target for gateway %s", configuration.instance_name) - logger.debug("Creating ALB Listener for gateway %s...", configuration.instance_name) + logger.debug("Creating HTTPS ALB listener for gateway %s...", configuration.instance_name) response = elb_client.create_listener( LoadBalancerArn=lb_arn, Protocol="HTTPS", @@ -611,7 +612,26 @@ def create_gateway( ], ) listener_arn = response["Listeners"][0]["ListenerArn"] - logger.debug("Created ALB Listener for gateway %s", configuration.instance_name) + logger.debug("Created HTTPS ALB listener for gateway %s", configuration.instance_name) + + logger.debug("Creating HTTP ALB listener for gateway %s...", configuration.instance_name) + response = elb_client.create_listener( + LoadBalancerArn=lb_arn, + Protocol="HTTP", + Port=80, + DefaultActions=[ + { + "Type": "redirect", + "RedirectConfig": { + "Protocol": "HTTPS", + "Port": "443", + "StatusCode": "HTTP_301", + }, + } + ], + ) + http_listener_arn = response["Listeners"][0]["ListenerArn"] + logger.debug("Created HTTP ALB listener for gateway %s", configuration.instance_name) ip_address = _get_instance_ip(instance, configuration.public_ip) return GatewayProvisioningData( @@ -623,6 +643,7 @@ def create_gateway( lb_arn=lb_arn, tg_arn=tg_arn, listener_arn=listener_arn, + http_listener_arn=http_listener_arn, ).json(), ) @@ -659,6 +680,8 @@ def terminate_gateway( elb_client = self.session.client("elbv2", region_name=configuration.region) logger.debug("Deleting ALB resources for gateway %s...", configuration.instance_name) + if backend_data_parsed.http_listener_arn is not None: + elb_client.delete_listener(ListenerArn=backend_data_parsed.http_listener_arn) elb_client.delete_listener(ListenerArn=backend_data_parsed.listener_arn) elb_client.delete_target_group(TargetGroupArn=backend_data_parsed.tg_arn) elb_client.delete_load_balancer(LoadBalancerArn=backend_data_parsed.lb_arn)