SQLite
NFS
Persistent Volume
Database Management
Cloud Storage

How to place SQLite database outside of NFS Persistent Volume

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

SQLite relies on file-level locking for concurrency control, but NFS (Network File System) uses advisory locks that do not provide the guarantees SQLite requires. Running SQLite on an NFS-mounted volume leads to database corruption, "database is locked" errors, and data loss under concurrent access. The solution is to place the SQLite database on a local filesystem — either a local Persistent Volume (PV), a hostPath volume, an emptyDir, or a block storage volume (EBS, Persistent Disk) that provides proper POSIX file locking semantics.

Why SQLite Fails on NFS

SQLite uses POSIX advisory locks (fcntl()) to coordinate read/write access. NFS has several problems with this:

  • Advisory locks are not enforced: NFS advisory locks do not prevent other processes from writing. A second process can modify the database file even while it is locked.
  • Lock state can be lost silently: If the NFS server restarts or the network connection drops, lock state is lost without notification. SQLite believes it still holds the lock.
  • Stale file handles: NFS caches file data aggressively. SQLite may read stale data after another process writes, leading to corruption.

Solution 1: Local Persistent Volume

Use a local PersistentVolume backed by a disk on a specific Kubernetes node.

yaml
1# local-pv.yaml
2apiVersion: v1
3kind: PersistentVolume
4metadata:
5  name: sqlite-local-pv
6spec:
7  capacity:
8    storage: 5Gi
9  accessModes:
10    - ReadWriteOnce
11  persistentVolumeReclaimPolicy: Retain
12  storageClassName: local-storage
13  local:
14    path: /mnt/data/sqlite
15  nodeAffinity:
16    required:
17      nodeSelectorTerms:
18        - matchExpressions:
19            - key: kubernetes.io/hostname
20              operator: In
21              values:
22                - worker-node-1
23---
24apiVersion: v1
25kind: PersistentVolumeClaim
26metadata:
27  name: sqlite-pvc
28spec:
29  accessModes:
30    - ReadWriteOnce
31  storageClassName: local-storage
32  resources:
33    requests:
34      storage: 5Gi
yaml
1# deployment.yaml
2apiVersion: apps/v1
3kind: Deployment
4metadata:
5  name: myapp
6spec:
7  replicas: 1  # SQLite supports only one writer — single replica
8  template:
9    spec:
10      containers:
11        - name: app
12          image: myapp:latest
13          volumeMounts:
14            - name: sqlite-storage
15              mountPath: /data/sqlite
16      volumes:
17        - name: sqlite-storage
18          persistentVolumeClaim:
19            claimName: sqlite-pvc

Solution 2: Cloud Block Storage (EBS, Persistent Disk)

Cloud block storage volumes (AWS EBS, GCP Persistent Disk, Azure Disk) attach as local block devices and provide proper file locking.

yaml
1apiVersion: v1
2kind: PersistentVolumeClaim
3metadata:
4  name: sqlite-ebs-pvc
5spec:
6  accessModes:
7    - ReadWriteOnce  # Block storage is always RWO
8  storageClassName: gp3  # AWS EBS gp3
9  resources:
10    requests:
11      storage: 10Gi
yaml
1# Mount in the pod
2containers:
3  - name: app
4    volumeMounts:
5      - name: sqlite-vol
6        mountPath: /data/sqlite
7volumes:
8  - name: sqlite-vol
9    persistentVolumeClaim:
10      claimName: sqlite-ebs-pvc

Solution 3: hostPath Volume (Development Only)

yaml
1volumes:
2  - name: sqlite-storage
3    hostPath:
4      path: /var/lib/myapp/sqlite
5      type: DirectoryOrCreate

hostPath uses the node's local filesystem directly. This is simple but the data is tied to a specific node and lost if the pod moves.

Solution 4: emptyDir (Ephemeral)

yaml
1volumes:
2  - name: sqlite-storage
3    emptyDir:
4      sizeLimit: 1Gi

emptyDir provides a local filesystem but data is deleted when the pod is removed. Use this only for caches or temporary databases that can be rebuilt.

SQLite Configuration for Kubernetes

python
1import sqlite3
2
3# Use WAL mode for better concurrent read performance
4conn = sqlite3.connect('/data/sqlite/app.db')
5conn.execute('PRAGMA journal_mode=WAL')
6conn.execute('PRAGMA synchronous=NORMAL')  # Faster writes, still crash-safe with WAL
7conn.execute('PRAGMA busy_timeout=5000')   # Wait 5 seconds before returning SQLITE_BUSY

Common Pitfalls

  • Running multiple replicas with SQLite: SQLite supports only one writer at a time. Running multiple pod replicas all writing to the same database causes "database is locked" errors or corruption. Use replicas: 1 or switch to PostgreSQL/MySQL for multi-replica deployments.
  • Using ReadWriteMany access mode: ReadWriteMany (RWX) volumes are typically NFS or CephFS — exactly the filesystems that break SQLite. SQLite requires ReadWriteOnce (RWO) volumes with local or block storage.
  • Forgetting node affinity with local PVs: Local PersistentVolumes are tied to a specific node. Without node affinity configuration, the pod may be scheduled on a different node where the volume does not exist, causing the pod to stay in Pending state.
  • Not setting PRAGMA journal_mode=WAL: The default journal mode (DELETE) is slower and more prone to lock contention. WAL (Write-Ahead Logging) mode allows concurrent readers while writing and is strongly recommended for any server-side SQLite usage.
  • Data loss with emptyDir on pod restart: emptyDir data is deleted when the pod is removed (not just restarted in place). For persistent data, use a PersistentVolumeClaim with local or block storage. Only use emptyDir for caches or throwaway data.

Summary

  • Never use SQLite on NFS — advisory locks do not provide the guarantees SQLite needs
  • Use local PersistentVolumes or cloud block storage (EBS, Persistent Disk) with ReadWriteOnce access mode
  • Limit SQLite deployments to single-replica pods (one writer)
  • Enable WAL journal mode and set busy_timeout for better concurrency handling
  • For multi-replica or high-concurrency workloads, switch to a client-server database like PostgreSQL

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.