Ends in
00
days
00
hrs
00
mins
00
secs
ENROLL NOW

🚀 $4.99 Claude Certified Architect Foundations (CCA-F) Practice Exams

How to Deploy a Lovable Site to AWS Lambda Using the AWS Lambda Web Adapter

Home » AWS » How to Deploy a Lovable Site to AWS Lambda Using the AWS Lambda Web Adapter

How to Deploy a Lovable Site to AWS Lambda Using the AWS Lambda Web Adapter

There is a real satisfaction in taking something you built yourself and putting it live on infrastructure you own and control. This guide shows you how to deploy a Lovable site to AWS Lambda using the AWS Lambda Web Adapter, so a page you designed in an afternoon can run on your own AWS account instead of staying inside someone else’s platform. Tools like Lovable make the front end easy, and the AWS Lambda Web Adapter is what lets that app run on serverless hosting that costs almost nothing until someone visits. The full path, from idea to a live link, is more within reach than it looks, and you will walk through every step here, including the snags worth avoiding.

I am a Business Development Manager at Tutorials Dojo, and I handle a fair amount of the tech side of my work. For our TD for Business rollout, I built a simple link page in Lovable, a Linktree-style page that introduces the program, links to our products, and lists my contact details and an affiliate code.

What follows is the exact process I used to move that page from Lovable onto AWS Lambda with the Lambda Web Adapter, in the order I did it.

It was my first time with this particular setup, so I ran into a few snags along the way. I have noted them where they came up, so you can skip the time I lost on them.

Why this setup makes sense for a business

A quick note before the steps, since the choice of Lambda over EC2 is deliberate.

A traditional server (an EC2 instance) runs around the clock and bills you for every hour, even when no one visits. A simple link page has light, uneven traffic, so most of that would be paid idle time. Running it on AWS Lambda instead means you pay only when someone actually loads the page, which for a page like this falls inside the free tier and effectively costs nothing.

A few other practical benefits: Lambda scales up on its own if a post or campaign drives a traffic spike, the page lives on our own AWS account rather than a third-party platform, and the same approach can be reused for any future landing page at the same near-zero cost. None of this requires keeping a server running.

What the Lambda Web Adapter does

One technical note so the rest makes sense. Lambda normally expects your code in a special “handler” format, not as a normal web server, which is why people often reach for EC2 to run a regular web app. The AWS Lambda Web Adapter removes that limitation. It is a small tool that sits in front of your app inside Lambda and translates between Lambda and ordinary web requests, so a normal web app runs on Lambda without being rewritten. That is what lets us get serverless pricing while keeping a standard app.

How the pieces fit together

Architecture diagram showing how a visitor reaches the site: the browser connects through CloudFront over HTTPS to a Lambda Function URL, which invokes an AWS Lambda container where the AWS Lambda Web Adapter passes the request to the site's web server, which returns the pages.

AWS Lambda Web Adapter architecture: a Lovable site running on AWS Lambda

Optionally, Amazon CloudFront goes in front for a cleaner link, caching, and a custom domain.

Tools you need first

You need four free tools plus an AWS account. On a Mac, you install each by downloading it and double-clicking, the same as any other app. On Windows it is the same idea: download the installer and run it. 

Set aside about an hour for the first run, and much less once you have done it once. Creating an AWS account is free but asks for a card to verify you. Everything in this guide stays inside the free tier for a small site, so you should not expect a real bill.

  1. Node.js (the LTS version) from nodejs.org. This builds the site. On Mac it is a .pkg, on Windows a .msi. After installing, type node -v in a terminal; if it prints a version number like v20.x.x, it worked.
  2. Docker Desktop from docker.com. This packages the site into a container. On Mac, download the build that matches your chip (how to check that is just below). On Windows, download Docker Desktop for Windows; it uses WSL 2 underneath, and the installer sets that up for you, so just accept the prompts and restart if it asks. Either way, launch it and leave it running while you work. Confirm with docker --version.
  3. AWS CLI v2. On Mac the installer is at https://awscli.amazonaws.com/AWSCLIV2.pkg. On Windows it is https://awscli.amazonaws.com/AWSCLIV2.msi. Run it and click through the steps, then open a new terminal and type aws --version to confirm.
  4. VS Code from code.visualstudio.com. This is the editor we work in, and it has a built-in terminal, so you never have to leave it.

