Skip to content
📌 Provide isolated databases for apps with Service Bindings.
SQLite Replication

SQLite Replication

OpenRun integrates Litestream to continuously replicate SQLite databases to S3-compatible object storage (AWS S3, SeaweedFS, MinIO, Cloudflare R2 and others). Two things can be replicated:

  • App data: the SQLite databases behind SQLite service bindings.
  • Server metadata: OpenRun’s own metadata and audit databases (single node, SQLite metadata).

Replication is continuous (changes upload within about a second) and restore is automatic: a new node, a lost volume or a re-attached binding is repopulated from the replica before the app starts. Together this gives disaster recovery for a complete node loss — start a new server with the same config file and everything comes back from object storage.

Litestream Configs

Litestream settings are named entries in the server config. Multiple configs allow different buckets, endpoints or credentials to coexist (per team, per environment, metadata vs apps):

openrun.toml
[litestream.mainbackup]
endpoint = "https://s3.example.com"        # S3-compatible endpoint; empty for AWS S3
bucket = "openrun-backups"
region = "us-east-1"
path_prefix = "openrun"                    # replica key prefix inside the bucket
access_key_id = '{{secret_from "env" "LITESTREAM_KEY_ID"}}'
secret_access_key = '{{secret_from "env" "LITESTREAM_KEY"}}'
force_path_style = true                    # required by most S3-compatibles
sync_interval = "1s"
retention = "72h"
snapshot_interval = "24h"

[metadata]
litestream_config = "mainbackup"           # replicate the server's own databases
KeyDefaultDescription
types3Replica type. file replicates to a local directory (metadata only; not usable for app bindings)
endpointS3-compatible endpoint URL; leave empty for AWS S3
container_endpointEndpoint as reachable from inside app containers, when it differs from endpoint (see below)
bucketBucket name (required for s3)
regionS3 region
path_prefixKey prefix inside the bucket
pathLocal directory (required for file)
access_key_id, secret_access_keyCredentials; support secret references, resolved at startup
force_path_stylefalsePath-style S3 URLs, required by most S3-compatible servers
sync_interval1sHow often changes are uploaded. This bounds the data loss window on a crash
retention24hHow long replica history is kept. This bounds how far back a restore can go
snapshot_interval24hSnapshot frequency; lower values make restores faster
sidecar_imagelitestream/litestream:0.5Litestream image for app replication containers. Keep pinned: the replica format ties restores to the image series
checkpoint_interval, min_checkpoint_page_count, truncate_page_nCheckpoint tuning, see Litestream’s guide. Apps with long-running reads should set truncate_page_n = 0
storage_class, sse_kms_key_idOptional S3 storage class and server-side encryption settings

Credentials cannot use the db secret provider: metadata replication must be resolvable before the metadata database is opened. All configured entries are validated at server startup.

Metadata Replication

Set metadata.litestream_config to a config name to replicate the server’s metadata and audit databases (the file cache is a rebuildable cache and is not replicated). This applies when the metadata database is SQLite; with PostgreSQL metadata there is nothing to do.

On startup, if a metadata database file is missing and a replica exists, it is restored before the server opens it. To rebuild a lost node: install OpenRun on a new machine, use the same config file, start the server. Apps, bindings, services, versions and audit history are restored from the replica; containerized apps then redeploy on first request, restoring their SQLite data the same way.

Replication runs inside the OpenRun server process (Litestream is embedded as a library); no separate binary or process is needed.

App Data Replication

Enable replication for a SQLite service by referencing a config:

openrun service create sqlite/main --config litestream_config=mainbackup
openrun app create --bind sqlite/main --approve github.com/example/notes-app /notes

Every *.db file the app creates in its binding directory is replicated, including files created while the app is running (Litestream’s directory watcher discovers them within seconds).

How it runs:

  • Docker/Podman: a per-app companion container (clc-<app-id>-ls) runs litestream replicate sharing the app’s data volume. Before the app container starts on an empty volume, one-shot restore containers pull any replicated databases back. The sidecar survives app version updates, stops gently (after a final sync) when the app is idle-shut-down, and restarts with the app.
  • Kubernetes: restore init containers plus a native sidecar (init container with restartPolicy: Always) run inside the app’s pod. Requires Kubernetes 1.29 or newer. The sidecar starts before the app container and is terminated after it, so the final changes are always synced.

Replica locations are keyed by binding identity and environment:

s3://<bucket>/<path_prefix>/bindings/<binding-id>/prod/...
s3://<bucket>/<path_prefix>/bindings/<binding-id>/staged/...

Production and staging apps replicate to separate locations. Because the replica follows the binding (not the app), detaching a binding and attaching it to a new app restores the data into the new app’s fresh volume. To use different buckets or credentials per environment, link a staging service whose config names a different litestream config.

Changing a service’s litestream_config takes effect on the next app reload: replication starts fresh in the new location, and the old location stops advancing but keeps its history.

Container-visible endpoints

The S3 endpoint must be reachable from inside app containers. For a localhost endpoint with Docker/Podman, OpenRun automatically substitutes the container-reachable host name (like host.docker.internal). Kubernetes has no automatic mapping: if pods reach the endpoint through a different address than the server, set container_endpoint on the litestream config. The server keeps using endpoint for its own access.

Monitoring

openrun replication status reports the state of every replicated database:

$ openrun replication status -f table
Kind       Target                              Config       State     LastSync             Files      Apps
metadata   metadata                            mainbackup   healthy   2026-07-25 16:31:42  -
metadata   audit                               mainbackup   healthy   2026-07-25 16:31:42  -
app        /auto/app_prd_.../sqlite (prod)     mainbackup   healthy   2026-07-25 23:31:30  data.db    /notes
app        /auto/app_prd_.../sqlite (staged)   mainbackup   pending   -                    -          stage.localhost:/notes

Use -f json for full detail, including the per-file breakdown and replica transaction ids. The same data is available to apps through the read-only replication_status function of the openrun.in plugin.

StateMeaning
healthyReplica advanced recently (metadata: local and replica are in sync)
idleReplica is reachable but has not advanced recently; normal for an app that is not writing
pendingNothing replicated yet (fresh binding, or an environment that has not deployed)
sidecar_downThe replication container is not running while replicated data exists; fix by reloading the app (app reload --promote)
misconfiguredThe service references a litestream config that is not defined (or not usable for apps)
errorThe replica location could not be checked; see the error field

App states combine the replica listing in object storage with the replication container’s state, so a dead sidecar is flagged even when the app is idle.

Operational Notes

  • Data loss window: replication is asynchronous. On a catastrophic failure, up to the last sync_interval of writes (1s by default) can be lost.
  • Restore window: bounded by retention. Increase it (and consider snapshot_interval) if you need to restore older states.
  • Storage: app volumes are Docker named volumes or block-backed PVCs. Network filesystems (NFS/SMB) are not supported for SQLite data, and NFS-backed PVCs may delay discovery of new database files.
  • Volume permissions: fresh volumes and restored files are made writable for non-root app users with a chmod that runs using the Litestream image, so distroless app images work with replication enabled. Kubernetes pods additionally get fsGroup: 65532.
  • Deletes keep data: deleting an app or binding keeps the volume and the replica. Replica history ages out per retention.
  • Image pinning: sidecar_image and the embedded Litestream are on the 0.5 series (LTX replica format). Upgrades are an operator action.
  • Windows: fully supported. App replication runs in Linux containers; metadata replication runs on the host through the embedded library.