How to Install SQLite and Self-Hosted libSQL (Turso) on Ubuntu 24.04
SQLite is the most widely deployed database engine in the world. It ships inside every Android phone, every iOS device, every copy of Firefox and Chrome, every Python install, and inside aircraft and Bloomberg terminals. Despite that scale, most server developers still reach for Postgres or MySQL by default -- even for workloads where SQLite would be faster, simpler, and free of operational overhead. This guide walks you through installing SQLite on Ubuntu 24.04, configuring it for production-grade web workloads, replicating it continuously with Litestream, and upgrading to a full Turso-compatible remote database by running the open-source libSQL server (sqld) on your own VPS.
Skip the setup? Deploy a ready-to-use SQLite + libSQL VPS with WAL tuning, Litestream backups, and sqld pre-configured. Launch a CloudCore Starter now and be writing queries in 60 seconds.Table of Contents
What is SQLite?
SQLite is a C-language library that implements a small, fast, self-contained, full-featured SQL database engine. Unlike Postgres or MySQL, SQLite is not a separate server process -- it runs inside your application as a linked library, reading and writing directly to a single file on disk. There is no daemon to start, no port to listen on, no user accounts to manage, and no network round-trip. The entire database -- schema, tables, indexes, triggers, and views -- lives in one portable file that you can copy, version control, attach to an email, or check into S3.
SQLite is explicitly in the public domain and is one of the four formats recommended by the US Library of Congress for long-term data preservation. It is embedded in essentially every smartphone, every major browser, every Mac and Windows PC (via system libraries), every Python and Ruby install, and inside devices as varied as Tesla cars, Airbus A350 avionics, and Bloomberg trading terminals. Estimates put the number of deployed SQLite instances well past one trillion, making it the most widely used database software in history by a wide margin.
Use cases where SQLite shines:
- Embedded applications -- Mobile apps, desktop apps, browser storage, IoT devices, edge gateways.
- Development and testing -- Zero-setup dev environments and fast, deterministic test fixtures.
- Low-to-medium-concurrency web apps -- Blogs, internal tools, admin panels, SaaS apps with a single writer and many readers.
- Data analysis and ETL -- Ingest CSV/JSON, run SQL against it, export results.
sqlite-utilsand Datasette make this workflow a joy. - Cache and session stores -- Fast, persistent local state without a Redis dependency.
- Application file format -- When a file needs structured, queryable, transactional storage (think Photos libraries,
.sketchfiles, Fossil repos).
Why SQLite Beats "Real" Databases for Many Workloads
The historic wisdom "SQLite is fine for dev, use Postgres in prod" deserves a second look. A wave of production systems -- some of the biggest on the internet -- are built on SQLite:
- Cloudflare D1 is a serverless, globally distributed database built on SQLite (via libSQL-style storage) and serves billions of queries a month across Cloudflare's edge network.
- Fly.io hosts thousands of production apps that run SQLite on a local volume with Litestream or LiteFS replication instead of a managed Postgres.
- Amazon Bedrock and the Expensify backend both famously built SQLite-backed systems that scale to the kind of load you would "obviously" use a real database for, and beat the pants off it.
- Tailscale's control plane and parts of Notion's stack have leaned on embedded SQLite for hot paths.
SELECT from WAL-mode cache is a few microseconds. Multiply that by every query on a page render and the difference compounds.SQLite is also incredibly cheap to operate. No replicas to keep in sync, no connection pooler, no pg_hba.conf, no shared_buffers tuning, no role/grant dance, no upgrade pain. Your backup is a file. Your rollback is cp backup.db app.db.
The classic objection -- "SQLite can't handle concurrent writes" -- is mostly wrong with WAL mode. A single SQLite database supports one writer at a time, but that writer does not block readers, and with WAL enabled it can sustain thousands of writes per second on modern NVMe. The workloads where SQLite genuinely breaks down are: multi-writer sharded systems, OLAP over hundreds of GB, and apps that need synchronous multi-node durability. Everything else is fair game.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- 2 GB+ RAM (SQLite itself is tiny; this is for your app and libSQL)
- 20 GB+ SSD storage (NVMe strongly recommended -- SQLite performance is I/O bound)
Recommended Plan: CloudCore Starter>
SQLite's minimal footprint means you do not need a huge VPS. For most SQLite + libSQL workloads we recommend CloudCore Starter:>
- 2 vCPU cores
- 4 GB RAM
- 50 GB NVMe SSD
- Unmetered bandwidth>
This is more than enough for a SQLite-backed web app with Litestream replication and a sqld listener. Scale up to CloudCore Professional only if you are running analytics queries over tens of GB.Connect to your server via SSH:
ssh root@your-server-ipStep 1: Update System Packages
Refresh your package index and apply any pending upgrades. SQLite moves quickly and Ubuntu 24.04 ships a fairly recent release in its repos, but always start from a clean baseline.
sudo apt update && sudo apt upgrade -yIf the kernel was updated, reboot:
sudo rebootStep 2: Install sqlite3 and sqlite-utils
Ubuntu's default repos include the sqlite3 CLI and its shared library. Install them:
sudo apt install -y sqlite3 libsqlite3-devVerify the version:
sqlite3 --versionExpected output:
3.45.1 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257cc467aNext install sqlite-utils, Simon Willison's Swiss-army-knife companion CLI. It gives you one-liners for inserting CSV/JSON, extracting columns into lookup tables, running analysis queries, and a superb in-memory mode for ad-hoc data work.
The cleanest install on Ubuntu 24.04 is via pipx, which avoids the externally-managed-environment error from system pip:
sudo apt install -y pipx
pipx ensurepath
pipx install sqlite-utilsClose and reopen your SSH session (so ~/.local/bin is on PATH) and verify:
sqlite-utils --versionExpected output:
sqlite-utils, version 3.36Step 3: First Database and SQL Basics
Create your first database. SQLite has no CREATE DATABASE -- you just open a filename and it appears:
mkdir -p ~/sqlite && cd ~/sqlite
sqlite3 myapp.dbYou are now inside the SQLite REPL. Turn on human-readable output, then create a table and insert a row:
.mode column .headers onCREATE TABLE users ( id INTEGER PRIMARY KEY, email TEXT NOT NULL UNIQUE, name TEXT, created_at TEXT DEFAULT (datetime('now')) );
INSERT INTO users (email, name) VALUES ('[email protected]', 'Alice'), ('[email protected]', 'Bob');
SELECT * FROM users;
Expected output:
id email name created_at
-- ----------------- ----- -------------------
1 [email protected] Alice 2026-04-16 10:00:00
2 [email protected] Bob 2026-04-16 10:00:00Useful dot-commands inside the REPL:
.tables-- list all tables.schema users-- show theCREATE TABLEstatement.indexes users-- list indexes on a table.mode column/.mode box/.mode json-- change output format.headers on-- include column names in output.dump-- emit a SQL backup of the entire database.quit-- exit
.quit and try a one-off query from the shell:sqlite3 myapp.db "SELECT COUNT(*) FROM users;"sqlite-utils Power Tools
sqlite-utils turns flat files into tables in one command. Create a CSV and import it:
cat > people.csv <<'EOF' id,name,city 1,Alice,London 2,Bob,Berlin 3,Chen,Taipei EOF
sqlite-utils insert myapp.db people people.csv --csv sqlite-utils tables myapp.db --counts
Expected output:
[{"table": "users", "count": 2},
{"table": "people", "count": 3}]Other high-leverage commands:
sqlite-utils memory data.csv "SELECT city, COUNT(*) FROM t GROUP BY city"-- run SQL against a CSV without even creating a database file.sqlite-utils extract myapp.db people city-- normalize a repeated column into its own lookup table with a foreign key.sqlite-utils transform myapp.db users --add phone TEXT-- safely add columns, reorder, or change types.sqlite-utils analyze-tables myapp.db-- summary stats for every column (distinct counts, nulls, most common values).
Step 4: Production PRAGMAs (WAL and Friends)
SQLite's default settings are conservative and optimized for safety on arbitrary hardware. For a server-side web app on a modern SSD, change four things and you will get 10-100x better throughput.
Open the database and run:
sqlite3 myapp.dbPRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;What each one does:
journal_mode = WAL(Write-Ahead Logging) -- This is the single most important setting. WAL allows readers and one writer to work concurrently without blocking each other. Writes append tomyapp.db-waland are periodically checkpointed back into the main database. This setting is persistent -- once set, it stays set for that database file.synchronous = NORMAL-- In WAL mode,NORMALis safe (no corruption on OS crash) but skips a redundant fsync thatFULLdoes. Expect a 2-5x write speedup with no meaningful durability loss on ext4 + journaling.foreign_keys = ON-- Foreign key constraints are disabled by default (for backwards compatibility, astonishingly). You must enable them per-connection. Your application should run this on every connection open.busy_timeout = 5000-- When the writer lock is held by another connection, instead of immediately returningSQLITE_BUSY, wait up to 5 seconds for the lock. This makes the "database is locked" error nearly impossible to hit under normal load.
PRAGMA foreign_keys=ON and PRAGMA busy_timeout=5000 on every connection (these are per-connection). journal_mode and synchronous persist.For a Python SQLAlchemy app, for example:
from sqlalchemy import event, create_engineengine = create_engine("sqlite:///myapp.db")
@event.listens_for(engine, "connect") def set_sqlite_pragmas(conn, _): cur = conn.cursor() cur.execute("PRAGMA foreign_keys=ON") cur.execute("PRAGMA busy_timeout=5000") cur.close()
After enabling WAL, you will see two extra files next to myapp.db:
myapp.db -- main database
myapp.db-wal -- write-ahead log
myapp.db-shm -- shared-memory indexAll three must be kept together. Copy them as a unit, or use the online backup API (next section).
Step 5: Live Backups
The classic Unix instinct is cp myapp.db backup.db. Do not do this on a live database -- you may capture a half-written transaction and produce a corrupt backup.
Use one of two safe methods instead.
Method A: VACUUM INTO
This is the simplest live backup. It writes a clean, compacted copy of the database to a new file:
sqlite3 myapp.db "VACUUM INTO 'myapp-backup-$(date +%F).db';"This is transactionally consistent, works on a live database, and defragments in the process.
Method B: .backup (Online Backup API)
The .backup dot-command uses SQLite's online backup API, which copies page-by-page while the source database continues to accept writes:
sqlite3 myapp.db ".backup 'myapp-backup.db'"Both methods are safe on a WAL-mode database. For a nightly cron, VACUUM INTO is usually the right choice.
Optional: GUI Access with DB Browser for SQLite
For ad-hoc exploration, the open-source DB Browser for SQLite provides a desktop GUI. Install it on your laptop (not the server) and copy backup files down via scp, or point it at a remote file via an SFTP mount. It is the same app internal teams at government agencies use for records exports.
Step 6: Continuous Replication with Litestream
A nightly file copy is nice, but real production needs near-zero RPO (recovery point objective). Litestream by Ben Johnson (now Fly.io) streams the SQLite WAL to an object store (S3, Backblaze B2, Azure, GCS, or even another local filesystem) in real time. If your VPS dies, you restore from S3 with at most a few seconds of data loss.
Install the Litestream Binary
Download the latest release for Linux amd64:
curl -LO https://github.com/benbjohnson/litestream/releases/download/v0.3.13/litestream-v0.3.13-linux-amd64.deb
sudo dpkg -i litestream-v0.3.13-linux-amd64.deb
litestream versionExpected output:
v0.3.13Configure Replication to S3
Create /etc/litestream.yml:
sudo tee /etc/litestream.yml > /dev/null <<'EOF'
dbs:
- path: /home/ubuntu/sqlite/myapp.db
replicas:
- type: s3
bucket: my-litestream-backups
path: myapp
region: us-east-1
access-key-id: YOUR_AWS_KEY
secret-access-key: YOUR_AWS_SECRET
EOF
sudo chmod 600 /etc/litestream.ymlBackblaze B2 and any S3-compatible store work too -- just add an endpoint: key. For B2:
- type: s3
endpoint: https://s3.us-west-002.backblazeb2.com
bucket: my-bucket
path: myappRun Litestream as a systemd Service
The Debian package already installs litestream.service. Enable and start it:
sudo systemctl enable --now litestream
sudo systemctl status litestreamYou can confirm replication is live:
sudo journalctl -u litestream -fExpected output:
level=INFO msg="litestream v0.3.13"
level=INFO msg="initialized db" path=/home/ubuntu/sqlite/myapp.db
level=INFO msg="replicating to: name=s3 bucket=my-litestream-backups ..."
level=INFO msg="write snapshot" ...Restore from a Replica
On a fresh server (or in disaster recovery):
litestream restore -o /home/ubuntu/sqlite/myapp.db \
s3://my-litestream-backups/myappLitestream replays the WAL up to the most recent committed transaction. RPO in practice is under 10 seconds.
Step 7: Explore Data with Datasette
Datasette is a read-only web UI and JSON API for SQLite databases, also by Simon Willison. Point it at any .db file and you get instant browsable tables, facets, full-text search, graphs, and an OpenAPI-style JSON endpoint -- ideal for internal dashboards or publishing open data.
Install it with pipx:
pipx install datasette
datasette serve ~/sqlite/myapp.db --host 0.0.0.0 --port 8001Browse to http://your-server-ip:8001 and explore. For production, run it behind Nginx with basic auth or put it on a private IP.
Step 8: Install libSQL Server (sqld) for Turso-Compatible Access
SQLite's one limitation for server apps is that the database lives inside one process on one machine. If you want multiple servers to query the same SQLite database -- or if you want your laptop dev app to connect to the same database your production API uses -- you need a networked SQLite. That is what libSQL solves.
libSQL is an open-source, MIT-licensed fork of SQLite maintained by Turso. It adds HTTP and gRPC wire protocols, native replication, and embedded-replica clients. sqld is the daemon that speaks these protocols. You can run Turso managed cloud, or self-host sqld on your own VPS and get the same client APIs.
Install sqld
Turso publishes a one-line installer that drops sqld and the turso CLI into ~/.turso/bin:
curl -sSfL https://get.tur.so/install.sh | bash
source ~/.bashrc
sqld --versionExpected output:
sqld 0.24.32Run sqld Manually
Pick a data directory and launch:
mkdir -p ~/sqld-data
sqld --http-listen-addr 0.0.0.0:8080 --db-path ~/sqld-dataExpected output:
INFO Starting sqld
INFO HTTP listening on 0.0.0.0:8080
INFO Primary node readysqld stores each logical database as a directory under --db-path. The HTTP endpoint implements the Hrana protocol (Turso's line protocol), plus a compatibility layer so any HTTP client can POST SQL:
curl -s http://localhost:8080/v2/pipeline -H "Content-Type: application/json" -d '{
"requests": [
{"type": "execute", "stmt": {"sql": "CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)"}},
{"type": "execute", "stmt": {"sql": "INSERT INTO kv VALUES (?, ?)", "args": [{"type":"text","value":"hello"},{"type":"text","value":"world"}]}},
{"type": "execute", "stmt": {"sql": "SELECT * FROM kv"}},
{"type": "close"}
]
}'Run sqld as a systemd Service
For production, supervise it with systemd:
sudo tee /etc/systemd/system/sqld.service > /dev/null <<'EOF' [Unit] Description=libSQL Server (sqld) After=network.target[Service] Type=simple User=ubuntu Environment="SQLD_HTTP_LISTEN_ADDR=0.0.0.0:8080" Environment="SQLD_DB_PATH=/home/ubuntu/sqld-data" ExecStart=/home/ubuntu/.turso/bin/sqld Restart=on-failure RestartSec=5
[Install] WantedBy=multi-user.target EOF
sudo systemctl daemon-reload sudo systemctl enable --now sqld sudo systemctl status sqld
How libSQL Extends SQLite
Compared to stock SQLite, libSQL adds:
- HTTP + WebSocket + gRPC remote access -- Any client, any language, over the wire.
- Native streaming replication --
sqldprimary pushes a consistent frame log to read-replicasqldinstances. - Embedded replicas -- A client library can open a local SQLite file that the library itself keeps in sync with a remote primary. Reads hit local disk in microseconds; writes are forwarded to the primary and acknowledged when durable. This gives you Postgres-read-replica semantics without running a separate replica server.
- Still single-writer -- libSQL preserves SQLite's single-writer guarantee, which is what keeps semantics sane. Multi-node writes funnel through the primary.
- 100% SQLite on-disk format compatibility -- You can
sqlite3a libSQL data file directly. There is no migration gap.
Step 9: Secure sqld with TLS and JWT Auth
sqld on port 8080 with no auth is fine for localhost. Expose it publicly and you need two things: JWT authentication and TLS via Nginx.
Enable JWT Auth
sqld accepts a JWT-signing public key and requires every request to present a bearer token signed by the corresponding private key. Generate a keypair:
openssl genpkey -algorithm ED25519 -out sqld-private.pem
openssl pkey -in sqld-private.pem -pubout -out sqld-public.pemStart sqld with the public key:
sqld \
--http-listen-addr 0.0.0.0:8080 \
--db-path ~/sqld-data \
--auth-jwt-key-file /home/ubuntu/sqld-public.pemUpdate the systemd unit accordingly. Your application signs a JWT with the private key (any standard JWT library works) and sends Authorization: Bearer <token> on every request.
Reverse Proxy with TLS
Put Nginx in front for TLS termination:
sudo apt install -y nginx certbot python3-certbot-nginxsudo tee /etc/nginx/sites-available/sqld > /dev/null <<'EOF' server { listen 443 ssl http2; server_name db.yourdomain.com;ssl_certificate /etc/letsencrypt/live/db.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/db.yourdomain.com/privkey.pem;
client_max_body_size 50m;
location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;
# Needed for Hrana WebSocket upgrade proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 3600s; } }
server { listen 80; server_name db.yourdomain.com; return 301 https://$host$request_uri; } EOF
sudo ln -s /etc/nginx/sites-available/sqld /etc/nginx/sites-enabled/ sudo certbot --nginx -d db.yourdomain.com sudo nginx -t && sudo systemctl reload nginx
You can now connect clients to libsql://db.yourdomain.com with the JWT bearer token.
Step 10: Embedded Replicas from Client Apps
The killer feature of libSQL is the embedded replica: your application opens a local SQLite file that the libSQL client library keeps in sync with your sqld primary. Reads never leave the process. Writes are forwarded to the primary transparently.
Python
pip install libsql-experimentalimport libsql_experimental as libsqlconn = libsql.connect( "local-replica.db", sync_url="libsql://db.yourdomain.com", auth_token="YOUR_JWT", ) conn.sync() # pull latest from primary
cur = conn.cursor() cur.execute("SELECT * FROM users") print(cur.fetchall())
cur.execute("INSERT INTO users (email, name) VALUES (?, ?)", ("[email protected]", "Carol")) conn.commit() # write forwarded to primary
Node.js / TypeScript
npm install @libsql/clientimport { createClient } from "@libsql/client";const client = createClient({ url: "file:local-replica.db", syncUrl: "libsql://db.yourdomain.com", authToken: process.env.TURSO_AUTH_TOKEN!, });
await client.sync(); const result = await client.execute("SELECT * FROM users"); console.log(result.rows);
Official clients also exist for Rust (libsql crate), Go (github.com/tursodatabase/libsql-client-go), Dart/Flutter, and PHP.
Self-Hosted sqld vs Turso Cloud
Turso Cloud runs this exact same sqld binary on their infrastructure with edge replication across 35+ regions. For hobby projects it is free (500 databases, 9 GB storage). Self-hosting on your own CloudCore VPS costs a flat monthly fee and gives you full data residency, no quota limits, and zero network egress to a third party. The client APIs are identical -- you can switch between them by changing a URL.
rqlite: A Distributed Alternative
If your requirement is strongly-consistent multi-node writes (not just read replicas), look at rqlite. rqlite wraps SQLite in a Raft consensus group: every node can accept writes, they are replicated via Raft, and you get linearizable reads across a 3-or-5-node cluster.
Quick install:
curl -L https://github.com/rqlite/rqlite/releases/download/v8.26.7/rqlite-v8.26.7-linux-amd64.tar.gz | tar xz
cd rqlite-v8.26.7-linux-amd64
./rqlited -node-id 1 ~/rqlite-datarqlite is the right answer when you need multi-writer SQLite with HA failover. libSQL is the right answer when you want single-writer SQLite with fast read replicas and embedded local caches. They are complementary, not competing.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
Error: database is locked | WAL off, busy_timeout missing, or long-running write txn | Enable WAL (PRAGMA journal_mode=WAL) and set PRAGMA busy_timeout=5000 on every connection. Shorten long transactions. |
| Backup file is corrupt when copied while app is running | Used cp instead of VACUUM INTO or .backup | Use sqlite3 db.sqlite "VACUUM INTO 'backup.db';" or Litestream. Never cp a live SQLite file. |
| Disk fills up unexpectedly | myapp.db-wal grew without checkpointing | Run PRAGMA wal_checkpoint(TRUNCATE); or set PRAGMA wal_autocheckpoint=1000;. Shrink with VACUUM. |
| SQLite is "slow" on cloud storage | Database file is on NFS, EFS, or networked block storage with poor fsync semantics | SQLite requires correct fsync. Run on local NVMe. Never place a SQLite file on NFS -- corruption is guaranteed on concurrent access. |
| "SSD lasts 6 months" / high wear | Write amplification from tiny transactions + synchronous=FULL | Set synchronous=NORMAL, batch writes in transactions, and use WAL. Modern enterprise NVMe handles billions of writes. |
sqld won't start: "address in use" | Another process on 8080 | sudo lsof -i :8080. Kill or change --http-listen-addr. |
sqld data directory from another version fails to open | Version mismatch after upgrade | Back up first, then follow Turso release notes. Major versions occasionally require migration. |
| Concurrent writes from multiple hosts | Two separate processes on different machines writing to the same file | Never do this. Put sqld in front of the file. Only sqld should write to --db-path. |
FAQ
When is SQLite the wrong choice?
Reach for Postgres or another client-server database when you need: (1) multiple machines writing to the same database with strong consistency, (2) OLAP workloads over hundreds of GB where ClickHouse or DuckDB would be a better fit, (3) advanced types like JSONB with indexing, PostGIS, or full-text search beyond SQLite's FTS5, (4) role-based access control enforced at the database level, or (5) true concurrent write throughput in the thousands-per-second range across multiple writers. For roughly 90% of web apps -- blogs, SaaS dashboards, internal tools, APIs with less than a few hundred writes/sec -- SQLite will outperform a networked Postgres on the metrics that matter (latency and ops cost).
SQLite vs Postgres for a web app -- which should I pick?
If your app is a single process (or a few processes) on one VPS and your write load is moderate, SQLite with WAL + Litestream will be faster, cheaper, and simpler than Postgres. A query that takes 2 ms over the network takes 20 microseconds in-process. If your app is multi-region, multi-writer, or needs Postgres-specific features (JSONB, PostGIS, pg_vector, row-level security), use Postgres. See our Postgres install guide for that path.
Is libSQL fully compatible with SQLite?
Yes. libSQL is a fork that tracks upstream SQLite closely and preserves the on-disk format. Every SQLite pragma, every SELECT, every index works. libSQL adds features (native replication, WebAssembly user-defined functions, vector search) on top, but never removes or breaks existing SQLite behavior. You can take a libSQL database file and open it with the stock sqlite3 CLI right now.
rqlite vs libSQL -- which do I want?
Pick libSQL if you want one primary writer with read replicas, embedded-replica client libraries, and HTTP/gRPC remote access. Pick rqlite if you want multi-node fault tolerance with automatic failover and Raft-backed strong consistency across the cluster. libSQL prioritizes SQLite's single-writer model and edge replicas; rqlite prioritizes cluster HA. Both are excellent; they solve different problems.
How much does Turso Cloud cost vs self-hosting?
Turso Cloud has a generous free tier (500 databases, 9 GB total storage, 1 billion row reads/month) and paid plans starting around USD 29/month. Self-hosting sqld on a CloudCore Starter VPS at EUR 7.99/month gives you unlimited databases, unlimited queries, unlimited storage (up to disk size), full data residency, and zero vendor lock-in. The right choice depends on whether you value Turso's global edge replication (pick Cloud) or flat-rate simplicity and sovereignty (pick self-hosted).
Can I run SQLite in Docker?
Yes -- but the database file should live on a mounted volume on local NVMe, not inside the container's ephemeral layer and never on a bind-mounted network volume. A common pattern is to run your application container with -v /opt/app/data:/data pointing at a VPS-local SSD directory. See our Docker install guide for setup.
Next Steps
Now that SQLite and libSQL are running, here are high-leverage next moves:
- Set up nightly Litestream restore drills -- A backup you have never restored is not a backup. Schedule a monthly
litestream restoreinto a scratch VPS and diff against production to prove your RPO. - Wire up Datasette publish -- Use
datasette publishto turn any SQLite database into a deployable static site with an API. Great for internal dashboards and public open-data portals. - Add a libSQL read replica on a second VPS -- Run a second
sqldinstance in--replicamode pointed at your primary for geographically distributed reads. - Integrate full-text search with FTS5 -- Create an FTS5 virtual table and watch your search queries return in sub-millisecond latency without Elasticsearch.
- Combine with Docker Compose -- Deploy
sqldalongside your app, Litestream, and Datasette in a single compose file. See the Docker install guide. - Try vector search -- libSQL supports native vector indexes. Store OpenAI or local embeddings directly in SQLite and query with
vector_top_k()-- no separate vector database required.
Skip the Manual Install -- Get SQLite + libSQL Pre-Configured>
Our CloudCore Starter VPS comes with SQLite tuned for WAL, sqld running as a systemd service, Litestream streaming to S3, and Nginx + TLS in front of the libSQL endpoint. Deploy in 60 seconds and start writing queries immediately.
>
-sqlite3andsqlite-utilspre-installed
- WAL + production PRAGMAs applied
- sqld listening on a TLS-protected subdomain
- Litestream configured with your S3/B2 bucket
- Datasette optional for browsing data>
Deploy Your SQLite VPS Now -- CloudCore Starter from EUR 7.99/month.