Install
Ophiolite ships as one server image, ophiolite-server:<release>. It holds the project service,
the worker, the web gateway, the Workspace and a small web server. With it you run PostgreSQL 16;
the compose file in deployment/package/ starts both. The image runs on x86_64 and ARM64
(aarch64) Linux: one release name covers both, and Docker or Podman pulls the image for the
computer’s processor.
To look at Ophiolite on one computer before you install it for others, see Try it on your own computer.
What you prepare
Section titled “What you prepare”A directory with:
| File | What it is |
|---|---|
compose.yaml, ophiolite-ops | from deployment/package/ of the release you install |
config.json | where people reach Ophiolite, the project, its first administrator, the database |
secrets/admin_token | the first administrator’s sign-in token: one line, 32–256 printable characters |
secrets/db_password | the database password |
.env | OPHIOLITE_IMAGE=<registry>/ophiolite-server:<release> (once that release is published: ghcr.io/ophiolite-labs/ophiolite-server:<release>, pulled without a login); optionally OPHIOLITE_PORT, and OPHIOLITE_NAME (the compose project name, ophiolite by default: give each deployment on one host its own) |
config.json (every key is checked; unknown keys are refused, and secrets never go in it):
{ "schema": "ophiolite.deployment-config/1", "origin": "https://ophiolite.example.org", "tls": "external", "project": {"id": "geoscience", "name": "Geoscience"}, "administrator": {"id": "operator", "name": "Operator"}, "database": {"host": "postgres", "name": "ophiolite", "user": "ophiolite"}, "limits": {"upload_bytes": 33554432, "scientific_reads": 2, "read_wait_ms": 10000, "file_bytes": 4294967296, "upload_total_bytes": 17179869184, "retained_bytes": 17179869184, "grid_cells": 3000000}}upload_bytes is the largest request, at most 32 MiB (33554432): a file up to that size goes in
one request, a larger one in parts of that size. Results can be up to 32 MiB, so importing a portable
bundle into another deployment needs that limit there; a lower limit is stated in the refusal a person
sees. file_bytes is the largest original (4 GiB here; at most 64 GiB): uploads need free disk of 2.5
times their size, and /healthz answers 503 when the disk cannot hold 2.5 times file_bytes. The
other limits and their defaults are in Large files.
public_site (optional, false by default) shows the Ophiolite public site at / to visitors who
are not signed in; signed-in people still land in the Workspace, and sign-in is at /sign-in. It
suits an evaluation; a production deployment usually leaves it off.
capture (optional, off by default) says whether this deployment may learn from how people work.
by-consent needs a tier: on your own machine (local-evaluation), your own servers (self-managed) or
an instance we run for you alone (managed-dedicated) as well as on a public demonstration with its
demonstration section; each person then decides in their account. tier (optional) names the kind of
deployment: public-demonstration, local-evaluation, self-managed or managed-dedicated;
shared-cloud is not available yet and is refused. The configuration check names what is wrong:
'tier' is a required property for by-consent without a tier,
'demonstration' is a required property for a public demonstration without
one, and tier: 'shared-cloud' should not be valid for the shared cloud. Only config.json sets these
two: the same variables in the container’s environment are ignored.
Learning from how people work is off by default. It is on only when the operator of this deployment sets it and the person agrees. An export is a deliberate download by someone authorised, never an automatic upload.
An exported folder is yours wherever you download it; sending it anywhere is your decision. The
instance’s administrator sees the setting, the consent text version and when capture was last
switched on or off in the deployment/health answer.
tls says who terminates HTTPS: external (your load balancer or ingress, in front of port
8080), caddy (the image obtains a certificate for the origin’s host name) or none (only for
an evaluation on http://127.0.0.1).
The image runs as user 10001, so the two secret files must be readable by that user. Keep a copy of the administrator token somewhere safe: Ophiolite stores only its hash.
The same instruction holds for Docker (rootful or rootless) and Podman: make the files 0644 and keep
them private through the directory, chmod 0700 secrets && chmod 0644 secrets/*. The image’s user has no fixed group, so a file
readable only by a group (chgrp, mode 0640) is not enough. The server’s log says
PermissionError: /run/secrets/db_password when the files are not readable.
Known issue — Podman 4.9 (the Ubuntu 24.04 package). While another container joins or leaves the
deployment’s network (an ophiolite-ops command, for example), the other containers can briefly fail to
resolve each other’s names. Ophiolite retries its database connection for a few seconds; if the database is
still unreachable, a request is answered 503 with Retry-After and “retry the request”, never a failure
inside the service. The Python SDK and the command line retry such an answer for reads and for publications
that are safe to repeat; retry other changes yourself. A newer Podman
(netavark and aardvark-dns) avoids the pause.
docker compose up -dOn the first start the server checks the configuration, waits for the database, applies the
schema and creates the project and the administrator from secrets/admin_token in one step.
Open the origin in a browser, choose Use a sign-in token and paste that token. Later starts do not need the token file.
If the server does not come up, docker compose logs server names the reason: a configuration
field, the database, a migration, or a release it cannot start from (see Upgrade).
Time to a first sign-in. The release qualification installs a fresh deployment this way, and
the administrator token must sign in 3 times in a row, a second apart, within 300 seconds of
docker compose up -d; a later sign-in fails the qualification. The same timer runs on to the
first passing lane: people added, a first import and calculation published, and every one of them
read back exactly. That too must finish within 300 seconds of docker compose up -d. Both times are
recorded with each qualification, as install_to_session_s and install_to_lane_s.
With outbound traffic blocked. Ophiolite needs no connection out of your network: no Ophiolite
account, subscription or licence server. To run it that way, copy
examples/closed-network/compose.override.yaml beside compose.yaml as compose.override.yaml; the
deployment’s network becomes internal, so its containers reach each other and nothing outside it. The
release qualification installs a deployment this way, after checking that the same probe reaches a
test endpoint from an ordinary network and cannot from the deployment’s: the administrator signs in,
shares a log with a member who reads it, and other people are refused. This shows the install works
with egress blocked; it does not show that no connection is ever attempted. An internal network may
not publish ports, so reach the server through a proxy or client on that network.
People
Section titled “People”./ophiolite-ops member bob "Bob" --no-compute # writes ./tokens/bob.token for Bob./ophiolite-ops member carol "Carol" --role viewer./ophiolite-ops user dana "Dana" # an account without project membershipGive each person their token file: its contents are their sign-in token (Use a sign-in token on the sign-in screen; project access keys cannot be used to sign in). Sharing, groups and stages then happen in the Workspace.
Sign-in through your identity provider
Section titled “Sign-in through your identity provider”Tokens are enough for the Workspace. Python applications (ophiolite login), connectors and
invitations need sign-in through an OpenID Connect identity provider (for example Keycloak or
your organisation’s provider). Register two clients there:
| Client | Kind | Settings |
|---|---|---|
workspace | confidential | authorization code with PKCE (S256); redirect URI <origin>/auth/callback; an audience mapper that puts workspace in access tokens |
ophiolite-python | public | device authorization grant only, consent required; the same workspace audience mapper |
Then give Ophiolite a private file (mode 0600) and restart it:
{ "issuer": "https://id.example.org/realms/geoscience", "client_id": "workspace", "client_secret": "<the workspace client secret>", "application_client_id": "ophiolite-python", "introspection": true, "account_url": "https://id.example.org/realms/geoscience/account"}chmod 600 sign-in.json./ophiolite-ops configure sign-in sign-in.json # validated, kept in the state volume, server restarted./ophiolite-ops link-identity alice <subject> # once per person: the provider's `sub` for Alice./ophiolite-ops project-creator alice # Alice may create projects (--revoke takes it back)Sign-in lasts seven days without use and thirty days in all, for sign-in tokens and the identity
provider alike; ./evaluation-identity gives the evaluation provider the same numbers, and
deployment/workspace-identity/apply_policy.py --evaluation <directory> applies them to one prepared
before 2026.10.2 (then run configure sign-in again).
Ophiolite never matches people by email or name: each provider account is linked to one
Ophiolite user explicitly (--disable removes the link and ends its sessions). The issuer must be
https://; "allow_loopback_http": true admits an http://127.0.0.1 provider for evaluation only.
./ophiolite-ops configure sign-in --remove returns to token-only sign-in.
Invitations found by address are off unless you turn them on in the same document.
With "invitation_discovery": {"vouch": "issuer"} a person who signs in sees the pending invitations
for the address your provider marks verified, and may accept or decline them without the emailed
link; {"vouch": "domains", "domains": ["example.org"], "organizations": ["<organization id>"]}
limits that to those address domains and organizations; {"vouch": "off"}, or leaving the key out,
keeps the emailed link as the only way in. Turn it on only for a provider that proves people control
the addresses it marks verified. Accepting asks the provider for a sign-in no older than
"invitation_discovery_max_age" seconds (30 to 3600; 300 when absent). configure sign-in refuses a
value the server would refuse. Sign-ins begun before the upgrade find nothing until the next sign-in.
Evaluation provider on the same host
Section titled “Evaluation provider on the same host”To try browser sign-in and Python applications before your organisation’s provider is
connected, deployment/package/examples/evaluation-identity/ runs Keycloak beside the server on
http://127.0.0.1:38180. It needs an origin on http://127.0.0.1 ("tls": "none"), is not for
production, and keeps passwords in a file. A second evaluation deployment on the same host puts
OPHIOLITE_IDENTITY_PORT=38181 (any free port) and its own OPHIOLITE_NAME in its .env before
running ./evaluation-identity.
cp <release>/deployment/package/examples/evaluation-identity/* .echo 'COMPOSE_FILE=compose.yaml:compose.identity.yaml' >> .env./evaluation-identity alice bob carol # Ophiolite user ids; writes ./identity (owner-only)docker compose up -d./ophiolite-ops configure sign-in identity/sign-in.json./ophiolite-ops link-identity alice <subject printed for alice> # and so on for each personEach person signs in with the name and the password listed in identity/passwords.txt. The
provider keeps its data in a volume, so sign-ins survive restarts, backups and upgrades. It reads
the people only into an empty volume; to add people later (this signs everyone out once):
./evaluation-identity alice bob carol dave # every name, old and newdocker compose down # keeps every volumedocker volume rm "${OPHIOLITE_NAME:-ophiolite}_identity-data"docker compose up -dOn rootless Podman the server shares the provider’s network, and recreating the provider while
the server container exists fails. ./ophiolite-ops backup, export, import and upgrade never
recreate it (from 2026.10.31). A plain docker compose up -d after you change a compose file or
.env, or a restore, can still meet it: when one reports that it cannot start the server, run
docker compose down and then docker compose up -d (volumes are kept).
Data sources and connection administration
Section titled “Data sources and connection administration”Approved external sources (see Managed sources) and the PostgreSQL targets that project administrators may connect to (see Connection administration) are operator documents too:
./ophiolite-ops configure sources sources.json # {"connections": [...]}./ophiolite-ops configure connection-admin targets.json # {"targets": {"wells": {"host": ..., "port": 5432, # "dbname": ..., "schema": ..., "table": ..., "sslmode": "verify-full"}}}A database on another host must use verified TLS (verify-full or verify-ca);
ophiolite-ops refuses any target the server would refuse, before keeping it. When the
database’s certificate comes from your organisation’s own certificate authority, keep that
authority’s certificate first and name it in the target:
./ophiolite-ops connection-ca corp corp-ca.pem # kept as /state/ca/corp.pem./ophiolite-ops configure connection-admin targets.json # ..."sslmode": "verify-full", "sslrootcert": "/state/ca/corp.pem"CA certificates travel in backups; ./ophiolite-ops connection-ca corp --remove forgets one.
At its first start the server sets connection administration up by itself: a private key
(/state/connection.key, owner-only) that encrypts the database credentials people enter, and an
administration document with no approved database yet. Nothing else is needed before project
administrators open Add a source; the screen says that no database is approved until you
configure one. The first start prints:
ophiolite: Connection key created. Keep a copy apart from backups: ./ophiolite-ops connection-key export connection.keyBackups never contain the key: keep it with ./ophiolite-ops connection-key export connection.key
and bring it into a restored deployment with ./ophiolite-ops connection-key import connection.key
(see Recovery). The server never replaces a key or a document that exists, and
configure connection-admin keeps the key the document names. ./ophiolite-ops configure connection-admin --remove
removes every approved database; the key and the document stay, so stored credentials remain
readable once a database is approved again. A key file with the wrong permissions or unreadable
bytes no longer stops the server: managed database reads stay off and Add a source names the
problem. The source list travels in backups; the sign-in document and the connection targets do
not, so configure them again after a restore into a new directory.
Databases added in the browser
Section titled “Databases added in the browser”A fresh install starts in organisation mode with the self-hosted profile: organisation owners, and the people they
let add databases, add a database on a private address in the browser, and nobody needs to edit a file (see
Connection administration). A deployment
upgraded from an earlier release keeps operator mode until you change it, and so does any deployment whose people
had already stored sign-ins when the server first set connection administration up.
./ophiolite-ops configure destination-rule rule.json # {"mode": "organisation", "profile": "self-hosted", # "allow": ["db.example.invalid", "203.0.113.0/24"], "deny": ["10.9.0.0/16"]}profile is self-hosted (private ranges allowed) or public (global addresses only unless allowed). Link-local and
cloud metadata addresses and the deployment’s own databases are always refused; deny comes before allow. The rule is
checked by the server before it is kept, and the server restarts. Databases you approve in configure connection-admin
are not affected by the rule.
To lock down, configure {"mode": "operator"}. Databases already added in the browser are kept but stop reading until
you adopt them one by one with ./ophiolite-ops connection-admin adopt <id> (the id is under Technical details on the
organisation’s Connections page), or turn organisation mode on again. There is no --remove for the rule: an absent
rule would mean operator mode, so the command asks you to say so.
Software for people’s computers
Section titled “Software for people’s computers”The OpendTect route and the QGIS plugin run on a person’s own computer and need the Connectors package and the plugin of this exact release. Write them from the image and hand them to the people who use those applications:
./ophiolite-ops client-kit ophiolite-client-kit.tarThe tar holds the Connectors wheel (pip install ophiolite_connectors-*.whl), the QGIS plugin
zip, the example tools the guides name (examples-procedures.zip, which unpacks to
examples/procedures/<name>) and client-kit.json with the source commit and each file’s SHA-256.
The Python SDK of the same release comes from PyPI once that release is published:
python -m pip install 'ophiolite==<release>'What was qualified
Section titled “What was qualified”The Integration CI job image builds the image from the pinned components and runs, on a clean
Ubuntu 24.04 host: install to a signed-in session, members, the pilot journey, the access and
tampering checks of the adversarial qualification, a publication interrupted by a kill, backup
and restore, the release gate, a project move and an offline exit.
Sign-in through the evaluation provider, link-identity and a Python login with Workspace
approval were checked by hand with rootless Podman on 28 September 2026; they are not part of the
CI job. Your organisation’s provider is not qualified by Ophiolite: check it with one person first.
Both processors
Section titled “Both processors”Each release is built twice from the same pinned components, once on x86_64 and once on ARM64 (Ubuntu 24.04 on both), and each image is checked on its own processor. On ARM64 the same install, members, pilot, adversarial, interruption, backup and restore, release gate, project move and offline exit checks run, with the conformance suite. A backup made on one processor is restored on the other, in both directions, and reads back the same assets with the same original bytes and provenance. The release name is published only after both images pass and the two are checked to be the images that were qualified.
Not run on ARM64: the build with the address sanitizer, and the checks that need the hosted test accounts; those run on x86_64 only.
Numbers on the two processors
Section titled “Numbers on the two processors”- The calculation engine, in the build. The engine is compiled without fused multiply-add on both processors, and one table of fixed inputs is computed on each. Most results are the same to the last bit; those that use the system maths library (exponentials, logarithms, sines) agree within a bound stated for each result. One result differs by design: the mean of an interval summary is accumulated in extended precision, which keeps 64 bits on x86_64 and 113 on ARM64, so for inputs that cancel badly the two processors can return different means. Each processor’s own value is recorded and checked.
- The shale-volume calculation, through the installed image. The same well log is uploaded to an installation on each processor and shale volume is computed by every method. The linear and Steiber methods and the percentile picks give the same numbers to the last bit; the Larionov and Clavier methods, which use the system maths library, agree within a bound stated for each sample.
