Every app includes:

path use
/shared.db replicated SQLite
/shared/ replicated files

Open them as local paths. Writes commit locally, converge across instances and regions, and land durably in object storage. There is no database service or primary endpoint to provision.

Use these before adding a volume. A persistent volume is for software that requires a private local filesystem.

sqlite3 /shared.db <<'SQL'
CREATE TABLE IF NOT EXISTS notes(id TEXT PRIMARY KEY, body TEXT);
INSERT INTO notes VALUES('one', 'hello')
  ON CONFLICT(id) DO UPDATE SET body=excluded.body;
SELECT body FROM notes WHERE id='one';
SQL
printf 'generated once\n' > /shared/artifact.txt
cat /shared/artifact.txt

multi-writer rules

The replication engine is syzy; its documentation specifies the full conflict model.

SQLite compatibility

/shared.db works transparently with programs that dynamically link the system libsqlite3: Python's stdlib, Ruby's system SQLite gem, the CLI, Rails, Django, and Go built with -tags=libsqlite3.

Bindings that statically embed SQLite cannot be intercepted. Examples include better-sqlite3, Node's built-in node:sqlite, modernc.org/sqlite, and mattn/go-sqlite3 without the system-library build tag. Rebuild against libsqlite3.so, or load the engine explicitly after opening:

SELECT load_extension('/usr/local/lib/syzy-engine.so', 'sqlite3_syzy_init');

shared files

/shared/ is a content-oriented POSIX mount for uploads, generated assets, and small blobs. Advisory locks, mmap, and O_DIRECT are not supported; put coordination in /shared.db.

Two instances writing one file merge by byte range under the same last-writer-wins clock; creating the same new path twice keeps one winner.

mount shared files elsewhere

A shared Compose volume mounts the app's /shared/ tree at another path:

services:
  web:
    volumes: [uploads:/srv/uploads]
  worker:
    volumes: [uploads:/work/uploads]

volumes:
  uploads:
    x-kedge: {shared: true}

Both mount points refer to the same replicated file tree. One service may currently mount one shared volume path.

named databases

An app's default database follows that app. A named database can outlive and serve several apps:

kedge db create mydata
kedge db attach mydata myapp
kedge db attach mydata myworker

New instances attach immediately; existing instances pick it up when they recycle. kedge db detach myapp returns that app to its private default. The app page provides a SQL console for either kind. Run kedge db shell myapp 'SELECT …' from anywhere, or omit myapp when KEDGE_APP is set.

The database console gives each submitted statement its own transaction boundary, and writes replicate atomically. Explicit BEGIN, COMMIT, and savepoint wrappers are not supported. Put writes that must commit together in one statement, such as a multi-row INSERT or a CTE.

previews and forks

On object-backed deployments, a preview's first deploy branches the production app's current database and files without copying all pages. Re-pushes keep the preview's changes; deleting and recreating it branches production again. A source with no stored state starts empty. Local servers without an object bucket keep the empty-preview behavior.

The branch covers /shared.db and /shared/, including an attached named database. Persistent volumes are separate. External database state is not branched; inherited URLs can still point both apps at the same service. Explicit forks use the same shared-data branch:

kedge fork myapp myapp-experiment

A static or HTML app published with the forkable=true property can be forked by anyone, signed in or not, into an app they own; the copy is private and carries the source's data as of that moment.