Deploying JBrowse Web
TL;DR: JBrowse Web is a static site (HTML/JS/CSS plus config.json), served
from any static host. Script the config generation so trackIds stay stable and
share links stay reproducible
across rebuilds.
JBrowse Web is a static web application, a folder of HTML, JS, and CSS plus
your config.json. Any static file host (Nginx, Apache, S3, GitHub Pages, a
Docker image behind an ingress) can serve it. Data files (BAM, BigWig, VCF, ...)
are read directly from wherever they live via HTTP range requests, so the only
server-side requirement is that your data host supports range requests and CORS
(see Serving data files).
The minimal deployment
-
Lay down the static app into a folder:
npx @jbrowse/cli create jbrowse-web -
Add an assembly and tracks, which writes
config.jsonfor you:cd jbrowse-web npx @jbrowse/cli add-assembly https://example.com/hg38.fa.gz --name hg38 npx @jbrowse/cli add-track https://example.com/sample.bam --trackId ngs-reads --name "NGS reads" --assemblyNames hg38 -
Serve the folder with any static host:
npx serve . # or copy it into your Nginx image
jbrowse add-track writes a JSON entry into the tracks array of
config.json, and a script can do the same (next section).
Docker/Kubernetes make sense where you are bundling JBrowse alongside other server-side code you operate. The static folder above drops into whatever image or bucket your pipeline already uses.
Generating config.json from a script
For repetitive data, generate config.json. A track is an object in the
tracks array, so any language that can write JSON works. For example, turning
a samplesheet into a config:
// samplesheet rows: { sample, assembly, bigwig }
import { readFileSync, writeFileSync } from 'fs'
const rows = JSON.parse(readFileSync('samplesheet.json', 'utf8'))
const tracks = rows.map(row => ({
type: 'QuantitativeTrack',
// a stable, deterministic trackId is the important part, see below
trackId: `rnaseq-${row.assembly}-${row.sample}`,
name: `RNA-seq ${row.sample}`,
assemblyNames: [row.assembly],
adapter: { type: 'BigWigAdapter', uri: row.bigwig },
}))
const config = JSON.parse(readFileSync('config.base.json', 'utf8'))
writeFileSync('config.json', JSON.stringify({ ...config, tracks }, null, 2))
config.base.json holds everything that isn't per-sample (your assemblies
array and any global settings); the script merges in the generated tracks.
For a set of signals that belong together (e.g. an RNA-seq timecourse in
triplicate), emit a single
MultiQuantitativeTrack whose
subadapters array is built from the same rows. See that guide for a templated
subadapters example.
Tools like Jsonnet fit here too, if you prefer a
templating language to a script; the output is still ordinary config.json.
Keep trackIds stable for reproducible links
When a shared session is restored, JBrowse looks up each track by its trackId.
If your pipeline regenerates config.json with different trackIds each
build (an ID embedding a timestamp or random suffix), previously shared links
fail to restore those tracks. Derive each trackId deterministically from
stable inputs, as in the script above, where the ID comes from the assembly and
sample name.
Changing or deleting a track's ID breaks any saved session that references it, and the whole session fails to load rather than just that track.
Cache-busting in index.html
Assemblies, tracks, plugins, and the default session are all config.json, so
all of it can be scripted. The
cache-buster is a one-line
<script> in index.html, since config.json is fetched before it can
configure anything.