Picking the right Docker download. On a Mac, click the Apple logo at the top-left of the screen, choose About This Mac, and read the chip line: if it says Apple M1, M2, M3, or M4 you have an Apple Silicon Mac, and if it says Intel you have an Intel Mac. Pick the matching download. On Windows, almost every machine is 64-bit Intel or AMD, so the standard Docker Desktop for Windows download is the right one unless you know you have an ARM device.

Working in VS Code: the basics

A few things you will do over and over. None of them require coding knowledge.

  • Open your project: File menu, then Open Folder, then pick your project folder. The files show up in the left sidebar.
  • Open the terminal: Terminal menu, then New Terminal. A panel opens at the bottom. It is just a box where you type a command and press Return. Opened this way, it is already pointed at your project folder, which is what every command below assumes. If a command ever asks for your Mac password, the typing stays invisible on purpose.
  • Create a file: in the left sidebar, click the New File icon (a small page with a plus) at the top, or right-click an empty spot and choose New File. Type the file name, press Return, then paste the contents and save with Cmd+S.
  • Edit a file: click it in the sidebar to open it, make the change, and save with Cmd+S. A filled dot on the file’s tab means there are unsaved changes; Cmd+S saves and the dot turns back into an X.
  • Find and replace text inside a file: open the file, press Cmd+Option+F, type the text to find in the top box and the replacement in the bottom box, then click Replace All.
  • Tutorials dojo strip
  • Search across the whole project: press Cmd+Shift+F and type a word to find which file something lives in. Useful when you are not sure where a bit of code is.

On Windows, the shortcuts use the Ctrl key instead of Cmd. So Cmd+S becomes Ctrl+S (save), Cmd+A becomes Ctrl+A (select all), and Cmd+Shift+F stays Ctrl+Shift+F (search the project). The one that is named differently is find and replace inside a file: on Windows press Ctrl+H instead of Cmd+Option+F. The terminal that opens on Windows is PowerShell, which is the version of the commands I point out later where the two systems differ.

Step 1: Get the code out of Lovable

Lovable connects to GitHub, which is the cleanest way to get a copy.

  1. In Lovable, click the GitHub button and connect your account.
  2. Choose Create Repository. Lovable pushes the whole project to a new GitHub repo.
  3. On github.com, open that repo, click the green Code button, and choose Download ZIP.
  4. Unzip it and open the folder in VS Code (File, then Open Folder).

That gives you the project on your computer without learning Git commands.

Step 2: Know what kind of app Lovable gave you

This matters. Our TD for Business site, like most current Lovable projects, is built on TanStack Start, a full framework that renders pages on a server using a build tool called Nitro. You can confirm yours by opening package.json and looking for @tanstack/react-start and nitro in the dependencies.

When this kind of app builds, it produces a dist/client folder and a dist/server folder. There is no plain index.html to serve on its own, because the app runs its own web server. That is precisely the case the Lambda Web Adapter is built for: we run the app’s server on Lambda.

(If your Lovable app is an older static type with no server framework, the steps are simpler and I note the difference later.)

Step 3: Build it once locally to confirm

In the VS Code terminal, inside the project folder:

npm install
npm run build

If that finishes without red errors, you are good. These commands print a lot of text and can take a minute or two, which is normal. Fix any build errors here, because they will fail in AWS too.

Step 4: Make the build produce a Node server (the key gotcha)

This is the part that cost me the most time, so pay attention here.

Lovable’s TanStack Start config defaults to building the server for Cloudflare. That is fine inside Lovable, but it is wrong for AWS Lambda: a Cloudflare-targeted build does not start a normal web server, so the Lambda Web Adapter has nothing to talk to. The container starts but nothing listens, and the page never loads.

The fix is to tell the build to produce a standard Node server. In the VS Code sidebar, click vite.config.ts to open it, select everything with Cmd+A, delete it, and paste in this (then save with Cmd+S):

import { defineConfig } from "@lovable.dev/vite-tanstack-config";

export default defineConfig({
  tanstackStart: {
    server: { entry: "server" },
  },
  nitro: {
    preset: "node-server",
    output: {
      dir: "dist",
      serverDir: "dist/server",
      publicDir: "dist/client",
    },
  },
});

