Skip to content

Let a Function URL point at a Lambda alias - #7035

Open
Hjorthdx wants to merge 1 commit into
anomalyco:devfrom
Hjorthdx:function-url-alias
Open

Hjorthdx wants to merge 1 commit into
anomalyco:devfrom
Hjorthdx:function-url-alias

Conversation

@Hjorthdx

@Hjorthdx Hjorthdx commented Oct 20, 2026 •

Copy link
Copy Markdown

A Function with versioning: { alias: "live" } creates a Lambda alias on the version each deploy publishes, and creates its function URL on that alias. Router routes and SsrSite servers use that URL, so they serve whichever version the alias points to, and a rollback is just moving the alias.

new sst.aws.Function("MyFunction", {
  handler: "src/lambda.handler",
  url: true,
  versioning: {
    alias: "live",
  },
});

Problem

Lambda aliases are the standard way to roll back without a deploy: publish a version on every deploy, send traffic to an alias, and point the alias at an earlier version when something breaks (aws lambda update-alias --name live --function-version 41). It takes effect at once, and nothing gets rebuilt.

SST always creates function URLs on $LATEST, so the alias never gets traffic. For a site behind a Router it's worse: SsrSite builds the Router metadata from the server's URL,

servers: servers.map((s) => [new URL(s.url).host, s.region.lat, s.region.lng]),

and there's no option or transform that changes which URL that is. transform.server can turn on versioning, but it can't move the URL, and the KvKeys resource that writes the metadata isn't exposed.

SST already does this for durable functions, whose URL is created on an alias in createUrl. It's just not available for anything else.

Use case

We run several Next.js sites with sst.aws.Nextjs behind one Router, and want to roll back a bad deploy from a console in seconds, without running sst deploy on an old commit while the site is broken. To get an alias into the traffic path today we:

  • create the alias, a second function URL on it and the CloudFront permissions for it ourselves, next to SST's;
  • rewrite the servers host in SST's KvKeys inputs with a Pulumi stack transformation, matched by SST's internal resource name;
  • re-pin the metadata to the alias on every deploy, since SST writes its unqualified URL back whenever its own inputs change;
  • put a site's metadata back on $LATEST before its alias URL is deleted when it leaves the rollout. Without that, CloudFront signs requests for a URL that no longer exists and the site answers 403 until SST happens to rewrite the metadata;
  • run our own warmer, since warm invokes $LATEST.

All of it depends on SST internals: the KvKeys resource name, the shape of the metadata, and there being exactly one servers row. With this PR, all of it goes away. For Nextjs it's a transform.server:

new sst.aws.Nextjs("Web", {
  router: { instance: router },
  transform: {
    server: (args) => {
      args.versioning = { alias: "live" };
      args.transform = {
        ...args.transform,
        alias: (_alias, opts) => {
          opts.ignoreChanges = ["functionVersion"];
        },
      };
    },
  },
});

Design

Function

  • versioning also takes { alias }. It creates a lambda.Alias with that name, pointing at the published version, and turns on publish.
  • If url is enabled, the function URL is created on the alias (qualifier). Router routes and SsrSite metadata use that URL, so they follow it with no other changes.
  • The URL permissions, public and CloudFront OAC for Router routes, are granted on the alias, since Lambda resource policies are per qualifier.
  • transform.alias changes the alias resource. A tool that moves the alias outside of SST can stop deploys from moving it back with ignoreChanges: ["functionVersion"]. Without that, a deploy moves the alias to the version it published. A deploy with no code changes publishes no new version, so it leaves a rolled-back alias alone.
  • nodes.alias exposes the alias, and links include its ARN so linked resources can invoke it.
  • Together with durable it throws a VisibleError, since durable functions already put their URL on an alias of their own.

SsrSite

  • The server and image optimizer permissions are granted on the function's alias, if it has one.
  • The warm cron invokes <name>:<alias>, since that's what the URL serves.

component.ts

  • aws:lambda/alias:Alias is added to the resources whose physical names aren't prefixed. Alias names are scoped to their function, like FunctionUrl and Permission, which are already on that list.

Unchanged without an alias. Every resource gets the same inputs as before. qualifier resolves to undefined and isn't sent.

Adding or removing the alias replaces the function URL, so its hostname changes. This is documented on the option. The Router metadata is updated to the new URL in the same deploy, and Pulumi deletes replaced resources at the end of a deploy, so the old URL should outlive the switch.

Open questions

  • The name. versioning.alias keeps it next to publish. url.alias would say more clearly what changes, but an alias is useful without a URL too.
  • Other triggers. I've kept the alias to the function URL. With versioning: true, targetArn (API Gateway routes, subscribers, …) is the published version. Should those use the alias when there is one, so a rollback covers every trigger? That's a bigger change, so I left it out.
  • Provisioned concurrency is still configured on the published version. On the alias would probably be more useful when there is one.
  • SsrSite option. server.versioning on SsrSite would be nicer than transform.server. Happy to add it.

Relation to #7030 and #7034

Independent of both. #7030 minifies the Router's viewer-request function, and #7034 lets an injection pick the server host for one request. This changes which host a site's metadata names for everyone.

Testing

  • New test/components/function-alias.test.ts, 8 tests. It builds real Functions and a Router under Pulumi mocks, with only the Runtime.Build RPC, the bootstrap bucket lookup and the dynamic KV providers stubbed. It checks that:
    • without an alias, the URL and permissions are unchanged;
    • the alias points at the published version;
    • the URL is created on the alias, and url returns the alias URL;
    • public and CloudFront permissions are granted on the alias;
    • the Router route metadata names the alias URL;
    • transform.alias can change the alias and set ignoreChanges;
    • links can invoke the alias.
  • tsc --noEmit passes, and all 108 platform tests pass.

Run with vitest run --pool=forks: on Node 22 the default threads pool reports ERR_TRACE_EVENTS_UNAVAILABLE from Pulumi's closure serialization, the same as router-waf.test.ts on dev. bucket, alb and service-alb fail to load in my install on dev as well (@types/node resolve), so they're unrelated.

A Function with versioning: { alias: "live" } creates a Lambda alias on the
version it publishes, and creates its function URL on that alias. Router
routes, and SsrSite servers through transform.server, then serve whatever
version the alias points to, so a rollback is an alias move.

- transform.alias, e.g. to ignore functionVersion when a tool outside SST
  moves the alias
- URL permissions, including CloudFront OAC grants in Function and SsrSite,
  are granted on the alias
- the SsrSite warmer warms the alias, and links can invoke it
- not supported together with durable, which has an alias of its own

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant