Libre Biotech

Setting Up a Read-Only, Docker-Served JBrowse 2 Instance with Remote Genome Storage via sshfs

A step-by-step playbook for publishing any reference genome in JBrowse 2 on a Linux server using only Docker, nginx, sshfs, samtools/htslib and the JBrowse CLI. The guide shows how to 1. install the minimal software stack, 2. mount a read-only genome directory from a remote server with sshfs, 3. convert and index FASTA + GFF3 files (BGZF + tabix), 4. create a JBrowse site, add assemblies and tracks via symlinks (no data duplication), 5. serve the site through an nginx-alpine container, and 6. troubleshoot common 404/403 and permission pitfalls. Every command is copy-paste-ready and uses clear placeholders (`<SITE>`, `<MOUNT>`, `<REMOTE_HOST>`, etc.) so the same recipe works on any Linux machine—laptop, HPC node, VM or cloud instance.

management
Version History
Version 1 Current
Effective: 2025-07-03

First version.

Procedure Steps (Version 1)

1 Install prerequisites (once per server)

# Ubuntu / Debian
sudo apt-get update
sudo apt-get install docker.io fuse sshfs

sudo usermod -aG docker $USER   # let yourself run Docker without sudo
newgrp docker                    # activate the new group in this shell

CentOS/RHELyum install docker, enable & start docker.service. Make sure /etc/fuse.conf contains user_allow_other (uncomment if needed).

2 Create the folders that will hold the site and mount

sudo mkdir -p <SITE>          # e.g. /opt/jbrowse-site
sudo mkdir -p <MOUNT>         # e.g. /mnt/genomes/h_armigera
sudo chown $USER:$USER <SITE> <MOUNT>

3 Mount the remote genome directory read‑only

sshfs \
  -o IdentitiesOnly=yes,reconnect,ro,allow_other,default_permissions \
  -o uid=101,gid=101 \            # nginx inside the container = UID 101
  <REMOTE_USER>@<REMOTE_HOST>:<REMOTE_DIR> \
  <MOUNT>

What the options do

option reason
ro keep the reference data safe
allow_other,default_permissions let other processes (nginx) read through FUSE while still honouring UNIX mode bits
uid=101,gid=101 inside the nginx container every file appears owned/readable by user nginx

If the mount ever drops, run the same command to reconnect.

4 Make sure your reference files are BGZF‑compressed and indexed

(do this on either the remote host or locally inside <MOUNT>)

cd <MOUNT>

# convert the genome FASTA to BGZF
gunzip -c MyGenome.fa.gz | bgzip -c > MyGenome.fa.bgz

# index the FASTA (creates .fai and .gzi)
samtools faidx MyGenome.fa.bgz

# sort + bgzip + index the GFF3 annotation
gunzip -c MyGenome.gff.gz | sort -k1,1 -k4,4n | \
  bgzip -c > MyGenome.sorted.gff.bgz
tabix -p gff MyGenome.sorted.gff.bgz

You should now have five files:

MyGenome.fa.bgz          (BGZF FASTA)
MyGenome.fa.bgz.fai
MyGenome.fa.bgz.gzi
MyGenome.sorted.gff.bgz  (BGZF GFF)
MyGenome.sorted.gff.bgz.tbi

(If you already received BGZF files and their indexes, just place them in the directory—no conversion needed.)

5 Create an empty JBrowse site

docker run --rm -v <SITE>:/data \
  quay.io/biocontainers/jbrowse2:3.5.1--h71b9176_0 \
  jbrowse create /data --force

The --force overwrites the blank template if you run it twice.

6 Add the assembly

docker run --rm \
  -v <SITE>:/data \
  -v <MOUNT>:/src:ro \
  quay.io/biocontainers/jbrowse2:3.5.1--h71b9176_0 \
  jbrowse add-assembly /src/MyGenome.fa.bgz \
    --type bgzipFasta \
    --name MyGenome \
    --displayName "My species (v1)" \
    --load symlink \
    --out /data

--load symlink writes a small link into <SITE> instead of copying the 100‑MB FASTA.

7 Add the gene‑model track

docker run --rm \
  -v <SITE>:/data \
  -v <MOUNT>:/src:ro \
  quay.io/biocontainers/jbrowse2:3.5.1--h71b9176_0 \
  jbrowse add-track /src/MyGenome.sorted.gff.bgz \
    --assemblyName MyGenome \
    --trackId gene_models \
    --name "Official genes" \
    --load symlink \
    --out /data

8 (Recommended) build the search index once

docker run --rm -v <SITE>:/data \
  quay.io/biocontainers/jbrowse2:3.5.1--h71b9176_0 \
  jbrowse text-index /data

This makes the search box jump to gene IDs quickly.

9 Launch nginx to serve the site

docker run -d --name jbrowse2 \
  -v <SITE>:/usr/share/nginx/html:ro \
  -v <MOUNT>:/src:ro \
  -p <PORT>:80 \
  nginx:alpine

Why the second -v? Your FASTA and GFF symlinks point into /src, so that path must exist inside the container.

10 View the browser

Open a browser to http://<SERVER_A_IP>:<PORT>/

  1. In the assembly dropdown choose MyGenomeOpen.
  2. Tick the “Official genes” track.

You’re browsing the remote genome with zero local duplication.

11 Add more tracks later (browser stays running)

# example: RNA‑seq coverage BigWig
docker run --rm \
  -v <SITE>:/data \
  -v <MOUNT>:/src:ro \
  quay.io/biocontainers/jbrowse2:3.5.1--h71b9176_0 \
  jbrowse add-track /src/MyRNAseq.bw \
    --assemblyName MyGenome \
    --trackId rna_cov \
    --name "RNA‑seq coverage" \
    --load symlink \
    --out /data

Just refresh the web page – no container restart is needed.