Skip to content

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.

A directory with:

FileWhat it is
compose.yaml, ophiolite-opsfrom deployment/package/ of the release you install
config.jsonwhere people reach Ophiolite, the project, its first administrator, the database
secrets/admin_tokenthe first administrator’s sign-in token: one line, 32–256 printable characters
secrets/db_passwordthe database password
.envOPHIOLITE_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.

Terminal window
docker compose up -d

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

Terminal window
./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 membership

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

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:

ClientKindSettings
workspaceconfidentialauthorization code with PKCE (S256); redirect URI <origin>/auth/callback; an audience mapper that puts workspace in access tokens
ophiolite-pythonpublicdevice 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"
}
Terminal window
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.

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.

Terminal window
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 person

Each 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):

Terminal window
./evaluation-identity alice bob carol dave # every name, old and new
docker compose down # keeps every volume
docker volume rm "${OPHIOLITE_NAME:-ophiolite}_identity-data"
docker compose up -d

On 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:

Terminal window
./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:

Terminal window
./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.key

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

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.

Terminal window
./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.

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:

Terminal window
./ophiolite-ops client-kit ophiolite-client-kit.tar

The 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:

Terminal window
python -m pip install 'ophiolite==<release>'

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.

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.

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