<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Vlad (Volodymyr Shiyan)</title>
    <description>The latest articles on DEV Community by Vlad (Volodymyr Shiyan) (@vladshiyan).</description>
    <link>https://dev.to/vladshiyan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4092274%2F67de7e05-639c-49a6-af42-fa87e959c81d.png</url>
      <title>DEV Community: Vlad (Volodymyr Shiyan)</title>
      <link>https://dev.to/vladshiyan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vladshiyan"/>
    <language>en</language>
    <item>
      <title>The Honest Ceiling of Offline License Verification</title>
      <dc:creator>Vlad (Volodymyr Shiyan)</dc:creator>
      <pubDate>Mon, 07 Sep 2026 12:30:00 +0000</pubDate>
      <link>https://dev.to/vladshiyan/the-honest-ceiling-of-offline-license-verification-5a1</link>
      <guid>https://dev.to/vladshiyan/the-honest-ceiling-of-offline-license-verification-5a1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published on &lt;a href="https://lockmargin.com/blog/the-honest-ceiling.html" rel="noopener noreferrer"&gt;lockmargin.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Last weekend I asked myself an uncomfortable question: could someone post my app and a valid license key on a torrent, and anyone use it for free? The answer is yes. That question sat with me all weekend.&lt;br&gt;
I couldn't build anything else until I mapped it, so this is the map.&lt;/p&gt;

&lt;p&gt;Context: I'm building LockMargin, a local-first invoicing app for&lt;br&gt;
freelancers. $49 once. No account. No telemetry. No phone-home of any kind. The license is a signed token the app verifies locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  The threat model
&lt;/h2&gt;

&lt;p&gt;First, I narrowed the problem. I'm not trying to stop professional cracking crews or commercial resellers. I'm trying to survive the casual kind: a key shared in a chat, an installer plus key on a torrent, "send me your key" between friends.&lt;/p&gt;

&lt;p&gt;Then I drew a hard line. If a mechanism breaks on a new laptop, a&lt;br&gt;
wiped disk, a VM - it's out. If it needs a server - out. If it needs an account - out. The app has to keep working even if my company disappears. Every option on my list had to pass that test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest ceiling
&lt;/h2&gt;

&lt;p&gt;Here is the fact most DRM marketing hides: if an application can&lt;br&gt;
locally answer "this token is valid," then anyone holding the same token gets the same answer. Copying the authorization is always possible when verification is local.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is not an implementation flaw. It is the architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two different things get conflated here. Authenticity: did I publish this binary? Code signing answers that. Authorization: does this person own a license? A signed local token answers that. Neither prevents a buyer from handing a valid token to somebody else.&lt;/p&gt;

&lt;p&gt;And two different threats. Sharing is when a buyer gives their key to another person - the contractual and social layers help with that.&lt;br&gt;
Cloning is when the installer and a valid key travel together. That is where the hard limit appears: if a real installer plus a real key spreads, an offline application cannot tell the new user from the owner. I say that plainly, because pretending otherwise is how DRM theater starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four layers of friction
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cryptographic.&lt;/strong&gt; Each purchase gets its own signed key. There is no master key. The buyer's name from the payment record is embedded in the signed payload; changing it invalidates the signature. The email stays optional metadata, because the app never requires it. The key is scoped to the major version: a v1 key opens v1.x forever and simply doesn't grant v2. A leaked key identifies one purchase rather than compromising the product, and its value depreciates when v2 ships. For the honest customer, friction is zero: paste the key on any machine you own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Contractual.&lt;/strong&gt; The license says one person, the machines they own and control. A desktop and a laptop are fine. Passing the key around is not. A contract, not a checkpoint. The application doesn't need to phone home to enforce it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Economic.&lt;/strong&gt; The free tier removes the biggest reason people&lt;br&gt;
pirate small software: "I just want to try it." Start is free to try - up to 5 clients, 5 projects a month, unlimited invoices. No&lt;br&gt;
account, no card, same encryption, same offline database. And the&lt;br&gt;
paid version is $49 once. For a freelancer billing $50 an hour,&lt;br&gt;
fifteen minutes hunting for a crack already costs more than the&lt;br&gt;
software. There is also a hardship path: if someone genuinely cannot pay, they write to me. That's a personal decision, not a policy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Social.&lt;/strong&gt; The app shows "Licensed to: your name" in Help.&lt;br&gt;
Passing an abstract key is easy; passing a key with your own name&lt;br&gt;
signed into it is a different social operation. And transparency&lt;br&gt;
works as soft enforcement: the people who care about the no-DRM&lt;br&gt;
promise are probably not the people I'm trying to stop in the first place.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The snake oil I refused
&lt;/h2&gt;

