K3s Embedded SQLite Datastore: Architecture Note for Single‑Node Deployments
Architecture note on K3s’ embedded SQLite datastore: requirements, minimal design, trust boundaries, operational checks, failure modes, and when to switch to an external store.
30 Jul 2025, 20:08 UTC

Requirements
For a lightweight Kubernetes distribution the control plane must start without external dependencies, store all API objects persistently, and run on devices with limited RAM and storage (e.g., Raspberry Pi, edge gateways). The datastore therefore needs to be zero‑configuration, file‑based, and usable by a single server process.
Smallest Suitable Design
K3s satisfies the above by using SQLite as its default datastore. When no --datastore-endpoint flag is supplied, the server opens the file /var/lib/rancher/k3s/server/db.sqlite directly. No separate etcd cluster is launched, removing the need for additional binaries, network ports, or quorum logic.
The server process holds the SQLite file open for the lifetime of the API server; all read and write operations are performed via the SQLite C API inside the same process.
Trust and Data Boundaries
The SQLite file is a regular file on the host filesystem. By default it is owned by root with mode 0600 (-rw-------), meaning only the user running the k3s server (typically root) can read or write it. No network socket is exposed, so the trust boundary is limited to the host’s local access controls.
If the file’s permissions are loosened (e.g., 0644), any local user could read the cluster state, including secrets stored in etcd‑like objects. Conversely, making the file unwritable by the k3s user prevents the server from starting.
Operational Checks
- Verify the datastore in use
Run on the host where k3s is executed:
ps -ef | grep k3s
Look for either the absence of an explicit
--datastore-endpointflag (default SQLite) or a line similar to:... --datastore-endpoint='sqlite:///var/lib/rancher/k3s/server/db.sqlite'
If a different endpoint appears, the node is not using the embedded SQLite store.
- Check file ownership and permissions
ls -l /var/lib/rancher/k3s/server/db.sqlite
Expected output (ownership may vary if you run k3s as a non‑root user):
-rw------- 1 root root 12M Sep 30 09:00 /var/lib/rancher/k3s/server/db.sqlite
Anything more permissive than
0600should be tightened withchmod 600andchown root:root. - Run an integrity check
Stop the k3s server temporarily (or use a read‑only copy) and execute:
sqlite3 /var/lib/rancher/k3s/server/db.sqlite "PRAGMA integrity_check;"
A healthy database returns the single word
ok. Any other output indicates corruption and requires restore from backup. - Monitor size and vacuum
SQLite does not automatically return freed pages to the filesystem. Periodic
VACUUMcan reclaim space:sqlite3 /var/lib/rancher/k3s/server/db.sqlite "VACUUM;"
Schedule this during a maintenance window; the operation locks the database, so API requests will pause briefly.
Failure Modes and Design Triggers
- Disk‑full or I/O errors – If the partition holding
db.sqliteruns out of space, the server will fail to start or crash with messages likedatabase disk image is malformed. Monitoring free space on the mount point (e.g.,df -h /var/lib/rancher/k3s) and alerting on<10%remaining is a practical guard. - SQLite corruption – Power loss during a write can leave the file in an inconsistent state. The integrity check described above detects this; restoration requires copying a known‑good backup back to
db.sqliteand ensuring correct permissions. - Concurrent writers – SQLite supports multiple readers but only one writer at a time. Running two k3s server processes pointing to the same
db.sqlitewill lead to write conflicts and eventual corruption. This is why the embedded store is only suitable for a single‑node control plane.
When the Design Must Change
If any of the following conditions appear, migrate to an external datastore (etcd, MySQL, or PostgreSQL):
- The cluster exceeds a few dozen nodes and the API server experiences sustained high write load (e.g., frequent pod scaling, heavy controller activity). SQLite’s write performance degrades noticeably under such workloads.
- High‑availability is required. A single SQLite file cannot survive a node failure without shared storage; an external datastore provides replication and quorum.
- Operational policies demand separation of the control plane from the host filesystem (e.g., immutable host images). Placing the datastore on a network‑mounted volume or external DB satisfies this.
To switch, start k3s with an explicit endpoint, for example:
k3s server \ --datastore-endpoint='mysql://user:password@tcp(mysql-host:3306)/k3s' \ --tls-san $(hostname)
Before changing, etcd‑snapshot or mysqldump the current SQLite file (after stopping the server) and import it into the target datastore following the vendor’s migration guide.
Practical Verification Checklist
- Confirm the server is using SQLite via
psoutput. - Validate file permissions are
0600(or appropriate for your runtime user). - Run
PRAGMA integrity_check;and expectok. - Monitor disk usage on the volume containing
db.sqlite; set alerts for<10%free space. - Review logs for messages containing
database disk image is malformedorSQLITE_BUSYas early signs of trouble.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.