ML6 • Blog

How to migrate a Lovable app to cloud architecture

Written by Bert Christiaens | Apr 28, 2026 7:00:00 AM

Executive Summary

Lovable enables rapid prototyping by abstracting backend complexity through managed services like Supabase and built-in AI gateways. However, this convenience introduces architectural constraints when applications need to scale, integrate, or meet enterprise security requirements.

This article outlines a principled migration path from a Lovable app to a cloud-native architecture. The focus is not on tooling, but on structure: introducing a service layer, mapping managed primitives to cloud equivalents, and shifting from a 2-tier to a 3-tier architecture with an explicit backend API.

Using the Plant Pal example, we show how to incrementally migrate without rewriting the application by isolating change to a single service boundary. The key idea is simple: when designed correctly, migration becomes a controlled architectural transition rather than a disruptive rebuild.

Migrating a Lovable App to the Cloud

And how to set yourself up for migration from Day 1

Lovable lets you go from idea to working application in minutes. But as applications grow, so do concerns about maintainability, ownership, and security. At some point, what was perfect for prototyping starts holding you back. And when that moment arrives, chances are it’s already too late — and the “migration” turns into a rewrite.

This blog post is Part 2 of a two-part technical series. Part 1 dissected the anatomy of a Lovable app (and where that setup starts to clash with enterprise constraints). This post focuses on how to migrate any Lovable application to the cloud. Along the way, I’ll share a few tips you can adopt from Day 1 to reduce your migration pains later.

❌ This is not a step-by-step Terraform tutorial or a guide to setting up VPCs, load balancers, and production-grade CI/CD.

✅ Instead, the focus is on the architectural decisions and mental model behind a clean migration path. The accompanying repository contains the full implementation details for those who want to go deeper.

You can find the Plant Pal codebase here, containing both the original Lovable app and the migrated Azure version.

1. A Short Recap

In Part 1, we introduced Plant Pal: a small app where you upload a plant photo, get an AI-powered health check, and view your analysis history.

Under the hood, Plant Pal is built on four core backend primitives:

  • Storage: Manages plant images (Supabase).
  • Database: Stores analysis history (Supabase).
  • Edge Functions: Handles server-side logic, AI orchestration, and secrets (Supabase).
  • AI Gateway: Performs the actual analysis via Lovable’s managed gateway.

The first three are Supabase-managed; the AI Gateway is Lovable-managed.

The architecture is “2-tier-ish”: the browser talks directly to Supabase Storage and Database using a publishable key and Row Level Security (RLS). Edge Functions handle anything requiring server-side secrets.

Part 1 ended by discussing enterprise constraints like network isolation and CI/CD, which make this setup hard to sustain. This post picks up from there.

2. A Lovable Best Practice

In Part 1, Plant Pal looked almost suspiciously “migratable.” All backend calls were isolated in a single service file (plantService.ts), and no React components touched the Supabase backend directly.

GenAI tools like Lovable prioritize speed, often scattering backend calls directly inside UI components (like useEffect hooks) by default. This leads to tight coupling, which creates friction in three key ways:

  • Blast radius: When you migrate, every component becomes a potential point of failure.
  • Discoverability: Finding every scattered backend call to update them is time-consuming.
  • Testability: It becomes significantly harder to mock or swap backend implementations without a clear service boundary.

Key Takeaway: Introduce a service boundary early. Components should call uploadPlantImage() without needing to know how the upload happens. During migration, you can swap the implementation behind that boundary without touching the UI.

To get Lovable to produce this structure consistently, we have added a small set of project rules (via Lovable’s knowledge feature):

Service Layer Rules

  • Organize backend access into domain-specific service files in src/services/<domain>Service.ts.
  • All interactions with Supabase (Storage, Database, Edge Functions...) must go through these service files.
  • React components never call supabase.* directly.
  • All LLM calls go through Edge Functions.

    The result? The entire Supabase surface of Plant Pal lives in one file, services/plantService.ts:

 

Concretely, this means we can change the backend all we want, and once finished, we only need to update one file in the frontend.

3. A Principled Migration

3.1 Map Your Primitives

The first step in migration is to identify your components, then swap each one to it’s cloud equivalent one at a time.

Every cloud has a direct equivalent for every Supabase primitive. That’s not a coincidence — Supabase is built on the same open-source building blocks (PostgreSQL, S3-compatible storage, Deno) that these cloud providers wrap as managed services.

The following table gives a high level overview of the alternatives for each cloud.

3.2 The Architectural Shift

However, the migration is a little more than a provider swap. We move from a 2-tier setup (where the browser talks directly to database and storage) to a 3-tier architecture, where all data access flows through a backend API that we control. That architectural shift is the real migration.

3.2.1 The Backend Expands

Shift: [Browser ➔ Database/Storage] becomes [Browser ➔ API ➔ Database/Storage]

In the Lovable version, the browser communicated directly with Storage and the Database, and we had a single Edge Function for AI orchestration. In the cloud-native version, those direct interactions become explicit API endpoints that we own.

Concretely, the backend grows from one function to three endpoints:How you implement these endpoints depends on your tech stack and scalability needs. Broadly, you have two common options:

Option 1: Serverless Functions (Azure Functions, AWS Lambda, GCP Functions)

Choose this if:

  • You want the lowest migration effort from Supabase Edge Functions.
  • You prefer a pay-per-invocation billing model.
  • You want the cloud provider to handle all scaling and server management.

Option 2: Containers (Azure Container Apps, AWS ECS, GCP Cloud Run)

Choose this if:

  • You need full control over the runtime environment or specific OS dependencies.
  • You have long-running processes or need to avoid “cold starts.”
  • You want easier local parity using Docker for both development and production.

3.2.2 Storage: From Public to Controlled

