When SQLite Is Enough: K3s's Datastore Decision and When to Move On
K3s replaces etcd with SQLite by default, saving memory and operational overhead on edge devices. This post explains why that works, when it breaks, and how to migrate to PostgreSQL for HA — with a concrete step-by-step example.
31 Aug 2025, 05:56 UTC

The Problem: etcd Overhead on a Raspberry Pi
You're standing up a Kubernetes cluster on a Raspberry Pi 4 with 4 GB of RAM. Upstream Kubernetes demands an etcd quorum — three nodes minimum for production HA — and each etcd instance wants 500 MB to 1 GB just for the database. Before you've scheduled a single workload, the control plane has already consumed half your memory.
K3s solves this by making a deliberate architectural choice: SQLite as the default datastore. No separate etcd process, no quorum negotiation, no additional binary. The control plane writes state to a single file under /var/lib/rancher/k3s/server/db/state.db. For single-node clusters, edge gateways, and development environments, this eliminates an entire operational surface.
Why SQLite Works for the Default Case
K3s embeds the Kubernetes API server, controller manager, and scheduler in one binary. The SQLite backend is compiled in via kine, a shim that translates Kubernetes API operations into SQL transactions. Because the API server is the only writer, SQLite's single-writer model maps cleanly to the control plane's access pattern.
The trade-off is explicit: you gain simplicity and memory efficiency, but you lose horizontal write scalability. SQLite cannot serve a multi-master HA control plane. If you need multiple server nodes sharing the same datastore, you must switch to an external database — MySQL, PostgreSQL, or etcd itself.
Worked Example: Migrating from Embedded SQLite to PostgreSQL
Suppose your single-node K3s cluster has grown into a three-node edge deployment. You need HA control plane, so you provision a managed PostgreSQL instance (e.g., CloudSQL, RDS, or a local Patroni cluster) and point all three K3s servers at it.
- Provision the database. Create a database named
k3sand a user with full privileges. Note the connection string:postgres://k3s-user:password@pg-host:5432/k3s?sslmode=require. - Stop the first server. On the existing node, run
sudo systemctl stop k3s. - Restart with the external datastore flag:
sudo k3s server \ --datastore-endpoint="postgres://k3s-user:password@pg-host:5432/k3s?sslmode=require" \ --cluster-initThe
--cluster-initflag tells this node to bootstrap the schema. Subsequent servers omit it. - Verify the migration. After the server starts, check the datastore backend:
k3s server --datastore-endpoint # Output should show the PostgreSQL connection string (password masked)Then confirm cluster health:
kubectl get nodes -o wide kubectl get pods -A - Join additional servers. On each new node, run the same
k3s servercommand without--cluster-init. They will register as voting members in the same PostgreSQL-backed cluster.
Required permissions: root or sudo on each Linux host. The PostgreSQL user needs CREATE, ALTER, DROP, SELECT, INSERT, UPDATE, DELETE on the target database.
Risk: Network partition between K3s servers and PostgreSQL will make the control plane unavailable. Plan for low-latency, high-reliability connectivity (same VPC, dedicated subnet).
Limitation: SQLite Is Not a Backup Strategy
A common misconception: because SQLite is a single file, you can back it up with cp or rsync. In practice, the database is held open by the running API server. A filesystem-level copy may capture a corrupted state. K3s provides k3s etcd-snapshot for etcd backups, but no equivalent snapshot command exists for the embedded SQLite.
If you need point-in-time recovery on SQLite, stop the server first (systemctl stop k3s), copy /var/lib/rancher/k3s/server/db/state.db, then restart. This downtime window is acceptable for many edge scenarios but not for production services with SLAs.
Decision Checklist
- Stay on SQLite if: single server node, no HA requirement, memory-constrained hardware, operational simplicity valued over recovery speed.
- Move to PostgreSQL/MySQL if: multiple server nodes, HA control plane required, existing managed database infrastructure, need for standard backup tooling (pg_dump, mysqldump).
- Move to external etcd if: you already operate etcd, need etcd-specific features (lease semantics, watch performance at scale), or organizational policy mandates it.
Verify Your Current Configuration
Run this on any K3s server to see which datastore you're using:
k3s server --datastore-endpoint
# sqlite:///var/lib/rancher/k3s/server/db/state.db?mode=rwc → embedded SQLite
# postgres://... or mysql://... or etcd://... → external
If the output shows a SQLite path and you're planning to add a second server node, schedule the migration before you hit the HA wall.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.