Series: Vibe Coding on Your Own Terms — Post 1 of 2
Post 2 preview: "From Localhost to Live: Publishing Your Vibe-Coded App to a Cloud Web Server"
Alternative headline options:
- Build Your Own Lovable: A Free, Local Vibe Coding Setup for the Mac
- Vibe Coding Without the Subscription: VSCode + Cline + Ollama + Supabase
What is vibe coding, anyway?
Vibe coding is a way of building software where you describe what you want in plain language and an AI writes the code — while you steer, review, and react to what appears on screen. You stay in the driver's seat as the product person: "add a dark mode toggle", "the delete button should ask for confirmation", "make the list sort by date". The AI handles the syntax, the file structure, the boilerplate. You handle the vibe: what the app should do and how it should feel.
The term took off in early 2025 (coined by AI researcher Andrej Karpathy), and it resonated because it describes something real: for a huge class of apps — internal tools, prototypes, side projects, small products — you no longer need to write most of the code yourself. You need to be able to direct code being written, and to recognize when something's off.
What is Lovable?
Lovable is one of the most popular vibe coding platforms. You type what you want into a chat box, and it builds a working web app in front of you — user interface, database, login, file uploads, the lot — with a live preview updating as you talk. Under the hood, Lovable generates a fairly standard modern web stack: React with TypeScript, styled with Tailwind CSS and shadcn/ui components, backed by Supabase (a Postgres database with authentication and file storage built in).
It's a great product. It's also a subscription with usage-based credits, your code lives in their cloud, and the AI calls go to commercial models. Which raises an obvious question for tinkerers:
Can you assemble the same experience yourself — free, local, and private?
Yes. That's what this post is about.
The goal of this post
By the end, you'll have a complete vibe coding environment running on your Mac:
| Lovable gives you | Your free local equivalent |
|---|---|
| Chat box that builds the app | Cline (an AI coding agent inside VSCode) |
| The AI brain | Ollama running an open-source model locally |
| Live preview | Vite dev server in your browser, hot-reloading on every change |
| Hidden code | VSCode — same code, except you can see and touch it |
| Database, auth, storage | Supabase running locally in containers — the exact same technology Lovable uses in the cloud |
| One-click publish | Coming in Post 2 of this series |
Total cost: €0. Your code never leaves your machine. Your prompts never leave your machine. And because it's the same stack Lovable uses, everything you build has a clean path to real cloud hosting later.
One honest expectation-setter before we start: a local open-source model on consumer hardware is not as capable as the frontier models Lovable rents. Your local agent will occasionally do something dumb, and this post shows you the guardrails that keep that manageable (they're half the value of the article — I hit every pitfall below for real during setup).
What you need before starting
- A Mac (Apple Silicon recommended; 16 GB RAM is comfortable, 8 GB works with the tricks in the RAM section below)
- VSCode installed, with the Cline extension
- Ollama installed, with a coding model pulled —
qwen2.5-coder(7b or 14b) is currently one of the strongest options that fits on consumer hardware
Setting up VSCode + Cline + Ollama is well covered elsewhere; this post starts where most guides stop: the local test server and database — the part that turns "an AI that edits files" into "a Lovable-like experience with a real running app."
Phase 1: One-time machine setup (~20 minutes)
Four command-line tools, installed once, used by every future project. Open the Terminal (in VSCode: Terminal → New Terminal).
1. Homebrew — the Mac's package manager
Check if you have it:
brew --versionIf that prints a version, move on. If not:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"On Apple Silicon, run the two extra commands the installer prints at the end (they add brew to your PATH), then open a new terminal.
2. OrbStack — the container runtime
Your local database will run in containers. The usual tool is Docker Desktop, but OrbStack is a drop-in replacement that uses noticeably less RAM and battery — and RAM matters, because Ollama is already eating a lot of it.
brew install orbstack
open -a OrbStackAccept the prompts; it then lives quietly in your menu bar. Verify:
docker --version3. Node.js — runs the dev server
brew install node
node -vPitfall #1 — scary-looking install messages. Homebrew may print "Caveats" like "Single Executable Application is disabled" or "Temporal support is disabled." These look like errors; they're harmless notices about niche Node features nothing in this stack uses. If you see the 🍺 beer glass, the install succeeded.
4. Supabase CLI — your entire backend in one tool
brew install supabase
supabase --versionThis is the piece most guides miss. The Supabase CLI runs a complete local backend — Postgres database, authentication, file storage, and a visual admin UI — with one command. It's the same open-source software behind Lovable's cloud backend. You don't install or configure Postgres yourself, ever.
Phase 2: Creating a project (~20 minutes, once per project)
5. Scaffold the app
Pick one folder where all your projects will live, and stick to it religiously:
mkdir -p ~/Projects && cd ~/Projects
npm create vite@latest my-first-app -- --template react-ts
cd my-first-app
npm installThe scaffolder asks "Which linter to use?" — pick ESLint. It's the long-established standard, and (a theme you'll see throughout) local AI models have seen vastly more ESLint projects in training than newer alternatives, so they make fewer mistakes with it.
Open the project in VSCode with code . — and if the terminal says command not found: code, that's a one-time VSCode setting: press Cmd+Shift+P, type "shell command", select "Shell Command: Install 'code' command in PATH", then open a new terminal.
Pitfall #2 — the wrong-folder trap. This cost me more time than anything else in the whole setup. If you open VSCode while your terminal is sitting in some other directory, your edits go into the wrong project — and later commands mysteriously fail because the files they expect were never changed. Two habits prevent it entirely: before any command, glance at the terminal prompt or run pwd — it must end in your project name; and in VSCode use File → Open Folder to open exactly the project folder, nothing above it.Now verify the heart of the setup — your local test server:
npm run devOpen http://localhost:5173 — the starter page loads. This Vite server hot-reloads the browser within a second of any file change, which is what creates the Lovable "watch it build live" feeling. Leave it running in its own terminal tab; open new tabs (the + icon) for other commands.
Pitfall #3 — port 5174? If the URL ever shows 5174 instead of 5173, you have a second dev server running in a forgotten terminal tab. Not harmful, but confusing. Close the duplicates; run exactly one.
6. Tailwind CSS + shadcn/ui — Lovable's visual language
npm install tailwindcss @tailwindcss/vite(If npm warns about fsevents and "install scripts not yet covered by allowScripts" — that's a newer npm security feature. fsevents is a trusted macOS file-watching helper; approve it with npm approve-scripts fsevents or just carry on, everything works either way.)
Replace the entire content of src/index.css with one line:
@import "tailwindcss";Pitfall #4 — paste replaces, not appends. When a guide says "replace the file content", select everything (Cmd+A), delete, then paste. I managed to paste a newvite.config.tsbelow the old one, producing a file with duplicate imports and two default exports — instant breakage. If something errors right after an edit, check you don't have the file's content twice.
Now, before running shadcn's installer, do the setup it silently requires. This is the step where my setup first properly failed:
Pitfall #5 — shadcn needs an "import alias" that Vite doesn't ship. shadcn/ui writes components that import from@/components/...— the@being shorthand for yoursrcfolder. The Vite template doesn't define that shorthand, sonpx shadcn initfails its preflight checks with "Could not find valid path aliases." The fix is three small edits, done once per project:
Install the type helper:
npm install -D @types/nodeReplace vite.config.ts with:
import path from "path"
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: { "@": path.resolve(__dirname, "./src") },
},
})Replace tsconfig.json with:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] }
}
}And in tsconfig.app.json, add these two lines at the top of the existing "compilerOptions" block:
"baseUrl": ".",
"paths": { "@/*": ["./src/*"] },Now shadcn will pass its checks:
npx shadcn@latest init
npx shadcn@latest add button card inputThe init asks two questions current guides rarely mention. "Select a component library?" — pick Radix UI, even though the tool recommends Base UI. Radix is what shadcn was originally built on and what Lovable uses, which again means it's what your local models know best. "Which preset?" — Nova (Lucide icons, the classic shadcn look). Defaults for everything else.
7. Start your local backend + database
supabase init
supabase startThe first supabase start downloads about a dozen container images — give it a few minutes.
Pitfall #6 — a download randomly fails. One of my thirteen images (storage-api) failed to pull with a cryptic registry error while the other twelve succeeded. Nothing was wrong with my setup — container registries occasionally hiccup. The fix is anticlimactic: runsupabase startagain. It resumes and fetches only what's missing.
When it finishes, run supabase status to see what you now have:
| Service | Address |
|---|---|
| Your app (test server) | http://localhost:5173 |
| Studio — visual database admin | http://127.0.0.1:54323 |
| API (REST/Auth/Storage) | http://127.0.0.1:54321 |
| Postgres, direct connection | port 54322 |
Open Studio in your browser and look around: table editor, SQL console, auth users, storage buckets. This is your window into the database for the entire vibe coding workflow — when the AI claims it saved something, Studio is where you check it actually did.
(You may also see "Stopped services: imgproxy, pooler" — those are optional components; that's normal.)
8. Wire the app to the database
npm install @supabase/supabase-jsPitfall #7 — the keys don't look like the tutorials say. Most guides tell you to copy an "anon key" that looks likeeyJhbGci.... Newer Supabase versions replaced that format:supabase statusnow prints a Publishable key (sb_publishable_...) and a Secret key (sb_secret_...). The Publishable key is your app's key. The Secret key is the admin key — it never goes anywhere near frontend code.
Create a file called .env.local in the project root:
VITE_SUPABASE_URL=http://127.0.0.1:54321
VITE_SUPABASE_ANON_KEY=sb_publishable_...your-key-here...And the client at src/lib/supabase.ts:
import { createClient } from '@supabase/supabase-js'
export const supabase = createClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY
)9. The .clinerules file — the most underrated step
Create a file called .clinerules in the project root. Cline reads it automatically before every task, and it's the difference between an agent that follows your architecture and one that improvises:
# Project rules
## Stack
- React + TypeScript + Vite
- Tailwind CSS v4 + shadcn/ui (components live in src/components/ui)
- Supabase for database, auth, and storage (client in src/lib/supabase.ts)
## Rules
- Use existing shadcn/ui components before writing custom ones.
- Never hand-write authentication logic; always use supabase.auth methods.
- All database schema changes go into SQL migration files via
`supabase migration new <name>` — never apply schema changes directly.
- After schema changes, apply with `supabase db reset`.
- Read environment variables only via import.meta.env.VITE_*.
- Keep components small; one component per file.
- Do not add new dependencies without asking first.Frontier models often infer these conventions; small local models need them written down. You'll see exactly why in a moment.
10. Version control — your undo button for AI mistakes
git init
git add -A
git status # check the list: .env.local must NOT appear (your keys stay out of git)
git commit -m "Project scaffold: Vite + React + Tailwind + shadcn + Supabase"When an AI edits your code, git is your safety net: commit after every working feature, and any AI-induced mess is one git checkout -- . away from undone.
Pitfall #8 — Supabase temp files bloating your commits. After your first feature commit you may notice thousands of inserted lines fromsupabase/.temp/...— internal scratch files that don't belong in version control. Exclude them once:echo "supabase/.temp/" >> .gitignore, thengit rm -r --cached supabase/.tempand commit.
Phase 3: The first feature — and what AI agents get wrong about databases
Setup done. Time to vibe. With supabase start and npm run dev both running, open the Cline panel and prompt:
Create a todos feature: a migration for a `todos` table (id uuid pk default gen_random_uuid(), title text not null, done boolean default false, created_at timestamptz default now()), then a page using shadcn/ui components that lists, adds, and toggles todos via the supabase client.
Cline will propose file changes, you approve them, and the browser hot-reloads into a todos UI. Magical — until you try to add a todo. Here's the honest part most tutorials skip. My first feature failed twice, in two instructive ways:
Pitfall #9 — "Could not find the table 'public.todos'". The UI exists but the database table doesn't. Databases change through migration files — small SQL scripts insupabase/migrations/that are applied withsupabase db reset. My local model built the UI but skipped the migration (yes, despite the rules file — small models sometimes do). The habit that catches this every time: after any feature that touches data, check that a new.sqlfile appeared insupabase/migrations/. If not, tell Cline: "Put the schema changes in a migration file viasupabase migration new." Then apply withsupabase db reset.
Pitfall #10 — "permission denied for table todos". Progress — the table exists now — but Postgres refuses your app access to it. Supabase apps talk to the database as a restricted "anon" role, and new tables need explicit access rights plus a Row Level Security policy. The robust fix is a migration that handles creation and permissions together. Create one with supabase migration new create_todos and give it this shape:create table if not exists public.todos (
id uuid primary key default gen_random_uuid(),
title text not null,
done boolean not null default false,
created_at timestamptz not null default now()
);
grant usage on schema public to anon, authenticated;
grant select, insert, update, delete on table public.todos to anon, authenticated;
alter table public.todos enable row level security;
drop policy if exists "dev allow all" on public.todos;
create policy "dev allow all" on public.todos
for all using (true) with check (true);Then supabase db reset, refresh the browser — and it works. Add a todo, reload the page, it persists. Open Studio and there's your row in Postgres. That's the full chain, verified: your prompt → local AI → code + migration → real database → live UI.
(That "allow all" policy is deliberately wide open — correct for local development where the only user is you, and exactly what gets replaced with real security rules before anything goes live. That's a Post 2 topic.)
Pitfall #11 — duplicate migrations. After the fix, my agent belatedly created its own create-table migration, which would have crashed the nextsupabase db reset(creating a table that already exists is an error). If two migrations create the same table, delete the redundant one and re-runsupabase db resetto prove the set is healthy.
Commit the milestone: git add -A && git commit -m "Add todos feature".
What daily vibe coding actually looks like
One source of confusion worth clearing up: VSCode ships its own AI chat panel, and Cline adds a second one. They're unrelated. Use only the Cline panel — it's the one connected to Ollama and the one that can edit files and run commands. Hide the built-in chat and never think about it again.
The Lovable mapping, one screen, two halves: VSCode with the Cline panel on the left, browser with your app on the right. Lovable's chat = Cline. Lovable's preview = localhost:5173. Lovable's hidden code = visible in your editor, which is a feature, not a bug.
The daily rhythm:
# session start
supabase start
npm run devThen loop: describe one feature to Cline → review the diffs → approve → watch the browser hot-reload → if data was involved, glance at supabase/migrations for a new file → test it → git add -A && git commit -m "feature". When something goes sideways, say so to Cline or roll back with git. At session end, supabase stop — which brings us to hardware.
A note on RAM (MacBook Air owners, this is for you). Ollama with a 7b model wants 5–8 GB; the local Supabase stack takes 1.5–2.5 GB; Vite and your browser another 1–2 GB. On 16 GB everything coexists happily. On 8 GB, use the toggle trick: supabase stop while the model is doing heavy generation, supabase start when you want to test. And three habits that specifically help small local models: keep tasks small (one feature per Cline conversation, then start a fresh task — long chats degrade small models), use Cline's Plan mode before Act mode for anything non-trivial, and keep your .clinerules current as the project grows.
What you have now — and what's next
For the price of an afternoon and zero euros per month, you now run the same stack Lovable sells: an AI agent building a React + Tailwind + shadcn app against a real Postgres database, live preview included, entirely on your own machine. Plus three things Lovable doesn't give you: total privacy, visible code you actually own and understand, and guardrails (git, migrations, rules file) that professional developers use.
Everything so far lives on localhost — visible only to you. Post 2 of this series covers the missing chapter: publishing. How to take a vibe-coded app live on a real cloud web server — GitHub for the code, Vercel for the frontend, Supabase Cloud for the database — where the entire local-to-live transition comes down to changing two environment variables and running two commands. And, critically, how to replace that wide-open development security policy with real Row Level Security rules before your app meets the public internet.
Until then: supabase start, npm run dev, and go build something.
This is Post 1 of the "Vibe Coding on Your Own Terms" series. Post 2: "From Localhost to Live: Publishing Your Vibe-Coded App to a Cloud Web Server."