K3s SQLite Datastore: When the Lightweight Default Becomes a Bottleneck
Embedded SQLite makes k3s ultra‑lightweight, but write‑heavy workloads and shared storage can hit concurrency limits. Here’s how to evaluate the trade‑off and migrate to external etcd if needed.
31 Oct 2025, 01:07 UTC

You spin up k3s expecting a minimal Kubernetes cluster, but the default datastore choice silently shapes your cluster's scalability limits. By default, k3s replaces etcd with an embedded SQLite database, meaning every API request, workload metadata change, and addon state lives in a single file on disk. This works fine for dev loops and tiny workloads, but write‑heavy pipelines or shared storage can hit SQLite’s concurrency ceiling before you notice.
How k3s SQLite works under the hood
K3s initializes the SQLite database at the first node start and stores it at /var/lib/rancher/k3s/agent/etc/k3s.db. The --datastore-option sqlite flag activates this mode, and it is the default behavior. All cluster data—API server state, workload metadata, core addon configurations—resides in that file. Re‑enabling etcd later is possible only by deploying a fresh k3s cluster with external etcd and migrating existing data, a process the project documents as operationally heavy.
Embedded initialization
When the first k3s node boots, the embedded SQLite database is created if it does not already exist. Subsequent nodes joining the cluster read from and write to the same file, assuming local disk access. The project's research confirms that file‑locking, managed via the OS’s fcntl or flock, coordinates concurrent access on Linux and macOS worker nodes.
File‑locking and concurrency
SQLite’s multi‑process concurrency relies on file‑locking, which works reliably when the database resides on local, non‑networked storage. Under typical workloads, the API server issues dozens of writes per second, and SQLite handles them without noticeable latency. However, the research notes that workloads exceeding thousands of operations per second may experience increased latency as the file‑locking mechanism serializes access.
When SQLite stalls: concurrency limits and storage pitfalls
- Write‑heavy workloads. CI/CD pipelines that continuously push resource definitions, extensive metric scraping, or large‑scale rollout strategies can generate write rates above SQLite’s comfortable envelope. The research flags that latency may rise noticeably beyond a few hundred writes per second, depending on hardware and network latency.
- Network‑mounted storage. If the SQLite file lives on an NFS, CIFS, or cloud‑attached volume shared across multiple k3s instances, file‑locking can fail. The research cautions that this often leads to node crashes or quorum errors, because the locking state does not synchronize reliably across network boundaries.
Backup and restore of the SQLite database also require care. Stopping k3s or using cp on the live file risks corruption if the process is mid‑write. The research recommends either taking the node offline first or using SQLite’s built‑in backup API (sqlite3 .backup) while the process remains running.
Worked example: migrating from SQLite to external etcd
If your workload has outgrown the embedded database, k3s v1.25+ provides a supported migration path, but it is not automatic. The steps illustrate the operational overhead the research highlights.
# Step 1 – Dump the embedded SQLite database
# Run on any k3s node with root privileges
sudo sqlite3 /var/lib/rancher/k3s/agent/etc/k3s.db '.dump' > k3s-sqlite-dump.sql
# Step 2 – Uninstall the current k3s cluster
# This removes the SQLite database and resets the node
sudo /usr/local/bin/k3s-uninstall.sh
# Step 3 – Install k3s with external etcd
# Provide a new cluster secret and opt into the etcd datastore
sudo k3s install --cluster-secret --datastore-option etcd
# Step 4 – Import the dump into the new etcd cluster
# This step is not automated by k3s; use standard etcd tooling
# Example (adjust paths and versions as needed):
etcdctl snapshot restore k3s-sqlite-dump.sql \
--data-dir /var/lib/etcd/default.etcd \
--initial-cluster =:2380 \
--initial-advertise-peer-urls :2380 \
--initial-token
After the import, verify that the API responds as expected:
# Check cluster nodes
k3s kubectl get nodes
# List all namespaces and pods to confirm API accessibility
k3s kubectl get pods --all-namespaces
Note that the import step uses standard etcdctl and requires version alignment between the dumped SQLite database and the new etcd binary. If the k3s version and etcd version are mismatched, the import may fail silently or corrupt the cluster state. Always keep backups of the original k3s.db before beginning.
Trade‑offs and decision checklist
| Scenario | Recommendation |
|---|---|
| Development, testing, or proof‑of‑concept clusters (< 3 nodes, local disk only) | Stay with the default SQLite datastore; no operational overhead. |
| Production clusters with modest write traffic (< 200 writes/second) and local storage | SQLite is sufficient; monitor API latency and database growth. |
| Write‑heavy pipelines, extensive metric scraping, or multi‑node production with network‑shared storage | Plan for external etcd from the start, or budget time for a later migration. |
If you are already running k3s in production and suspect SQLite is limiting performance, the research suggests starting with the verification commands below before committing to a full migration.
Closing: verify before you commit
- Inspect the database path and datastore flag:
ps aux | grep k3sand examine the systemd service file at/etc/systemd/system/k3s.servicefor the--datastore-optionvalue. - Verify SQLite presence:
ls -l /var/lib/rancher/k3s/agent/etc/k3s.db; an existing file confirms embedded mode, while its absence (in a fresh multi‑node install) may indicate etcd deployment. - Run API responsiveness checks after a controlled workload:
k3s kubectl get nodesandk3s kubectl get pods --all-namespaces. Compare latency to your baseline expectations. - If write rates approach or exceed a few hundred operations per second, or if the database resides on network‑mounted storage, prototype the external etcd path in a staging cluster before promoting to production.
K3s remains an excellent choice for lightweight Kubernetes, but the embedded SQLite datastore is a deliberate trade‑off. Understanding its limits—and the concrete migration path available in v1.25+—lets you decide early whether to stay the course or plan for external etcd, avoiding surprises later.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.