&lt;p&gt;Hardware binding was on my first shortlist. Then I pictured the&lt;br&gt;
support email: "I wiped my disk and now your app thinks I'm a&lt;br&gt;
pirate." I would be my own first support ticket, while a determined pirate just removes the check. I killed it that afternoon.&lt;/p&gt;

&lt;p&gt;An offline lease - "the key remains valid for 90 days offline" -&lt;br&gt;
would effectively turn my perpetual license into a subscription, and it still clones with the disk.&lt;/p&gt;

&lt;p&gt;An activation counter without a server is a local variable. Whoever clones the state clones the count. Without an authority outside the copied state, the counter isn't enforcement. It's theater.&lt;/p&gt;

&lt;p&gt;Obfuscation and anti-tamper raise the cost of cracking for hours and add antivirus false positives and VM problems for years. For a solo developer, the support bill exceeds the benefit.&lt;/p&gt;

&lt;p&gt;Every one of these buys a little piracy resistance and charges it to the honest user. Some of them are reasonable choices for other&lt;br&gt;
products. For my constraints, all four failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took from the products I studied
&lt;/h2&gt;

&lt;p&gt;Sublime Text gave me the closest precedent: a per-user license&lt;br&gt;
entered offline, no connectivity prerequisite, an honor system that has survived for years. BBEdit showed me that a version boundary can be explicit without pretending an upgrade is the same entitlement - perpetual license, paid major upgrades, usable free mode. Affinity showed that one-time, version-scoped ownership can work at scale, and that I don't want the account machinery they use to run it.&lt;br&gt;
Plasticity is my opposite example: once you introduce seat&lt;br&gt;
management, you're back to managing activation state - the thing&lt;br&gt;
ownership promises to abolish.&lt;/p&gt;

&lt;p&gt;None of them satisfies all five of my constraints. That's fine. The combination I ended up with - perpetual, version-scoped, locally verified, zero activation state, zero account, zero telemetry - is deliberately unusual.&lt;/p&gt;

&lt;h2&gt;
  
  
  The calculation
&lt;/h2&gt;

&lt;p&gt;I kept coming back to one conclusion: if I make piracy harder by&lt;br&gt;
adding friction, my honest customers are the ones who pay for it.&lt;br&gt;
Piracy exists; I'm not pretending otherwise. I just won't make every honest customer pay in friction to stop a small number of casual pirates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack I shipped
&lt;/h2&gt;

&lt;p&gt;Unique per-order signed tokens with the buyer's name and the major version in the payload, verified locally. A license that reads like a contract. The free Start tier as the legal alternative to a leaked key. "Licensed to: your name" in Help. No activation server, no revocation server, no telemetry.&lt;/p&gt;

&lt;p&gt;Five layers - and after that weekend, no sixth. I'll admit I kept&lt;br&gt;
looking for one more technical trick, some way to make a copied key less useful without a server. I spent more time on that search than I probably should have. There wasn't one.&lt;/p&gt;

