Saphan StudioDocs
Deployment

Run Studio as containers

Run the Studio server — gateway, launcher, console server and CouchDB — as container images on volumes: first start, upgrade, roll back, and rotate the CouchDB administrator.

This page is for the person who runs the Studio server and wants it as container images on volumes rather than as native services. A container image install is the install route of every edition, including Solo; the native install (saphan service install) is the other route, available in Team and Enterprise (editions). Docker with the compose plugin, or Podman, is required on the host.

What you get

One compose project, saphan-studio:

servicewhat it runsrestarts on boot
gatewaythe gateway. Published: its mutual-TLS listener on :8443, which refuses any caller without a certificate your client CA signed. Not published: the console socket (127.0.0.1:8788, inside the container)yes
launcherthe launcher, which starts the models, terminals and runs the gateway asks foryes
serverthe console server: fleet supervisor and projection serviceyes
couchdbthe channel store, on 127.0.0.1:5984 onlyyes
studio-init, studio-config, studio-declare, studio-migrateone-shot jobs: set volume ownership and install the credentials file, record the configuration, declare the workspace on a first start, run the store migrationno

All three Saphan services run one image, selected by digest. docker compose ps shows server as unhealthy whenever it is not serving — no workspace declared, a workspace it cannot open, or an installation whose owner is not enrolled yet (step 5 below).

Where your data lives. The root filesystem of every Saphan service is read-only; everything the product writes is on a volume:

volumeholds
workspacethe store and the record
homethe configuration and the trust material
launcherthe launcher's socket directory
couchdb-data, couchdb-configCouchDB's data and its local configuration

docker compose down keeps them. ⛔ docker compose down -v deletes them — never run it on a deployment you mean to keep.

1. First start

⚠ Saphan Studio Solo is in final testing. The Studio server image this page names is published from the release day; until then the image is not available.

  1. Put the deployment files on the host.

    saphan infra write --dir /opt/saphan/docker
  2. Place the secrets. Create /opt/saphan/docker/secrets/ (mode 0700) with the files below, each mode 0600 and readable by uid 10001 if your host enforces that:

    filewhat it is
    credentials.jsonthe credentials file, with the CouchDB administrator entry
    gateway-tls.crt, gateway-tls.keythe gateway's TLS certificate and key
    client-ca.pemthe CA whose client certificates the gateway accepts
    healthcheck-client.crt, healthcheck-client.keya client certificate that CA signed (extended key usage clientAuth), used only by the gateway's own healthcheck

    To mint the healthcheck certificate from the CA's key:

    openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -subj "/CN=studio-healthcheck" \
      -keyout healthcheck-client.key -out healthcheck-client.csr
    openssl x509 -req -in healthcheck-client.csr -CA <client CA certificate> -CAkey <client CA key> \
      -CAcreateserial -days 825 -extfile <(printf 'extendedKeyUsage=clientAuth') -out healthcheck-client.crt

    The secrets are mounted read-only at /run/secrets/ and never copied into an image. To keep them elsewhere, set SAPHAN_STUDIO_SECRETS_DIR to that directory.

  3. Have two image digests ready: the Studio server image and CouchDB. ⚠ A tag is refused: only <repository>@sha256:<64 hex digits> is accepted, because a tag can be moved under a running deployment. For CouchDB, read the digest from the registry you pull it from, for example docker buildx imagetools inspect couchdb:3.4.2.

  4. Start it.

    saphan infra studio up --dir /opt/saphan/docker \
      --image <registry>/saphan-studio-server@sha256:<digest> \
      --couchdb-image couchdb@sha256:<digest>

    This records both digests in /opt/saphan/docker/studio-image.env and starts the project. CouchDB's administrator and cookie secret come from your credentials file and are never container environment variables, so docker inspect does not print them. After a reboot the four long-lived services come back by themselves; to start the project again on the recorded digests, run saphan infra studio up --dir /opt/saphan/docker with no image flags.

    ⚠ On a first start up ends with server not healthy. That is expected: the server also needs the owner enrolled, which is the next step. gateway, launcher and couchdb come up healthy.

  5. Enrol the owner. The server will not start until this installation's actor is enrolled in the signed registry, and that entry is signed with your root key, which is never in the stack. Run this once, on the machine that holds ~/.saphan/trust/root.key and root.key.pub, with Docker reachable from it (DOCKER_HOST=ssh://<host> reaches another machine). It runs a one-off container of the server image against the project's workspace and home volumes; the root key is read from standard input, so it appears in no argument, no environment variable and no image layer, and inside the container it exists only on a tmpfs until the command ends. The first line sets the actor's name.

    ACTOR="$(id -un)"
    IMAGE="$(sed -n 's/^SAPHAN_STUDIO_IMAGE=//p' /opt/saphan/docker/studio-image.env)"
    printf '%s\n%s\n' "$(base64 -w0 ~/.saphan/trust/root.key)" "$(base64 -w0 ~/.saphan/trust/root.key.pub)" | docker run --rm -i --read-only --tmpfs /tmp -e HOME=/home/saphan -v saphan-studio_workspace:/srv/saphan/workspace -v saphan-studio_home:/home/saphan/.saphan "$IMAGE" sh -c 'set -e; umask 077; read -r k; read -r p; mkdir -p /home/saphan/.saphan/trust; printf %s "$k" | base64 -d > /tmp/root.key; printf %s "$p" | base64 -d > /home/saphan/.saphan/trust/root.key.pub; kid=$(saphan actor init --workspace /srv/saphan/workspace | sed -n "s/^ *key-id: //p"); test -n "$kid"; saphan identity enroll --workspace /srv/saphan/workspace --kind actor --subject "$1" --key-id "$kid" --root-key /tmp/root.key; rm -f /tmp/root.key' enrol "$ACTOR"

    It prints identity enroll: actor "<name>" enrolled in the signed identity store. Then start the project again; up returns once server is healthy:

    saphan infra studio up --dir /opt/saphan/docker
  6. Check it. docker compose ps shows gateway as healthy. From any machine, a request with no client certificate is refused, and one with a certificate your client CA signed is answered:

    curl --cacert <gateway CA> https://<host>:8443/
    curl --cacert <gateway CA> --cert <client.crt> --key <client.key> https://<host>:8443/

    The published port is the gateway's machine-to-machine door. The console socket asks for no client certificate, which is why it is not published: it is reachable only inside the gateway container's network namespace, for example from a TLS proxy container of your own started with network_mode: service:gateway.

