anomalyco/sst · error · VisibleError

You must provide the ports to expose via "loadBalancer.rules

Error message

You must provide the ports to expose via "loadBalancer.rules".

What it means

When a Service uses a managed load balancer, SST needs at least one routing rule to know which container ports to expose. `normalizeLoadBalancer` reads `loadBalancer.rules ?? loadBalancer.ports`; if both are missing or empty it throws this `VisibleError`.

Source

Thrown at platform/src/components/aws/service.ts:1954

        return v;
      });
    }

    function normalizeLoadBalancer() {
      const loadBalancer = args.loadBalancer ?? args.public;
      if (!loadBalancer) return;
      // ALB attachment case is handled by detectAlbAttachment() before this is called
      const inlineLoadBalancer = output(loadBalancer).apply(
        (lb) => lb as Exclude<typeof lb, { instance: Alb }>,
      );

      // normalize rules
      const rules = all([inlineLoadBalancer, containers]).apply(
        ([lb, containers]) => {
          // validate rules
          const lbRules = lb.rules ?? lb.ports;
          if (!lbRules || lbRules.length === 0)
            throw new VisibleError(
              `You must provide the ports to expose via "loadBalancer.rules".`,
            );

          // validate container defined when multiple containers exists
          if (containers.length > 1) {
            lbRules.forEach((v) => {
              if (!v.container)
                throw new VisibleError(
                  `You must provide a container name in "loadBalancer.rules" when there is more than one container.`,
                );
            });
          }

          // parse protocols and ports
          const rules = lbRules.map((v) => {
            const listenParts = v.listen.split("/");
            const listenPort = parseInt(listenParts[0]);
            const listenProtocol = listenParts[1];

View on GitHub (pinned to a0bd20f762)

Solutions

  1. Add `loadBalancer: { ports: [{ http: 80, https: 443 }] }` (or `rules: [...]`) to the Service.
  2. Alternatively define public ports inline on the container (`containers[].public.ports`) so routing is derived.
  3. If no load balancing is wanted, remove the `loadBalancer` prop entirely instead of passing an empty object.

Example fix

// before
const svc = new sst.aws.Service("Api", {
  cluster,
  loadBalancer: {}
});

// after
const svc = new sst.aws.Service("Api", {
  cluster,
  loadBalancer: { ports: [{ http: 80 }] }
});
Defensive patterns

Strategy: validation

Validate before calling

// validate before constructing the Service
const lb = args.loadBalancer;
const hasRules = lb && ((lb.rules?.length ?? 0) > 0 || (lb.ports?.length ?? 0) > 0);
if (lb && !hasRules) throw new Error("Provide loadBalancer.rules or loadBalancer.ports");

Type guard

function hasLbRules(lb?: { rules?: unknown[]; ports?: unknown[] }): boolean {
  return !!lb && ((lb.rules?.length ?? 0) > 0 || (lb.ports?.length ?? 0) > 0);
}

Try / catch

try {
  const svc = new sst.aws.Service("Api", args);
} catch (e) {
  if (e instanceof VisibleError && e.message.includes("loadBalancer.rules")) {
    args.loadBalancer = { ports: [{ http: 80 }] }; // retry with defaults
  } else throw e;
}

Prevention

When it happens

Trigger: Passing `loadBalancer: {}` (or an object with empty `rules`/`ports` arrays) to `new sst.aws.Service(...)` while not using inline container `public` ports.

Common situations: Migrating from an older config where ports were set on containers; refactoring `ports` into `rules` and leaving neither; template code where the ports block was deleted.

Understand the failure class

Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.

Related errors


AI-assisted analysis of anomalyco/sst@a0bd20f762 (2026-08-30). Data as JSON: /api/errors/23da610b29efb45c. Report an issue: GitHub.