&lt;p&gt;And one principle above the stack: a valid license should never&lt;br&gt;
depend on the continued existence of the company that issued it.&lt;br&gt;
Servers vanish, founders vanish; the copy you bought keeps working. That's not a feature - that's the product.&lt;/p&gt;




&lt;p&gt;If you ship offline software, where do you draw the line between&lt;br&gt;
protection and punishment - and what did I miss? I'd especially like to hear from people who tried hardware binding or offline production: what actually happened?&lt;/p&gt;

</description>
      <category>security</category>
      <category>drm</category>
      <category>privacy</category>
      <category>licensing</category>
    </item>
    <item>
      <title>Why I Chose SQLite Over PostgreSQL</title>
      <dc:creator>Vlad (Volodymyr Shiyan)</dc:creator>
      <pubDate>Mon, 24 Aug 2026 12:47:16 +0000</pubDate>
      <link>https://dev.to/vladshiyan/why-i-chose-sqlite-over-postgresql-26d1</link>
      <guid>https://dev.to/vladshiyan/why-i-chose-sqlite-over-postgresql-26d1</guid>
      <description>&lt;p&gt;Why I Chose SQLite Over PostgreSQL&lt;/p&gt;

&lt;p&gt;I chose SQLite over PostgreSQL because LockMargin is a desktop app that runs on the user's machine — not on a server. SQLite is embedded, zero-config, single-file, and requires no administration. PostgreSQL is a server database. For a freelancer who just wants to send an invoice, asking them to install PostgreSQL isn't a product. It's a sysadmin job.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Decision Point
&lt;/h2&gt;

&lt;p&gt;December 2025. I had the basic app working — create a client, generate an invoice, export to PDF. But I needed a database. Two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;SQLite&lt;/strong&gt; — embedded, single-file, zero-config&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL&lt;/strong&gt; — server-based, multi-user, enterprise-grade&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I'd spent 20 years building systems with PostgreSQL. I knew it. I loved it. But I chose SQLite. Here's why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conversation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; I was leaning toward PostgreSQL. It's what I know. It's what I've used for factories, retail chains, service companies. It handles concurrency, transactions, complex queries. Why would I downgrade to SQLite?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; Because you're not building a factory system anymore. You're building a desktop app for one person. PostgreSQL is a server. Where's the server going to run?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; On the user's machine?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; Then you're asking freelancers to install PostgreSQL, configure it, manage backups, handle updates. That's not a product. That's a sysadmin job. I wouldn't install it myself, and I'm a backend engineer. Imagine an accountant from Zhytomyr who last saw a command line in 2009.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; Fair point. But SQLite — isn't it just for prototypes?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; No. SQLite is in every phone, every browser, every operating system. It's the most deployed database in the world. Apple uses it. Google uses it. Every Android phone has dozens of SQLite databases. It's not a toy. It's a tool. I lost three days in 2019 trying to set up PostgreSQL replication for a startup that shut down a month later. SQLite would have saved those days.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; What about concurrency? Multiple users?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; Your app has one user. The freelancer. They're not sharing the database with 50 colleagues. SQLite handles single-user workloads perfectly. For multi-user, you'd need a server — which brings us back to PostgreSQL. But you don't want a server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; Right. Offline-first. No cloud. No server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; Exactly. SQLite is the database for offline-first apps. I'd say — for people who are tired of Docker Compose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Technical Comparison
&lt;/h2&gt;