Shift: [Public Access] ➔ [Private/Authenticated Access]

In Lovable, storage is often “convenience-first”: buckets are public, and the browser fetches files directly via permanent URLs.

In a cloud-native architecture, we flip the default to security-first. Buckets are private and hidden behind the firewall. The browser no longer has unconditional access; instead, it must request short-lived “signed URLs” from your backend API or stream files through a proxy endpoint. This gives you absolute control over who sees what and for how long.

3.2.3 Authorization: The Database Goes Private

Shift: [Row Level Security] ➔ [Application Layer Authorization]

In the original Lovable setup, the database was “on the internet,” protected only by Row Level Security (RLS) policies.

In the migrated architecture, the database is no longer reachable by the client. It lives inside a private network, accessible only by your backend API. Because the API is the sole gatekeeper, authorization shifts from the database layer (RLS) to the application layer. The backend validates the user’s identity once at the entry point, then executes queries on their behalf.

3.3 Road to Production Checklist

Moving to the cloud provides control, but control alone doesn’t equal production-readiness. Use this checklist to bridge the gap from prototype to professional system:

  • Network Isolation: Move your database and storage into a private Virtual Network (VPC/VNet).
  • API Gateway: Centralize rate limiting, IP filtering, and Web Application Firewall (WAF) protection.
  • Identity Management: Replace implicit auth with a dedicated provider (e.g., Azure Entra ID, Auth0, Cognito).
  • Automation (IaC): Turn your infrastructure into code (Terraform) to ensure identical environments.
  • Observability: Implement structured logging, failure alerts, and distributed tracing.

4. Migration to Azure: Step by Step

Let’s now take the above method and use it to migrate Plant Pal to Azure.

4.1 Foundations

The very first step in the Azure migration has nothing to do with Azure.

Before touching infrastructure, we export the Lovable project to GitHub and reshape it into a structure that can support infrastructure, backend, and frontend as separate concerns.

Lovable generates a single-project structure: one src/, one package.json, one runtime. That is perfect for prototyping. But once we introduce Terraform, a custom backend, CI/CD, and cloud resources, that flat structure becomes limiting.

So the first migration step is purely structural: we turn the project into a monorepo with three top-level folders: frontend/, backend/, and terraform/ .

The Lovable-generated files are moved to the frontend/folder and we introduce a new root package.json that delegates commands to the frontend.

After restructuring, the repository looks like this:

 

 

 

Crucially, this change is non-breaking. The app still talks to Supabase while we establish clear boundaries for independent deployment pipelines.

4.2 Infrastructure

With our repository structure ready, we use Terraform to define our Azure resources declaratively.

Step 1: Bootstrap for Safety

Your terraform state should never live on a developer’s laptop. Before deploying the app, we establish a Terraform Remote State. We provision a secure, locked storage account in Azure to hold the “brain” of our infrastructure. This prevents accidental conflicts and creates an audit trail for every change.

Step 2: Deploy Core Infrastructure

Once the foundation is secure, we deploy the actual application stack. For Plant Pal, this includes six key Azure resources:

  1. Azure PostgreSQL — The database
  2. Azure Storage Account — The file storage
  3. Azure Key Vault — Centralized secrets management
  4. Azure OpenAI — The LLM intelligence
  5. Azure Function App — The new backend runtime
  6. Azure Static Web App — The frontend host

Database Schema Migration

Because both platforms use PostgreSQL, we take the original SQL migrations and execute them against Azure. The only real adjustment needed is removing the RLS policies, as our new API now handles authorization.

Secrets & Local Development

Resources are provisioned, and their connection strings are stored in Azure Key Vault. For local development, we inject these via .env files. This replaces Lovable’s implicit secret management with explicit, auditable configuration.

At this stage, the Azure environment is fully provisioned: the infrastructure is live, the database schema runs on Azure, the frontend still works unchanged, and Supabase remains active in parallel.

In other words, we have duplicated the environment — without cutting over yet. By establishing infrastructure first and replacing runtime components only afterwards, we reduce risk and keep the migration controlled and incremental.

4.3 Backend API

With our infrastructure in place, we can now replace the Supabase Edge Function with our own backend.

Each function mirrors the responsibility of its Supabase counterpart — but now runs inside our own cloud environment.

Architecturally, this is the key moment of the migration!

We move from Browser → Database / Storage (public)
to Browser → Backend API → Database / Storage (private)

4.4 Frontend

Because we introduced a service layer in Section 2, only a single file required modification: src/services/plantService.ts. We simply swapped Supabase SDK calls for standard fetch() calls.

From the UI’s perspective, nothing happened! This is the direct payoff of introducing a service boundary early! In practical terms, this reduces risk dramatically. UI regressions are unlikely because the surface area of change is tiny.

5. Was It Worth It?

So .. was it worth it?

Starting with Lovable gives you speed through trust. You describe what you want, iterate quickly on UX, and within hours you have a working application. For design sprints or early product validation, that speed is hard to beat. If you are building an isolated app with limited security constraints, staying on Lovable Cloud is perfectly reasonable. We have done this ourselves for simple workflows where the real value comes from reduced operational overhead, not complex infrastructure.

But most companies do not operate in isolation. They have defined multi-cloud strategies, security policies, and identity standards. In that context, the win is combining both worlds:

  • Iterate rapidly on UX and frontend features using Lovable.
  • Export the code once the product direction stabilizes.
  • Continue development in a controlled environment with full architectural control, CI/CD, and observability.

At ML6, this middle ground has proven to be the most pragmatic approach. We use Lovable as a product accelerator to define the “What,” but we move to a managed cloud environment for the “How” — ensuring long-term maintainability and security.

Key Takeaway: Lovable is a product accelerator, not the final production runtime. If you treat it as the first phase of a structured migration path, you can move fast without ever losing control.