Deploying

Deploy a Lovable app to your own server (Vite or TanStack Start)

How to deploy a Lovable app to a server you control: tell the Vite and TanStack Start stacks apart, sync it to GitHub, build it in Docker, serve it over HTTPS, and keep or move the Supabase backend.

TL;DR

  • Lovable (opens in a new tab) projects created on or after 13 May 2026 are TanStack Start apps (package.json lists @tanstack/react-start) that need a running Node.js (opens in a new tab) server, while older ones are React and Vite apps that build to static files.
  • Sync the code to GitHub (opens in a new tab) from Lovable's Git settings (any plan), then build a Vite app into dist/ served with an index.html fallback, or a TanStack Start app with NITRO_PRESET=node-server and run it as node .output/server/index.mjs.
  • Set VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY before the build, because they are compiled into the JavaScript; a TanStack Start app's server also reads SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY where it runs.
  • The backend can stay on Lovable Cloud; add the new domain to its sign-in redirect URLs before you switch.
On this page

To deploy a Lovable app to your own server, sync it to GitHub, check which of Lovable's two stacks it uses, and build it accordingly: a React and Vite project becomes static files that any web server can serve, and a TanStack Start project becomes a Node.js server that has to keep running. Put either behind a reverse proxy with HTTPS, set the Supabase (opens in a new tab) values before the build, and add the new domain to the sign-in redirect URLs. This article is part of the guide to deploying an AI-built app to your own server.

The Lovable facts here come from Lovable's documentation, checked on 11 November 2026. The Dockerfiles were built and run that day on Node.js 22, against a fresh Vite app from npm create vite@latest (Vite 8, React template) and a fresh TanStack Start app from the TanStack CLI with its Nitro deployment option.

First: which Lovable app do you have?

Lovable changed its default stack in 2026, and the two need different hosting:

  • TanStack Start, the default for new projects since 13 May 2026 (and for new Enterprise projects since 22 June 2026). It renders pages into HTML on the server, and has server functions and API routes, so it needs a host that runs Node.js; a static file host alone will not work.
  • React and Vite, every older project unless you upgraded it. It builds to static files in dist/ and routes in the browser, so any web server can host it, provided it answers unknown paths with index.html.

To check, look for @tanstack/react-start in package.json, in Lovable's Code tab or in your repository. Upgrading an older project is optional: Lovable keeps Vite projects working.

Step 1: Sync the code to GitHub

Git sync works on every Lovable plan with github.com. Only a workspace owner or admin can add the connection:

  1. Open Workspace settings, then Git, then GitHub, and choose Add connection.
  2. Pick the account or organisation, choose which repositories the app may see, and click Install & Authorize.
  3. In the project's Git settings, click Connect. Lovable creates a new private repository and starts syncing.

The sync goes both ways: changes in Lovable are pushed to the repository, and commits pushed to the synced branch (by default main) come back into Lovable. You can keep building in Lovable after you move the hosting. Lovable always creates a new repository; it cannot connect to one that already exists. On a paid plan you can instead download a one-time .zip snapshot.

Neither route includes your database rows or uploaded files; those live in the backend (step 5).

Clone the repository and run the app locally before you write a Dockerfile. Lovable's docs note that their npm (opens in a new tab) examples assume a package-lock.json: if the repository has only bun.lock, either use Bun or run npm install once and commit the package-lock.json it writes. The Dockerfiles below use npm ci, which needs that file.

Step 2: Collect the build-time values

A Lovable app on the built-in backend reads three values from the .env file in the repository:

  • VITE_SUPABASE_URL
  • VITE_SUPABASE_PUBLISHABLE_KEY
  • VITE_SUPABASE_PROJECT_ID

Vite exposes any variable that starts with VITE_ to the browser and replaces it in the code at build time. So these values have to be present when the image is built, or the app ships without them and cannot reach its data, and anything in a VITE_ variable is readable by every visitor, which is fine for the Supabase URL and publishable key (they are meant to be public, and Row Level Security is what protects the data) and never fine for a secret.

The Dockerfiles below take the values as build arguments and keep .env out of the image, so what goes into a build is always explicit. Vite gives variables that are already set in the environment priority over .env files, so the build arguments win either way.

A TanStack Start app needs the same values a second time. Lovable's hosting guide says its server code also reads SUPABASE_URL and SUPABASE_PUBLISHABLE_KEY (and SUPABASE_PROJECT_ID), set to the same values as the browser ones, as environment variables where the server runs. The compose.yaml in step 4 passes them to the container.

Step 3a: The Dockerfile for a Vite Lovable app

This builds the app with Node.js 22, then copies only dist/ into a small Caddy (opens in a new tab) image that serves the files on port 8080:

# Build stage: Node 22 builds the static files into dist/
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# VITE_ variables are compiled into the JavaScript at build time
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_PUBLISHABLE_KEY
ARG VITE_SUPABASE_PROJECT_ID
RUN npm run build

