+1 (726) 207-9872

Serverless Framework Beyond AWS: Azure Functions, Google Cloud, and What Multi-Cloud Serverless Really Costs

Almost every Serverless Framework tutorial you will read — including most of the ones on this site — assumes AWS. That is honest: AWS Lambda is where the overwhelming majority of Serverless Framework deployments live. But we get the same question on roughly a third of our discovery calls: "We are on Azure / Google Cloud / we want to avoid lock-in. Can we still use the Serverless Framework?"

The answer in 2026 is nuanced, and the nuance matters more than the YAML. This post lays out what actually works today on each provider, where the Framework's non-AWS support genuinely stands after V4, and how to structure a codebase so a multi-cloud requirement does not quietly cost you a year of engineering.

The honest state of non-AWS support

Serverless Framework V4 was rebuilt around AWS. The new resolver, the built-in esbuild bundling, serverless dev, and the V4 licensing model all target AWS deployments. Support for other providers was never removed from the ecosystem, but it lives in community and provider-maintained plugins that were written against the V3 plugin surface, and their maintenance pace varies a lot.

Practically, that gives you three tiers:

ProviderHow it works todayRealistic verdict
AWS LambdaFirst-class, native in V4Use it without reservation
Azure Functionsserverless-azure-functions plugin (V3-era lineage)Works for straightforward HTTP/timer functions; expect to pin versions and test the plugin before committing
Google Cloudserverless-google-cloudfunctions plugin; Google now pushes Cloud Run functionsUsable, but Google's own tooling has moved ahead of the plugin
Cloudflare WorkersNo meaningful Serverless Framework pathUse Wrangler; do not fight this

Two conclusions follow, and we tell clients both:

  1. If you are AWS-only, use the Serverless Framework and stop thinking about this.
  2. If you are genuinely multi-cloud, do not pick a deployment tool as your abstraction layer. Choose the native tool per provider and put the abstraction in your code, not in your deploy config. We explain why below.

Running the Serverless Framework on Azure Functions

If you have an existing Azure estate and a team that already knows the Framework's workflow, the Azure plugin is a reasonable choice for HTTP APIs and scheduled jobs.

npm install --save-dev serverless-azure-functions
# serverless.yml
service: orders-api

provider:
  name: azure
  region: West Europe
  runtime: nodejs20
  subscriptionId: ${env:AZURE_SUBSCRIPTION_ID}
  stage: ${opt:stage, 'dev'}

plugins:
  - serverless-azure-functions

functions:
  createOrder:
    handler: src/handlers/orders.create
    events:
      - http: true
        methods:
          - POST
        authLevel: function
  nightlyReconcile:
    handler: src/handlers/jobs.reconcile
    events:
      - timer:
          schedule: '0 0 3 * * *'

Things that surprise AWS-shaped teams on the first deploy:

  • Auth is per-function, not per-API. authLevel: anonymous | function | admin replaces the API Gateway authorizer model. For real APIs put Azure API Management or Entra ID in front; the function key is not an authentication strategy.
  • Cron has six fields, not five. Azure timer triggers include seconds. 0 0 3 * * * is 3am daily, not every minute of hour 3.
  • The plan type is the performance decision. Consumption plan means cold starts; Premium plan buys pre-warmed instances; Flex Consumption sits between them. This is the Azure equivalent of the Provisioned Concurrency conversation, and it should be made deliberately, not by accepting the default.
  • Service principal auth. CI needs AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET and AZURE_SUBSCRIPTION_ID. Prefer OIDC federated credentials over a long-lived secret wherever your CI supports it — the same reasoning as our GitHub Actions and OIDC post.

Before you commit a roadmap to this path, spend one day deploying a throwaway service with the plugin on your target runtime version. That day tells you more about maintenance risk than any blog post, including this one.

Google Cloud: functions, and the Cloud Run drift

The serverless-google-cloudfunctions plugin deploys via Deployment Manager and expects a credentials key pointing at a service-account key file:

service: events-api

provider:
  name: google
  runtime: nodejs20
  project: ${env:GCP_PROJECT}
  region: europe-west1
  credentials: ~/.gcloud/keyfile.json

plugins:
  - serverless-google-cloudfunctions

functions:
  ingest:
    handler: ingest
    events:
      - http: path
  onUpload:
    handler: onUpload
    events:
      - event:
          eventType: google.storage.object.finalize
          resource: projects/${env:GCP_PROJECT}/buckets/uploads

