Everything not listed in this document should behave the same as upstream DeckShare. If a feature, setting, or behavior is not mentioned here, the upstream documentation is accurate and fully applicable — see the Documentation section of
instructions.mdfor links.
DeckShare is a self-hostable, multiuser, Anki-compatible spaced-repetition server. Upstream ships no prebuilt image and no built-in migration runner or admin bootstrap — this package builds the binary from source, adds a PostgreSQL sidecar, and runs schema migrations automatically on every start.
- Image and Container Runtime
- Volume and Data Layout
- File Models
- Dependencies
- Network Access and Interfaces
- Installation and First-Run Flow
- Actions
- Tasks
- Health Checks
- Backups and Restore
- Limitations and Differences
- Quick Reference for AI Consumers
Two images, built for x86_64 and aarch64.
deckshare— built by this package's own rootDockerfile, not upstream's. Upstream'sDockerfile(in theupstream-project/git submodule, pinned perUPDATING.md) compiles only thedecksharebinary into a shell-less distroless final stage. This package's Dockerfile instead compilesdeckshareandgoose(the migration tool upstream's own CI installs separately, per.github/workflows/ci.ymlin the submodule) into adebian:bookworm-slimfinal stage, and copies in the submodule'smigrations/directory at/migrations. Entrypoint is/usr/local/bin/deckshare;gooselives at/usr/local/bin/goose.postgres— the stockpostgresimage, unmodified.
Subcontainers, all defined in startos/main.ts:
postgres-sub(imagepostgres) — runs thepostgresdaemon.deckshare-sub(imagedeckshare) — runs both themigrateoneshot and thedecksharedaemon; the subcontainer is shared between them since they use the same image.
One volume, main, with three subpaths:
main/postgresql→ mounted at/var/lib/postgresqlinpostgres-sub— the PostgreSQL data directory.main/media→ mounted at/data/mediaindeckshare-sub— DeckShare's content-addressed media blob store (MEDIA_ROOT), holding imported note/card media (images, audio) referenced by the database.main/store.json— this package's own file, not upstream's. Holds the generated PostgreSQL password.
There is no embedded database — the data of record lives in PostgreSQL, in the volume above.
store.json(startos/fileModels/store.json.ts) — JSON, holds one key:pgPassword— generated once on install (startos/init/seedFiles.ts), never re-asserted afterward. There is no action to rotate it; recovering from a suspected leak means restoring the volume or reinstalling.
DeckShare itself owns no configuration file on disk — its only inputs are the four environment
variables set in startos/main.ts (DATABASE_URL, MEDIA_ROOT, ADDR, ORIGIN), all
re-asserted on every daemon start. ORIGIN is read fresh from the service's own interfaces
each start (utils.ts's getNonLocalUrls, via sdk.host.getOwn) rather than stored — every
non-local address the service is currently reachable at (LAN IP, .local, a configured
domain, Tor, ...), comma-separated. Upstream v0.1.26 added support for a comma-separated
ORIGIN (Jolls/deckshare#111/#112) specifically so a CSRF check doesn't have to pick one address;
before that fix this package made the user choose a single primary URL via a
now-removed Set Primary URL action, since every other address would 403 on state-changing
requests.
None. PostgreSQL runs as a bundled sidecar (postgres-sub), not a StartOS dependency.
One interface, ui (startos/interfaces.ts), type ui, protocol http, bound to port
3000 (DeckShare's fixed listen port — ADDR is hardcoded to :3000 in startos/main.ts, not
user-configurable). Serves both the web reviewer and any HTML admin/classroom views DeckShare
adds. PostgreSQL is not exposed on any interface; deckshare-sub reaches it at
127.0.0.1:5432.
On install, startos/init/seedFiles.ts generates the PostgreSQL password.
startos/main.ts then starts postgres, waits for pg_isready, runs the
migrate oneshot (goose ... up against /migrations — idempotent, safe on every start),
and only then starts the deckshare daemon. There is no separate setup wizard: the first
usable state is DeckShare's own signup screen. DeckShare has no built-in admin/superuser concept —
every account is created the same way, through that screen.
None.
None.
postgres(internal, not shown to the user) —pg_isreadyagainst127.0.0.1:5432. A failure here blocks themigrateoneshot and, transitively, thedecksharedaemon from starting; it means PostgreSQL itself hasn't finished initializing yet, not a data problem.deckshare/ Web Interface —checkPortListeningon port3000. "Not ready" during normal startup means themigrateoneshot (which blocks this daemon viarequires) is still running or PostgreSQL is still coming up; persistent failure after that means thedeckshareprocess itself crashed — check its logs.
Strategy: whole-volume copy (sdk.Backups.ofVolumes('main')), not a database dump. The
PostgreSQL data directory, the media blob store, and store.json are all backed up and
restored byte-for-byte together, so a restore comes back with the exact same generated
password it had at backup time — nothing to re-enter. Because the volume is copied rather
than dumped, a restored instance needs no re-sync or replay step before it's usable;
postgres simply starts against its already-populated data directory. ORIGIN is
recomputed fresh from the restored instance's own addresses on first start, not restored
from the backup.
- No admin-password or superuser bootstrap: every account, including the first, is created through DeckShare's own signup screen. If you intend to gate signups, do so at the network level (e.g. disable public interfaces) — DeckShare has no built-in registration toggle as of this packaging.
- PostgreSQL is not reachable from outside the package — there is no interface for it, and no action exposes its credentials. This differs from services (e.g. Arx) that expose their database for an external client to connect to.
package_id: 'deckshare'
image: local-build # custom Dockerfile, no public registry tag
architectures: [x86_64, aarch64]
subcontainers: [postgres-sub, deckshare-sub]
volumes:
main: /var/lib/postgresql, /data/media, /store.json
file_models:
- store.json
startos_managed_env_vars:
- DATABASE_URL
- MEDIA_ROOT
- ADDR
- ORIGIN
dependencies: none
interfaces:
ui: { type: ui, port: 3000 }
actions: none
tasks: none
health_checks:
- postgres
- deckshare