The crucial line is preset: "node-server". After rebuilding, you get a runnable server at dist/server/index.mjs that listens on a port, exactly what the adapter needs. This change only affects builds you run yourself, so it is safe and does not touch anything in Lovable.

Step 5: Add the Dockerfile with the Lambda Web Adapter

A container is a sealed box holding the app and everything it needs, so it runs the same on your laptop and on AWS. The Dockerfile is the recipe. Using the New File steps from earlier, create a file named Dockerfile (capital D, no extension) in the project root, and paste in this:

# Stage 1: build the site
FROM public.ecr.aws/docker/library/node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: the runtime box Lambda will run
FROM public.ecr.aws/docker/library/node:20-slim

# This one line adds the Lambda Web Adapter as a Lambda extension
COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter

WORKDIR /app
ENV PORT=8080
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server/index.mjs"]

The single line that turns Lambda into a web host is this:

COPY --from=public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 /lambda-adapter /opt/extensions/lambda-adapter

It copies the Lambda Web Adapter into the container. When AWS runs the box, the adapter starts automatically alongside the site, which listens on port 8080, and the adapter passes visitor requests to it.

In the same way, create another file named .dockerignore (note the leading dot) and paste in this:

node_modules
dist
.git

(If your app is the static type, you instead add a tiny static web server and point the last line at it. The Lambda Web Adapter line stays the same. The principle is identical: run a web server on a port, let the adapter front it.)

Step 6: Fix the images (Lovable’s hidden asset trap)

This one caught me out. Images you upload in Lovable are often not saved into the project. The code points to a link on Lovable’s servers, which works in Lovable’s preview but breaks everywhere else. After deploying, our logo and my photo showed up blank.

In the page code you will see imports like:

import tdLogo from "@/assets/td-logo.jpeg.asset.json";

That .asset.json is just a Lovable URL. The fix is to use real files in the project:

  1. Put the actual image files into the public folder. The easiest way: open your public folder in Finder and the project’s public folder in the VS Code sidebar side by side, then drag the images in. Give them simple names like td-logo.jpeg and profile.jpeg.
  2. Find where each image is used. Press Cmd+Shift+F and search for a word like logo to jump to the file that references it.
  3. In that file, swap the Lovable reference for a plain path. Use Find and Replace (Cmd+Option+F): for example find src={tdLogo.url} and replace with src="https://td-mainsite-cdn.tutorialsdojo.com/td-logo.jpeg", then save with Cmd+S.

Anything in public is served from the root, so /td-logo.jpeg works locally and on AWS. Repeat for every Lovable-linked image.

Step 7: Test the container on your computer first

Always confirm the box works locally before AWS. With Docker Desktop running:

docker build --platform linux/amd64 -t td-business .
docker run -p 8080:8080 td-business

The --platform linux/amd64 part matters on an Apple Silicon Mac, because Lambda runs this on an x86_64 chip, so you build to match. This build is only to test locally; you will build once more in Step 9, with one extra flag, before pushing to AWS.

Open http://localhost:8080 in your browser. If the full site loads with images, the box is correct. If something looks stale or an old version shows, do a hard refresh (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows) or open the page in a private or Incognito window, since browsers hold on to old versions aggressively. When you are done looking, click into the terminal and press Ctrl+C to stop the container. (If you later get a “port already allocated” error, an old container is still running; stop it with docker stop $(docker ps -q) and try again.)

Step 8: Connect your computer to AWS

In the AWS Console, you find any service by typing its name into the search bar at the very top of the page. So to open IAM, type “IAM” up there and click it. IAM is where you create the credentials that let your computer talk to AWS.

  1. Open IAM, go to Users, and create a user (for example td-deploy-user).
  2. Attach these policies for the first deploy: AmazonEC2ContainerRegistryFullAccess, AWSLambda_FullAccess, IAMFullAccess. (These are broad to keep the first run simple. You can tighten or delete this user once the site is live.)
  3. Open the user, go to the Security credentials tab, and create an access key. Choose the Command Line Interface option. You will see an Access Key ID and a Secret Access Key. Copy both now, the secret is shown only once.

Then in the terminal:

aws configure

Enter the key ID, the secret, your region (I used ap-southeast-1, Singapore, closest to the Philippines), and json. Confirm with:

aws sts get-caller-identity

