SQLite, Kubernetes & Litestream
I’ve been learning Kubernetes at home recently, and I’ve found a nice (but slightly dangerous!) pattern for hosting applications backed by SQLite in my homelab.
Table of Contents
Overview #
The pattern is relatively simple:
- No persistent volumes, just ephemeral ones
- No deployments, we use a statefulset instead
- No external database, we use litestream to backup/restore automagically
If this sounds familiar it’s because I’ve used this pattern for running SQLite-backed applications on fly.io before.
The obvious issue with this is that we risk losing any data written between Litestream replications if our cluster dies. Since I’m the only user and I have backups through litestream I’m fine with this risk level1.
Example manifest #
In this example we’ll deploy Kanboard, a basic Trello-like
application. I’m using ArgoCD & Kustomize to deploy this, but you can
just use kubectl apply -f example.yaml
as well. I will describe the
prerequisites after the yaml.
As we don’t use a persistent volume, any attachments used in Kanboard will disappear on restart, rescheduling of pod et c. Kanboard is not an ideal fit for this pattern! Linkding was much better :-)
The example goes the following:
- Create a namespace to run everything in
- Create a
ConfigMap
for litestream, which describes what database to backup, and where to backup/restore from - Create the
StatefulSet
, which includes an init container (which restores from backup if necessary) and Kanboard + Litestream in a pod. This also creates a ephemeral volume which the database is stored in. - Create a service for the STS
|
|
In order for this to work, you have to:
- Create a bucket to store the LiteStream backups in (you can use backblaze and their free tier for this)
- Create a secret to store this configuration in (we inject this secret into the LiteStream container in the manifest)
The secret needs to contain the following keys:
---
apiVersion: v1
kind: Secret
metadata:
name: litestream-s3
namespace: kanboard-sts
stringData:
# Bucket name
S3_BUCKET: ""
# Might look like: https://s3.us-west-000.backblazeb2.com
S3_ENDPOINT: ""
# Might look like this
S3_PATH: "kanboard_replica.sqlite3"
LITESTREAM_ACCESS_KEY_ID: ""
LITESTREAM_SECRET_ACCESS_KEY: ""
type: Opaque
Just apply the secret to your namespace.
Caveats / bootstrapping #
If you just apply the example manifest to your cluster, the
StatefulSet
wont be able to start! This is due to the init
container, which will crash as it can’t restore from backups as there
are no backups in the bucket.
The same thing goes for the LiteStream container running in the application pod. It won’t start as the application hasn’t started yet, so there is no database to backup.
So, in order to get this running:
- Comment out the entire
initContainers
section of the manifest (line 49-61) - Comment out the LiteStream container from the
containers
section of the manifest (line 63-74) - Apply the manifest, make sure the
StatefulSet
comes up - Uncomment the LiteStream container from the
containers
section again - Apply the manifest again, and make sure the
StatefulSet
comes up again - Uncomment the
initContainers
section again, and finally - Apply the manifest again
I haven’t tried the new sidecar support in Kubernetes 1.33 yet, that might remove the need for commenting/uncommenting the LiteStream container during bootstrapping.
Conclusion #
If you can live with the quirky bootstrapping process, it’s a fairly simple deployment pattern for SQLite backed applications.
I’m also using PostgreSQL on my NAS for “serious” applications, as I’ve decided that having my Kubernetes cluster depend on it is fine! ↩︎