Previous: operate the cluster · All tutorials
You will set up a second cluster that runs in Docker with the web interface
beside it, sign in with a user, and browse a repository in the browser. One
scribe-ctl setup --runtime docker --web builds the images, generates the
Compose project, and provisions everything, including the web interface's own
secrets and service account.
The web interface runs only as part of a Docker cluster, and scribe-ctl
never moves a cluster between runtimes, so this stage starts a new cluster;
the repositories of stages 1–4 stay in scribe-demo. (To run the services and
the web interface by hand instead, see Docker Compose
and the web interface by hand.)
You need Docker Engine or Docker Desktop with Compose v2 (docker compose up --wait), and the Scribe source checkout your scribe-ctl was built from: the
images are built from it and tagged with that scribe-ctl's build. No Node.js
or Rust toolchain is needed beyond what built scribe-ctl; the first image
build downloads base images and dependencies and takes a while.
1. Free the default ports and create a directory
The new cluster uses the same default ports as the stage 1–4 cluster. Stop
that one first (or give this setup --port-offset 1000 and add 1000 to every
port below). From scribe-demo:
scribe-ctl --config scribe-cluster.toml down
cd ..
umask 077
mkdir scribe-web-demo
cd scribe-web-demo2. Set up the Docker cluster
Replace /path/to/scribe with your checkout. The plan also builds the images
(building touches no cluster file), then runs every check:
scribe-ctl --config scribe-cluster.toml setup --variant gateway --runtime docker --web --source /path/to/scribe --plan Building scribe/auth:tutorial-next from /path/to/scribe (the first build takes a while)…
Building scribe/storage:tutorial-next from /path/to/scribe (the first build takes a while)…
Building scribe/gateway:tutorial-next from /path/to/scribe (the first build takes a while)…
Building the web interface's images (api, assets-derive, assets-index, cache, content, database, nginx, objects, search, web, worker) from /path/to/scribe/apps/web…
Scribe · local · setup plan · gateway variant · docker runtime
...
ENDPOINTS
auth tls://127.0.0.1:7443
storage-a tls://127.0.0.1:7447
gateway tls://127.0.0.1:7557
web http://localhost:8080
...
STEPS
1. Write the cluster definition scribe-cluster.toml and scribe-gateway.toml
2. Record the images scribe/auth:tutorial-next, scribe/storage:tutorial-next, scribe/gateway:tutorial-next and the web interface's of 0.1.0 (build tutorial-next) as a set in cluster-state/local/bin/tutorial-next
...
13. Create the web interface's missing secrets secrets/web/jwt.key and secrets/web/surreal.password
...
20. Ensure service account scribe-web reads in studio
21. Ensure scribe-web's credential secrets/web-reader/scribe-web.credential
22. Prepare the web database data/web (migrate.js on the empty store)
23. Write the cluster summary CLUSTER-INFO.txtThen run it and start the cluster:
scribe-ctl --config scribe-cluster.toml setup --variant gateway --runtime docker --web --source /path/to/scribe
scribe-ctl --config scribe-cluster.toml upScribe · local · Setup complete
wrote scribe-gateway.toml
wrote scribe-cluster.toml
recorded the images of 0.1.0 (build tutorial-next) in cluster-state/local/bin/tutorial-next
recorded tutorial-next as the active set
created the storage and gateway CA in pki/ca
created the auth CA in pki/auth-ca
issued pki/auth/server.pem
issued pki/storage-a/server.pem
issued pki/gateway/server.pem
initialized data/gateway
initialized data/auth; administrator credential secrets/admin.credential
initialized data/storage-a
storage-a: bound to namespace studio
wrote secrets/web/jwt.key
wrote secrets/web/surreal.password
server storage-a: registered in studio
storage-a: credential issued to secrets/storage-a.credential
scribe-gateway: created with its grants in studio
scribe-gateway: credential issued to secrets/gateway.credential
scribe-monitor: created with its grants in studio
scribe-monitor: credential issued to secrets/monitor.credential
scribe-web: created with its grants in studio
scribe-web: credential issued to secrets/web-reader/scribe-web.credential
prepared the web database data/web: applied 0001-stamp-shape-versions, 0002-sign-out-bearer-sessions
wrote CLUSTER-INFO.txt
...
Scribe · local · running
SERVICE STATE PID ENDPOINT
auth running — tls://127.0.0.1:7443
storage-a running — tls://127.0.0.1:7447
gateway running — tls://127.0.0.1:7557
api running — —
assets-derive running — —
assets-derive-sandbox-image running — —
assets-derive-sandbox-scene running — —
assets-index running — —
cache running — —
content running — —
database running — —
nginx running — http://localhost:8080
objects running — —
search running — —
web running — —
worker running — —up is docker compose up --wait: it returns once every container is healthy
and the gateway has fresh inventory.
What scribe-ctl did for you
-
Everything from stages 1–3, in one run: both CAs, the server certificates, the auth database with its pepper, revision witness and administrator credential, the store bound to
studiowith its credential, and the gateway with itsscribe-gatewayaccount. The files have the same names and meaning; the definition hasruntime = "docker", and auth's pepper and witness sit insecrets/auth/, a directory of their own that only auth's container mounts. -
Images and the Compose project. It built
scribe/auth,scribe/storage,scribe/gatewayand the web interface'sscribe/web-*images from the checkout, tagged with its own build, and recorded them as the cluster's first set (so a laterupgrade --sourcecan switch and switch back). Before every start it generates the Compose project incluster-state/local/docker/. Containers reach each other by service name and publish their ports on loopback only. The Scribe services' containers bind-mount only their own data and files and run as the owner of this directory, with a read-only root and no capabilities. -
The web interface (
[web]in the definition): its session-signing key and database password insecrets/web/; its database indata/web, prepared with the web worker'smigrate.js; and thescribe-webservice account, a namespace reader instudiowhose credential (secrets/web-reader/scribe-web.credential) the background services use to index repositories. The web services verify auth and the gateway against the two public CAs, like any client. -
The summary.
whereshows every file, and thedocker composecommand for the project if you want to look yourself:text Docker: Compose project scribe-local-2926d0f4 cluster-state/local/docker images: scribe/auth:tutorial-next, scribe/storage:tutorial-next, scribe/gateway:tutorial-next run docker compose for it as: docker compose --project-directory /home/you/scribe-web-demo/cluster-state/local/docker -p scribe-local-2926d0f4 -f /home/you/scribe-web-demo/cluster-state/local/docker/web.yaml -f /home/you/scribe-web-demo/cluster-state/local/docker/compose.yaml
The generated files are rewritten before every start: change the definition, not them. Details: run a cluster in Docker and run the web interface with scribe-ctl.
The same deployment by hand, with the same files, is the Docker
Compose guide followed by the web
interface guide. A Compose project made that way
comes under scribe-ctl later with adopt, which leaves its data and secrets
where they are and keeps clients' endpoints, sessions and trust: see take over
an existing Docker deployment.
3. Add a user and a repository
The helpers from stage 2 work here unchanged: they read the files relative to
the directory you load them in, and client-env.sh names the gateway from
stage 3. Copy them over, create the password file as in stage 2, and add Alice:
cp ../scribe-demo/admin-env.sh ../scribe-demo/client-env.sh .
mkdir -p passwords
(umask 077 && touch ./passwords/alice) # then put a password in it with an editor
. ./admin-env.sh
. ./client-env.sh
auth users add alice
auth users password alice --password-file ./passwords/alice
auth namespace-admins set alice studio
alice login alice --auth tls://127.0.0.1:7443 --auth-ca ./pki/auth-ca/ca.pem
alice repo create game --storage-compression lz4
alice workspace create game ./work/alice
(
cd work/alice
mkdir Assets
printf 'hero mesh v1\n' > Assets/Hero.txt
alice add Assets/Hero.txt
alice submit -m "Initial hero"
)Logged in as alice; session valid for 90 days.
created: studio/game id=72b2caea0caddd27a7e52823d1fa4b95 head=0 main=1b24da60b905 storage-compression=lz4
...
submitted change 1 (1 file, 1 of 1 chunks sent, 208 object bytes sent for 13 logical bytes, 3 server flushes)The CLI on your machine reaches the containers through their published ports, exactly as in stages 2 and 3.
4. Sign in and browse
Check the web API first:
curl --fail --show-error http://localhost:8080/api/v1/health{"status":"ok","version":"v1"}Open http://localhost:8080. Anonymous visitors see only public repositories, so the list is empty before you sign in.
- Open Sign in and enter
alicewith her password. Browser login uses Scribe auth; the web interface has no users of its own. - Open
studio/game, thenAssets/Hero.txt, and its history: the change Alice submitted from the CLI. - Use the Clone menu to see the native CLI commands for a working copy. Browser credentials are never included in them.
- Open Settings → Asset index. Within a minute it shows the files indexed,
read with the
scribe-webcredential setup issued.
Web login does not grant repository access: a user sees what their Scribe grants allow. See the web interface guide for file-size limits, session behavior, and integration details.
5. Diagnose
scribe-ctl --config scribe-cluster.toml status
scribe-ctl --config scribe-cluster.toml logs assets-index --lines 20
scribe-ctl --config scribe-cluster.toml logs apilogs takes any service of the project: the Scribe services by name, and the
web interface's (api, content, worker, assets-index, assets-derive,
web, nginx, database, cache, search, objects).
| Symptom | Check |
|---|---|
| The image build fails | Docker's network access and the build error; the same setup command resumes |
up times out | status, then logs SERVICE of the one that is not healthy |
| Sign-in is refused | The user's password (auth users password), and that you opened exactly http://localhost:8080 (localhost and 127.0.0.1 are different origins) |
| Signed in, but a repository is missing | That user's grants (scribe-ctl access --user NAME) |
| Settings → Asset index warns about the background account | scribe-ctl access --user scribe-web shows its reader grant; renew replaces its credential before it expires |
assets-derive-sandbox-* exits with bubblewrap cannot create a child's sandbox | See sandbox containers (Ubuntu 23.10 and later hosts) |
On Linux the web services run as their images' user (uid 1000): run
scribe-ctl as uid 1000, or give that uid read access to the web secrets and
the reader credential with an ACL, never by making them group-readable
(the ACL commands).
Docker Desktop maps ownership for you.
6. Stop, start, and serve HTTPS
scribe-ctl --config scribe-cluster.toml down
scribe-ctl --config scribe-cluster.toml updown stops the containers (docker compose stop) and keeps them, their logs,
and every volume; the data roots, the web database and the secrets are
directories here, on the host. Web sessions survive a restart. Back up what
what to back up lists:
besides data/ and secrets/, the web interface's objects volume holds
flow-run bundles that exist nowhere else, and the web database needs the
secrets/web/ files to be of any use.
This walkthrough serves plain HTTP on loopback. For team access, the web
interface's own TLS certificate is yours to provide: put fullchain.pem and
key.pem in secrets/web-tls/, set [web] origin in the definition to the
https:// address people use, and run up again. See production
deployment for the rest of a team setup, and
run a cluster in Docker for
upgrade --source, which also migrates and backs up the web database.