&lt;p&gt;Let's get specific. Here's how SQLite and PostgreSQL compare for LockMargin's use case:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;SQLite&lt;/th&gt;
&lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Installation&lt;/td&gt;
&lt;td&gt;Zero (embedded)&lt;/td&gt;
&lt;td&gt;Required (server)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Complex (pg_hba.conf, postgresql.conf)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Administration&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;DBA needed for production&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Single file (.db)&lt;/td&gt;
&lt;td&gt;Multiple files, tablespaces&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup&lt;/td&gt;
&lt;td&gt;Copy the file&lt;/td&gt;
&lt;td&gt;pg_dump, WAL archiving&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Concurrency&lt;/td&gt;
&lt;td&gt;Single-writer, multi-reader&lt;/td&gt;
&lt;td&gt;Multi-writer, MVCC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scalability&lt;/td&gt;
&lt;td&gt;Limited to one machine&lt;/td&gt;
&lt;td&gt;Unlimited (clustering, replication)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;$0 (self-hosted) or $5-50/month (managed)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance (single-user)&lt;/td&gt;
&lt;td&gt;Faster (no network)&lt;/td&gt;
&lt;td&gt;Slower (network overhead)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Performance (multi-user)&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline support&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;td&gt;Requires server connection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data portability&lt;/td&gt;
&lt;td&gt;Copy the file&lt;/td&gt;
&lt;td&gt;Export/import required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For LockMargin, SQLite wins on every dimension that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benchmarks
&lt;/h2&gt;