If it prints your account number, you are connected. Treat those keys like a password, do not share them, and you can delete the key once everything is live.

Step 9: Push the image to Amazon ECR (and the manifest gotcha)

ECR is where AWS stores your container image. Set two values first. Your account number is the 12-digit number that aws sts get-caller-identity printed in the last step (it is also at the top-right of the AWS Console when you click your account name). Replace YOUR_ACCOUNT_NUMBER with it.

On Mac (Terminal):

export AWS_ACCOUNT_ID=YOUR_ACCOUNT_NUMBER
export AWS_REGION=ap-southeast-1

Then create the repository, log Docker in, build, tag, and push:

aws ecr create-repository --repository-name td-business --region $AWS_REGION

aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com

docker build --platform linux/amd64 --provenance=false -t td-business .

docker tag td-business:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/td-business:latest

docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/td-business:latest

On Windows (PowerShell), the commands are the same except for how you set and read the two values:

$env:AWS_ACCOUNT_ID="YOUR_ACCOUNT_NUMBER"
$env:AWS_REGION="ap-southeast-1"

aws ecr create-repository --repository-name td-business --region $env:AWS_REGION

aws ecr get-login-password --region $env:AWS_REGION | docker login --username AWS --password-stdin "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com"

docker build --platform linux/amd64 --provenance=false -t td-business .

docker tag td-business:latest "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com/td-business:latest"

docker push "$($env:AWS_ACCOUNT_ID).dkr.ecr.$($env:AWS_REGION).amazonaws.com/td-business:latest"

The --provenance=false flag is important. Newer Docker builds produce an image in a multi-format style that Lambda will not accept, and you get an error about the manifest media type not being supported. This flag produces the simpler format Lambda wants.

Step 10: Create the Lambda function and get the public link

In the Console, make sure the region selector matches where you pushed the image.

  1. Open Lambda, Create function, choose Container image.
  2. Name it td-business, Browse images, pick the repository and the latest tag.
  3. Architecture x86_64, then Create function.
  4. Under Configuration, General configuration, set Memory to 512 MB and Timeout to 30 seconds.

Then create the link:

  1. Configuration, Function URL, Create function URL.
  2. Auth type NONE (publicly viewable).
  3. TD for Business
  4. Save and copy the URL.

Open it. The site is live on AWS Lambda, served through the Lambda Web Adapter. The first visit after a quiet period may take a second to wake up, which is normal for Lambda and fine for a launch page.

Step 11 (optional): A cleaner link and a custom domain

The Function URL works but is long. Amazon CloudFront in front gives a tidier HTTPS link, caching, and the option of a custom domain.

