5. Start the web interface with scribe-ctl and Docker
Documentation/Start here
5. Start the web interface with scribe-ctl and Docker8 min read

5. Start the web interface with scribe-ctl and Docker

On this page

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:

sh
scribe-ctl --config scribe-cluster.toml down
cd ..
umask 077
mkdir scribe-web-demo
cd scribe-web-demo

2. 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:

sh
scribe-ctl --config scribe-cluster.toml setup --variant gateway --runtime docker --web --source /path/to/scribe --plan
text
  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.txt

Then run it and start the cluster:

sh
scribe-ctl --config scribe-cluster.toml setup --variant gateway --runtime docker --web --source /path/to/scribe
scribe-ctl --config scribe-cluster.toml up
text
Scribe · 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 studio with its credential, and the gateway with its scribe-gateway account. The files have the same names and meaning; the definition has runtime = "docker", and auth's pepper and witness sit in secrets/auth/, a directory of their own that only auth's container mounts.

  • Images and the Compose project. It built scribe/auth, scribe/storage, scribe/gateway and the web interface's scribe/web-* images from the checkout, tagged with its own build, and recorded them as the cluster's first set (so a later upgrade --source can switch and switch back). Before every start it generates the Compose project in cluster-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 in secrets/web/; its database in data/web, prepared with the web worker's migrate.js; and the scribe-web service account, a namespace reader in studio whose 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. where shows every file, and the docker compose command 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:

sh
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"
)
text
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:

sh
curl --fail --show-error http://localhost:8080/api/v1/health
text
{"status":"ok","version":"v1"}

Open http://localhost:8080. Anonymous visitors see only public repositories, so the list is empty before you sign in.

  1. Open Sign in and enter alice with her password. Browser login uses Scribe auth; the web interface has no users of its own.
  2. Open studio/game, then Assets/Hero.txt, and its history: the change Alice submitted from the CLI.
  3. Use the Clone menu to see the native CLI commands for a working copy. Browser credentials are never included in them.
  4. Open Settings → Asset index. Within a minute it shows the files indexed, read with the scribe-web credential 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

sh
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 api

logs 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).

SymptomCheck
The image build failsDocker's network access and the build error; the same setup command resumes
up times outstatus, then logs SERVICE of the one that is not healthy
Sign-in is refusedThe 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 missingThat user's grants (scribe-ctl access --user NAME)
Settings → Asset index warns about the background accountscribe-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 sandboxSee 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

sh
scribe-ctl --config scribe-cluster.toml down
scribe-ctl --config scribe-cluster.toml up

down 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.

Source docs/tutorials/05-start-web-interface.mdSnapshot 2b8dcccb