2. Upgrade

  1. Back up first. Copy the workspace and couchdb-data volumes. If going back turns out to be unsafe, that copy is the only way back.

  2. Run the upgrade.

    saphan infra studio upgrade --dir /opt/saphan/docker \
      --image <registry>/saphan-studio-server@sha256:<new digest>
  3. Read what it did. In this order, it:

    1. records the new digest as current and the old one as previous in studio-image.env;
    2. runs the store migration with the new image on the same volumes, while the old containers keep serving;
    3. runs the rollback probe: a one-off container of the previous image tries to open the migrated store. The answer is recorded as SAPHAN_STUDIO_ROLLBACK=safe or SAPHAN_STUDIO_ROLLBACK=UNSAFE; a probe that could not run counts as UNSAFE;
    4. moves the containers to the new digest and waits for them to be healthy.

    If the migration fails, no running service has moved. If the new containers do not become healthy, the refusal names the next act, saphan infra studio rollback; nothing is undone for you.

  4. If it printed ROLLBACK IS UNSAFE, the services are on the new digest and a rollback will be refused. Going back needs a restore of the workspace and couchdb-data volumes from the backup of step 1.

⚠ The probe reads the store on the workspace volume only. It does not check CouchDB's databases; if an upgrade changes those, the backup of step 1 is the only guard.

3. Roll back

  1. grep SAPHAN_STUDIO_ROLLBACK /opt/saphan/docker/studio-image.env must not print UNSAFE. If it does, stop: the way back is the restore above.

  2. Roll back:

    saphan infra studio rollback --dir /opt/saphan/docker

    This puts the record back on the previous digest and starts the project on it, on the same volumes. A second rollback goes forward again, without a safety verdict.

⚠ A safe verdict means the previous image could open the store at the time of the upgrade, not that data written since then is understood by it.

4. Rotate the CouchDB administrator

  1. Edit the SAPHAN_COUCHDB_CREDENTIALS entry (<login>:<password>) in /opt/saphan/docker/secrets/credentials.json. Keep it mode 0600; do not touch other entries.

  2. Start the project again, saying the administrator changed:

    saphan infra studio up --dir /opt/saphan/docker --rotated-credentials

    CouchDB is recreated with the new administrator, and the gateway, the server and the migration pick it up without a restart.

  3. Check: docker compose ps shows couchdb and gateway healthy.

Settings the compose file owns

On every up and upgrade the project writes these configuration keys; a value you set for one of them with saphan config set is put back at the next start. Every other key is left alone.

keyvalueto change it
channel.base_url, evidence.base_urlhttp://couchdb:5984run saphan infra studio up once with SAPHAN_STUDIO_CHANNEL_URL=<http(s) URL> in its environment; the address is recorded in studio-image.env and kept by later starts. An address with a login or password in it is refused
gateway.mtls_listen0.0.0.0:8443not changeable: it is the port the compose file publishes
credentials.filethe credentials copy on the home volumenot changeable
channel.credential, evidence.credentialSAPHAN_COUCHDB_CREDENTIALSnot changeable

When something refuses

you seeit meansdo
the compose project mounts secrets from files that are not therea secret file is missingplace the files named in step 2
the image reference is not a digestyou passed a tagpass the @sha256: digest
the rollback is recorded as UNSAFEthe previous image cannot read the migrated storerestore the volumes from the pre-upgrade backup
already records … as the running imageup with image flags on a started deploymentuse upgrade, or up with no flags
server is unhealthy after a first startdocker compose logs server names why: no workspace means the declare step did not run; registry: runtime registry not provisioned means the owner is not enrolledstep 5
permission denied on a CouchDB paththe volume was not initialised for CouchDBrecreate the project; do not bind-mount over couchdb-data

⚠ Where to get the Studio server image is not described on this page yet: it needs the registry and digest your Saphan distribution provides.

On this page