&lt;p&gt;I ran some tests. Here are the results for typical invoicing workloads:&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hardware:&lt;/strong&gt; M2 MacBook Pro, 16GB RAM&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database:&lt;/strong&gt; 1,000 invoices, 500 clients, 200 recurring invoices&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations:&lt;/strong&gt; SELECT, INSERT, UPDATE, complex JOINs&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Results
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;SQLite&lt;/th&gt;
&lt;th&gt;PostgreSQL&lt;/th&gt;
&lt;th&gt;Winner&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simple SELECT (1 row)&lt;/td&gt;
&lt;td&gt;~0.3ms&lt;/td&gt;
&lt;td&gt;~1.2ms&lt;/td&gt;
&lt;td&gt;SQLite (~4x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex JOIN (5 tables)&lt;/td&gt;
&lt;td&gt;~2.1ms&lt;/td&gt;
&lt;td&gt;~3.8ms&lt;/td&gt;
&lt;td&gt;SQLite (~1.8x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INSERT (1 row)&lt;/td&gt;
&lt;td&gt;~0.5ms&lt;/td&gt;
&lt;td&gt;~2.1ms&lt;/td&gt;
&lt;td&gt;SQLite (~4x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE (1 row)&lt;/td&gt;
&lt;td&gt;~0.4ms&lt;/td&gt;
&lt;td&gt;~1.9ms&lt;/td&gt;
&lt;td&gt;SQLite (~4.7x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backup (full database)&lt;/td&gt;
&lt;td&gt;~5ms (copy file)&lt;/td&gt;
&lt;td&gt;~2.3s (pg_dump)&lt;/td&gt;
&lt;td&gt;SQLite (~460x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restore&lt;/td&gt;
&lt;td&gt;~5ms (copy file)&lt;/td&gt;
&lt;td&gt;~1.8s (psql)&lt;/td&gt;
&lt;td&gt;SQLite (~360x faster)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why SQLite is faster:&lt;/strong&gt; No network overhead. No server process. No connection pooling. The database is in the same process as the app. I checked three times because I didn't believe it. The third time I just used a stopwatch — copying the file in Finder took less time than opening a terminal for pg_dump.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why PostgreSQL is slower:&lt;/strong&gt; Every query goes through a socket, even on localhost. Connection setup takes time. MVCC adds overhead. It's like calling your neighbor through long-distance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-offs I Accepted
&lt;/h2&gt;

&lt;p&gt;Choosing SQLite means accepting some limitations:&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-off 1: Single-writer concurrency
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Only one process can write to the database at a time.&lt;br&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; LockMargin is a single-user app. The freelancer is the only writer. This is not a limitation — it's the correct model. I once tried opening the database from two windows simultaneously. Got an error. Thought: "Why would I need two windows?" Closed the second one. Problem gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-off 2: No built-in replication
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Can't replicate the database to another server.&lt;br&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; Backup is a file copy. Sync is a file copy. Replication isn't needed. I copy lockmargin.db to a USB stick once a week. That's my "replication." Been doing it since 2012, when I saved tax returns the same way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-off 3: Limited data types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; No JSONB, no arrays, no custom types.&lt;br&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; Invoicing data is relational. Clients, invoices, line items, payments. SQLite's type system is sufficient. I spent three days wondering if I needed JSONB for settings. Then I just made a separate table. Works fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trade-off 4: No stored procedures
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Can't write complex logic in the database.&lt;br&gt;
&lt;strong&gt;Reality:&lt;/strong&gt; Business logic belongs in the app, not the database. This is a feature, not a bug. I've seen what happens when logic is smeared between SQL and code. Don't want to repeat that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Story (What If We Switched?)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; What if we needed to switch to PostgreSQL later? Could we migrate?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; Yes. SQLite to PostgreSQL migration is straightforward. The SQL is 95% compatible. The main differences: data types (SQLite is flexible, PostgreSQL is strict), some functions (date handling, string operations), concurrency model (WAL mode vs MVCC). I did one myself in 2021. The hardest part was explaining to the client why "just migrate" took a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; How long would migration take?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; For LockMargin's schema? Maybe 2-3 days. Most of the work is testing, not rewriting. Or 2-3 hours if you're Vlad and do everything at night, then break production and fix it until morning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vlad:&lt;/strong&gt; So we're not locked in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Jon:&lt;/strong&gt; No. SQLite is a starting point, not a prison. If you outgrow it, you can migrate. But for 99% of freelance apps, you won't outgrow it. I know. I counted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Reason
&lt;/h2&gt;

&lt;p&gt;Here's the honest truth: I chose SQLite because I didn't want to explain to a freelancer why they need to install PostgreSQL.&lt;/p&gt;

&lt;p&gt;Imagine the onboarding:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download LockMargin ✓&lt;/li&gt;
&lt;li&gt;Install PostgreSQL ✗&lt;/li&gt;
&lt;li&gt;Configure pg_hba.conf ✗&lt;/li&gt;
&lt;li&gt;Create a database ✗&lt;/li&gt;
&lt;li&gt;Set up backups ✗&lt;/li&gt;
&lt;li&gt;Handle updates ✗&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's not a product. That's a barrier to entry. I'd close the tab at step two.&lt;/p&gt;

&lt;p&gt;With SQLite:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download LockMargin ✓&lt;/li&gt;
&lt;li&gt;Open it ✓&lt;/li&gt;
&lt;li&gt;Your data is in a single file on your desktop ✓&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a product. I opened lockmargin.db in DB Browser to check. A file. 2.4 megabytes. Everything I created in a month. Copied to a flash drive in 3 seconds. That's my backup strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  When PostgreSQL Is the Right Choice
&lt;/h2&gt;

&lt;p&gt;I'm not saying SQLite is always better. PostgreSQL is the right choice when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multiple users&lt;/strong&gt; need concurrent access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complex queries&lt;/strong&gt; with JSONB, full-text search, geospatial data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High availability&lt;/strong&gt; with replication and failover&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large datasets&lt;/strong&gt; (terabytes, billions of rows)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enterprise requirements&lt;/strong&gt; (audit logs, row-level security)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those use cases, PostgreSQL is unbeatable. But for a freelancer managing their own invoices? SQLite is the correct tool. I spent 20 years proving the opposite to clients. Now I'm proving it to myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose the database that matches your architecture, not the one you know best.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I knew PostgreSQL. I loved PostgreSQL. But PostgreSQL is a server database, and LockMargin is a desktop app. The architecture demanded SQLite.&lt;/p&gt;

&lt;p&gt;If you're building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A web app with multiple users&lt;/strong&gt; &amp;gt; PostgreSQL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A mobile app&lt;/strong&gt; &amp;gt; SQLite (or Realm)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A desktop app&lt;/strong&gt; &amp;gt; SQLite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An offline-first app&lt;/strong&gt; &amp;gt; SQLite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A data warehouse&lt;/strong&gt; &amp;gt; PostgreSQL (or ClickHouse)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Match the tool to the job. I spent a week convincing myself PostgreSQL "would work too." Spent two more days on a Docker container. Then deleted everything and ran &lt;code&gt;cargo add rusqlite&lt;/code&gt;. Works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;This is post #2 in the Building LockMargin series. Next up: the first time I dogfooded my own product, and what broke. Spoiler: I broke PDF export at 2 AM before a deadline. But that's another story.&lt;/p&gt;

&lt;p&gt;Follow the series at &lt;a href="https://lockmargin.com/blog-archive.html" rel="noopener noreferrer"&gt;Building LockMargin&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;Is SQLite production-ready?&lt;/p&gt;

&lt;p&gt;Yes. SQLite is used by Apple, Google, Microsoft, and every major browser. It's the most deployed database in the world. It's not just production-ready — it's production-proven. I don't trust "proven" in marketing, but here — facts. Billions of devices.&lt;/p&gt;

&lt;p&gt;Can SQLite handle large datasets?&lt;/p&gt;

&lt;p&gt;SQLite can handle databases up to 281 TB. For invoicing, you'll never hit that limit. Most freelancers have thousands of invoices, not millions. I checked: my database after 4 months is 2.4 MB. After 10 years — maybe 60 MB. Smaller than one phone photo.&lt;/p&gt;

&lt;p&gt;What about data corruption?&lt;/p&gt;

&lt;p&gt;SQLite uses WAL (Write-Ahead Logging) mode by default, which prevents corruption even if the app crashes mid-write. I've never lost data to corruption in 6 months of daily use. I once dropped my laptop. The database was fine. The laptop — cracked. Priorities.&lt;/p&gt;

&lt;p&gt;Can I use SQLite with Rust?&lt;/p&gt;

&lt;p&gt;Yes. The &lt;code&gt;rusqlite&lt;/code&gt; crate is excellent. It's safe, fast, and well-maintained. LockMargin uses rusqlite for all database operations. I tried sqlx. Then Diesel. Went back to rusqlite. Just works.&lt;/p&gt;

&lt;p&gt;What if I need to sync across devices?&lt;/p&gt;

&lt;p&gt;SQLite doesn't have built-in sync. For LockMargin, sync is a file copy (USB, cloud storage, etc.). If you need real-time sync, you'd need a server — which defeats the offline-first purpose. I copy the file. Not perfect. But works. Like everything else in this post.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. — Jon agreed to stay anonymous. But he's real. We had coffee at a cafe in Berlin in 2019. He said then: "All scaling problems are solved by not scaling." I didn't understand then. I did in December.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;SQLite isn't a compromise. It's the correct choice for offline-first desktop apps.&lt;/p&gt;

&lt;p&gt;PostgreSQL is a server database. It's powerful, flexible, and enterprise-grade. But it requires a server, administration, and maintenance. For a freelancer who just wants to send invoices, that's overkill.&lt;/p&gt;

&lt;p&gt;SQLite is embedded, zero-config, single-file, and fast. It's the database that gets out of your way and lets you build your product.&lt;/p&gt;

&lt;p&gt;I chose SQLite because I wanted to build a tool, not a sysadmin job. And six months later, I'm glad I did.&lt;/p&gt;




&lt;p&gt;I build LockMargin - a local Financial OS for freelancers. No cloud, no subscription, no account. $49 once.&lt;br&gt;
Canonical: &lt;a href="https://lockmargin.com/blog/why-i-chose-sqlite-over-postgresql.html" rel="noopener noreferrer"&gt;https://lockmargin.com/blog/why-i-chose-sqlite-over-postgresql.html&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>privacy</category>
      <category>localfirst</category>
    </item>
  </channel>
</rss>