# Run stage: Caddy serves dist/ and sends unknown paths to index.html
FROM caddy:2-alpine
COPY spa.Caddyfile /etc/caddy/Caddyfile
COPY --from=build /app/dist /srv

The spa.Caddyfile next to it makes client-side routes work: without try_files, refreshing /dashboard asks the server for a file called dashboard, which does not exist, and the visitor gets a 404:

:8080 {
	root * /srv
	encode
	try_files {path} /index.html
	file_server
}

Add a .dockerignore so local builds, Git history and your .env stay out of the image:

node_modules
dist
.git
.env
.env.*

In our test, a request for /dashboard/settings returned the app's index.html with status 200, and the Supabase URL passed as a build argument appeared in the built JavaScript while the value in a local .env did not.

Step 3b: The Dockerfile for a TanStack Start Lovable app

A TanStack Start app is built by Nitro, which has a preset per platform. For a container you want the Node.js server preset, and the result is a self-contained server in .output/. First check that package.json lists nitro. If it does not, follow TanStack's hosting guide: install it with npm install nitro and add nitro() from nitro/vite to the plugins in vite.config.ts.

FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
# Browser values are embedded at build time
ARG VITE_SUPABASE_URL
ARG VITE_SUPABASE_PUBLISHABLE_KEY
ARG VITE_SUPABASE_PROJECT_ID
ENV NITRO_PRESET=node-server
RUN npm run build

FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOST=0.0.0.0
COPY --from=build --chown=node:node /app/.output ./.output
USER node
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

The run stage copies only .output/, because Nitro bundles the server and the dependencies it needs into it. The server listens on port 3000, set by PORT and HOST. Do not add a try_files fallback in front of it: the server handles routing itself. Server-only secrets (an API key a server function uses, for example) go into the container's environment at run time, not into build arguments.

Use the same .dockerignore as above, with .output added.

Step 4: Run it behind Caddy on your server

Both stacks sit behind the same reverse proxy, which takes ports 80 and 443 and forwards requests to the app. The Caddyfile:

app.example.com {
	reverse_proxy app:8080
}

Use app:8080 for the Vite image above and app:3000 for TanStack Start. Then a compose.yaml that builds the app with its build arguments and runs Caddy beside it:

services:
  app:
    build:
      context: .
      args:
        VITE_SUPABASE_URL: ${VITE_SUPABASE_URL}
        VITE_SUPABASE_PUBLISHABLE_KEY: ${VITE_SUPABASE_PUBLISHABLE_KEY}
        VITE_SUPABASE_PROJECT_ID: ${VITE_SUPABASE_PROJECT_ID}
    environment:
      # Read at run time by TanStack Start's server code; the Vite image ignores them
      SUPABASE_URL: ${VITE_SUPABASE_URL}
      SUPABASE_PUBLISHABLE_KEY: ${VITE_SUPABASE_PUBLISHABLE_KEY}
      SUPABASE_PROJECT_ID: ${VITE_SUPABASE_PROJECT_ID}
    restart: unless-stopped

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config

volumes:
  caddy_data:
  caddy_config:

Replace app.example.com with your domain. Docker (opens in a new tab) Compose reads a .env file next to compose.yaml for the ${...} substitutions, which fill both the build arguments and the server's run-time variables. Lovable keeps the three values in .env at the project root, so a clone of the repository usually has them already; if yours does not, create the file on the server. Point your domain's A record at the server and start it:

docker compose up -d --build

Caddy gets the certificate as soon as the domain resolves to the server and ports 80 and 443 are open. We ran this stack with localhost as the site address, where Caddy uses its own local certificate authority; HTTP was redirected to HTTPS and both stacks answered through the proxy. A public certificate for a real domain needs a real server, which the test did not use.

No server yet? Deploying a web app to a VPS covers creating one, SSH keys, the firewall and Docker, and Hetzner vs DigitalOcean helps you choose a provider.

Step 5: Keep the backend, or move it

Lovable Cloud, the built-in backend, is built on Supabase: database, sign-in, storage, realtime, edge functions and secrets. You have three options.

Keep Lovable Cloud (the simplest move). Lovable documents hosting the frontend elsewhere while keeping the built-in backend, and you can go on building in Lovable, with its AI features and app connectors still working. You set the new host's environment variables and server secrets, and add the new domain to the sign-in redirect URLs. In Supabase terms, that is the Site URL (the default redirect address that confirmation and password-reset emails use) and the list of allowed Redirect URLs; any Google or GitHub sign-in also needs the new address in that provider's OAuth settings.

Move to your own Supabase project. Lovable's docs give the reference path: create the project, apply the migrations in supabase/migrations/ in order, restore the data export (it includes user accounts and password hashes, so users keep their passwords but must sign in again), reconfigure the sign-in providers and redirect URLs, copy the storage files, set the secrets, deploy the edge functions, recreate scheduled jobs, test on a separate deployment, and only then switch the values in .env and supabase/config.toml. Lovable's full export is a PostgreSQL (opens in a new tab) custom-format archive with zstd compression, restored with pg_restore, and you can request one every 24 hours. Calls to Lovable's AI features and app connectors keep running through Lovable until you replace them with your own provider accounts, so plan that replacement as part of the move.