The friction is directional: Google has folded Cloud Functions into Cloud Run functions, and the features they ship first — concurrency per instance, GPU attachment, minimum instances, direct VPC egress — land in gcloud and Terraform long before any Framework plugin. Two caveats worth flagging on day one: downloadable service-account keys are a credential you now have to rotate and protect (workload identity federation is the better path), and handler signatures differ from Lambda's (event, context) — you get Express-style (req, res) for HTTP and CloudEvents for triggers.

If Google Cloud is your primary platform, our recommendation is blunt: use gcloud or Terraform. You will spend less time debugging a translation layer than you save by reusing familiar YAML.

The abstraction that actually survives: hexagonal handlers

Here is the part that matters more than any of the YAML above. Teams that succeed at multi-cloud serverless do not share a deployment tool across clouds. They share business logic and keep a thin, disposable adapter per platform.

src/
  core/                 # zero cloud imports, pure TypeScript
    createOrder.ts
    ports.ts            # interfaces: OrderRepository, EventPublisher, Clock
  adapters/
    aws/
      handler.ts        # APIGatewayProxyEventV2 -> core
      dynamoOrderRepo.ts
      eventBridgePublisher.ts
    azure/
      handler.ts        # HttpRequest -> core
      cosmosOrderRepo.ts
      serviceBusPublisher.ts
  infra/
    aws/serverless.yml
    azure/serverless.yml

The core function knows nothing about any cloud:

// src/core/createOrder.ts
export interface OrderRepository { save(o: Order): Promise<void>; }
export interface EventPublisher { publish(name: string, detail: unknown): Promise<void>; }

export async function createOrder(
  input: CreateOrderInput,
  deps: { repo: OrderRepository; events: EventPublisher; now: () => Date },
): Promise<Order> {
  const order = { ...input, id: crypto.randomUUID(), createdAt: deps.now().toISOString() };
  await deps.repo.save(order);
  await deps.events.publish('OrderCreated', { id: order.id });
  return order;
}

The AWS adapter is fifteen lines:

// src/adapters/aws/handler.ts
import type { APIGatewayProxyHandlerV2 } from 'aws-lambda';
import { createOrder } from '../../core/createOrder';
import { dynamoOrderRepo } from './dynamoOrderRepo';
import { eventBridgePublisher } from './eventBridgePublisher';

export const handler: APIGatewayProxyHandlerV2 = async (event) => {
  const input = JSON.parse(event.body ?? '{}');
  const order = await createOrder(input, {
    repo: dynamoOrderRepo,
    events: eventBridgePublisher,
    now: () => new Date(),
  });
  return { statusCode: 201, body: JSON.stringify(order) };
};

The Azure adapter is a different fifteen lines against @azure/functions. The value is in the ratio: thousands of lines of portable core, a few dozen lines of per-cloud glue, and unit tests that run in milliseconds with in-memory fakes for OrderRepository and EventPublisher — no LocalStack, no emulator, no cloud account.

This is also the only approach that makes a future migration cheap. The teams that get trapped are not the ones that used Lambda-specific APIs; they are the ones that wrote business rules inside handler functions and queries inline next to HTTP parsing.

Choosing, in four questions

  1. Is one cloud already chosen for you by contract, credits, or your data gravity? Then build natively there and use the best-supported tool for that cloud. "Lock-in avoidance" that nobody will ever exercise is a tax with no payoff.
  2. Is multi-cloud a real requirement, or a slide? Real requirements have a named customer or a named regulator behind them. If you cannot name one, treat portability as a code-structure concern only.
  3. Do you need portability of the runtime, or of the data? Moving functions is a week. Moving a 4 TB DynamoDB table or a Cosmos DB graph is a quarter. Optimise the hard one.
  4. Who maintains the deployment path in 18 months? A community plugin with a thin maintainer bus factor is a risk you are choosing to own. Sometimes that is fine. It should never be accidental.

Rules of thumb we apply on engagements

  • AWS-primary: Serverless Framework V4, no hesitation.
  • Azure-primary: Azure Functions Core Tools, Bicep or Terraform — unless the team's Framework fluency is the deciding factor, in which case the plugin is defensible for straightforward workloads, with version pinning and a tested upgrade path.
  • Google-primary: gcloud and Terraform against Cloud Run functions.
  • Cloudflare: Wrangler.
  • Truly multi-cloud: native tooling per cloud, one shared core package, adapters at the edges, and one CI pipeline that builds the core once and deploys it twice.

The Serverless Framework's real value was never that it hid the cloud. It is that it makes the AWS event-to-function mapping declarative and reviewable. Use it where that value is highest, and put your portability where portability actually lives — in your own code.


Need a second opinion before you commit? SleekDeploy's architects run multi-cloud serverless assessments: we review your existing estate, model the real cost of portability, and hand back a written recommendation with a migration path. Get in touch or read more about our Serverless Architecture Design practice.