When creating the distribution, set the origin to your Function URL hostname (the part after https://, without the trailing slash). Treat it as a custom origin you type in, not one of the AWS services in the dropdown, since a Function URL is just an HTTPS endpoint. Two settings matter for a Lambda Function URL, and CloudFront often pre-selects them:

  • Cache policy: CachingDisabled (keeps the page fresh; enable caching later for speed).
  • Origin request policy: AllViewerExceptHostHeader. This is essential, or CloudFront forwards the visitor’s Host header to the Function URL, which rejects it and the page fails.

Set origin protocol to HTTPS only and viewer protocol policy to Redirect HTTP to HTTPS. After it deploys you get a clean cloudfront.net address.

Here is the page from this guide, live behind CloudFront: https://d2zuvc0lcd70i.cloudfront.net/

A true custom domain (like business.yourdomain.com) is possible but needs a domain you actually control, a free certificate from AWS Certificate Manager requested in the us-east-1 region, and DNS records added wherever the domain is managed. If you do not control that DNS, the person who does has to add the records, and a subdomain on a domain you do not own is not possible. The cloudfront.net link is a fine place to launch from in the meantime.

What it costs

For a launch site, this is effectively free or a few cents a month. Lambda has a large free tier (about a million requests and 400,000 GB-seconds of compute monthly), the Function URL adds nothing, ECR storage is cents for a small image, and CloudFront includes a free transfer tier. There is no EC2 instance, so nothing is billed when no one is visiting. Compared with even the smallest always-on EC2 server, which costs a few dollars a month just to exist, this is far cheaper for anything not under constant heavy load.

Troubleshooting: the real problems I hit

What you see Why it happens How to fix it
The page never loads and nothing listens on a port. The build is still targeting Cloudflare. Set preset: "node-server" in vite.config.ts and rebuild (Step 4).
A build error about a stray file used as the server entry. You added your own server.js at the project root. Delete it. A TanStack Start app already has its own server, and the extra file breaks the build.
The page cannot find index.html. The app is server-rendered, not static. Run its own server (dist/server/index.mjs); do not try to serve static files.
Images are broken after deployment but fine in Lovable. They were linked from Lovable’s servers. Put the real files in public and reference them with plain paths (Step 6).
Lambda rejects the image (manifest or media type not supported). The build produced a multi-format image. Rebuild with --provenance=false, then re-tag and re-push (Step 9).
Docker cannot connect to the daemon. Docker Desktop is not running. Open it, wait for the whale icon to go steady, then retry.
Port already allocated. An old container is still running. Stop it with docker stop $(docker ps -q) and run again.
You changed something and rebuilt, but nothing updated. Docker reused a cached layer, or the browser cached the old page. Rebuild with docker build --no-cache ..., then hard refresh (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows) or open a private window.
It works on the Function URL but not through CloudFront. CloudFront is forwarding the visitor’s Host header. Add the AllViewerExceptHostHeader origin request policy.
A platform warning (amd64 versus arm64) on an Apple Silicon Mac. You are building for a different chip than your Mac uses. Harmless. You build amd64 on purpose to match Lambda’s x86_64.

Wrapping up

That is the whole path: pull the code out of Lovable, switch the build to a Node server, wrap it in a container with the Lambda Web Adapter, push it to AWS, and put a Lambda function and Function URL in front. What you get is a real page on your own AWS account, on hosting that stays quiet and costs almost nothing until someone visits, and the same steps carry straight over to the next page you want to move.

None of this turned out to be magic. It was a set of specific steps and a few known snags, and the snags only cost time the first time through. That is the part worth keeping: once you can take something you built and put it live yourself, you stop waiting on anyone else to ship it for you. Read the errors, try the next step, and trust that the second run is always faster than the first.

Resources

Reference reading

AWS documentation

Tools to install

 

🚀 Get 10% OFF ALL PlayCloud Plans using TD-PLAYCLOUD-06022026

Tutorials Dojo portal

Turn Your Team Into Cloud-Ready Professionals Today

Tutorials Dojo for Business

Learn AWS with our PlayCloud Hands-On Labs

$2.99 AWS and Azure Exam Study Guide eBooks

tutorials dojo study guide eBook

New AWS Generative AI Developer Professional Course AIP-C01

AIP-C01 Exam Guide AIP-C01 examtopics AWS Certified Generative AI Developer Professional Exam Domains AIP-C01

Learn GCP By Doing! Try Our GCP PlayCloud

Learn Azure with our Azure PlayCloud

FREE AI and AWS Digital Courses

FREE AWS, Azure, GCP Practice Test Samplers

SAA-C03 Exam Guide SAA-C03 examtopics AWS Certified Solutions Architect Associate

Subscribe to our YouTube Channel

Tutorials Dojo YouTube Channel

Follow Us On Linkedin

Written by: Franchesca Asignacion

Franchesca Asignacion is a Cloud Business Development Manager at Tutorials Dojo. In addition to her business role, she works on the technical side of cloud and AI technologies, particularly in DevSecMLOps. She also produces educational video courses and writes technical articles focused on cloud computing and artificial intelligence, helping learners understand modern technologies and their practical applications.

AWS, Azure, and GCP Certifications are consistently among the top-paying IT certifications in the world, considering that most companies have now shifted to the cloud. Earn over $150,000 per year with an AWS, Azure, or GCP certification!

Follow us on LinkedIn, YouTube, Facebook, or join our Slack study group. More importantly, answer as many practice exams as you can to help increase your chances of passing your certification exams on your first try!

View Our AWS, Azure, and GCP Exam Reviewers Check out our FREE courses

Our Community

~98%
passing rate
Around 95-98% of our students pass the AWS Certification exams after training with our courses.
200k+
students
Over 200k enrollees choose Tutorials Dojo in preparing for their AWS Certification exams.
~4.8
ratings
Our courses are highly rated by our enrollees from all over the world.

What our students say about us?