Self-host Supabase. For data residency or full control, Lovable points to Supabase's own Docker self-hosting guide. A plain PostgreSQL database is not enough: the app needs Supabase's auth, storage, realtime and edge services too.

Move the frontend first. Once the app runs on your server against the backend it already has, moving the backend is a separate project with its own test run.

Step 6: Test, then switch the domain

On a temporary address, run Lovable's own checklist:

  1. Refresh a nested route. A 404 means a missing fallback (Vite) or a missing server (TanStack Start).
  2. Sign in and out. A redirect error means the address is not in the allow list.
  3. Read, write and upload something.
  4. For TanStack Start, load a server-rendered page and call a server function.
  5. Search the code for the old lovable.app address, hardcoded in links, emails or callbacks.

Then point your custom domain at the server. The lovable.app address keeps serving your last Lovable publish until you unpublish it, which gives you a fallback while DNS settles. Exporting code and moving the domain do not cancel your Lovable plan or Cloud usage; change those yourself when you are done.

Two other guides cover similar apps: a Bolt.new app, whose database is Bolt (opens in a new tab) Database or your own Supabase project, and a v0 app, which is always Next.js (opens in a new tab).

Doing this with OpsCrew

OpsCrew recognises a repository exported from Lovable, and its free opscrew scan checks it for leaked keys and exposed Supabase data before it goes live, such as a table without Row Level Security. It then builds the image in GitHub Actions (opens in a new tab), with its own Dockerfile for a Vite app or with yours when the repository has one (such as the TanStack Start Dockerfile above), treats VITE_ values as public build-time values, and deploys to a new server on Hetzner Cloud (opens in a new tab) or DigitalOcean (opens in a new tab) or to one you already have, with HTTPS on your domain. A static Vite app can also go on Cloudflare's hosting with no server. Each change waits for your approval of its plan. Use cases shows a scan of a test app shaped like a Lovable export.

For what is the same across builders, see the guide to deploying an AI-built app to your own server.

Key facts

FactValueSource
TanStack Start became Lovable's default for new projects13 May 2026Upgrade a project to TanStack Start (opens in a new tab)Verified on
Git sync availabilityAll plans (github.com); the .zip download needs a paid planDeployment, hosting, and ownership options with Lovable (opens in a new tab)Verified on
Node.js version Lovable's hosting guide usesNode.js 22Deploying and hosting outside Lovable (opens in a new tab)Verified on
Default port of a TanStack Start server built with Nitro3000Change it with the PORT and HOST variables.Deploying and hosting outside Lovable (opens in a new tab)Verified on

Frequently asked questions

How do I deploy a Lovable app?

Inside Lovable, press Publish. Outside it, Lovable's docs cover Netlify, Vercel (opens in a new tab) and Cloudflare (opens in a new tab) Pages, and containers or your own server with Nitro's node-server preset.

Can I self-host a Lovable app?

Yes. You own the code and can host it anywhere; only the Lovable editor and its AI agent cannot be self-hosted.

Can I deploy a Lovable app to AWS?

Yes. Lovable's docs list AWS (opens in a new tab) Amplify for Vite apps, and S3 with CloudFront through a CI/CD pipeline that uploads dist/. A TanStack Start app needs compute that runs Node.js, such as a container or a virtual machine, built with the node-server preset.

Sources

  1. Deploying and hosting outside Lovable (opens in a new tab), Lovable, accessed on
  2. Deployment, hosting, and ownership options with Lovable (opens in a new tab), Lovable, accessed on
  3. Upgrade a project to TanStack Start (opens in a new tab), Lovable, accessed on
  4. Sync your Lovable project with GitHub (opens in a new tab), Lovable, accessed on
  5. Built-in backend (opens in a new tab), Lovable, accessed on
  6. TanStack Start hosting (opens in a new tab), TanStack, accessed on
  7. Env variables and modes (opens in a new tab), Vite, accessed on
  8. Automatic HTTPS (opens in a new tab), Caddy, accessed on
  9. Redirect URLs (opens in a new tab), Supabase, accessed on
  • Deploying

    How to deploy an app you built with AI to your own server

    From a Lovable, Bolt.new, Replit, v0 or Claude Code project to a live URL on a server you control: code, build, backend, secrets, HTTPS and what breaks on the way.

    10 min read

  • Deploying

    Deploy a Bolt.new app to your own server

    How to deploy a Bolt.new app outside Bolt Cloud: get the code, find out which stack Bolt chose, build it in Docker, serve it over HTTPS, and claim the database into Supabase.

    8 min read

  • Deploying

    Deploy a Replit app to a VPS

    How to move a Replit app to a VPS you control: push it to GitHub, turn the .replit build and run commands into a Dockerfile, re-create the secrets, and move the production database with pg_dump.

    8 min read

Put OpsCrew to work on your app.

Run npx opscrew init in your app's repository. It checks your tools and tokens first, for free.