JBrowse 2
Overview
This image runs JBrowse 2 v4.3.0, the open source genome browser from the Generic Model Organism Database (GMOD) project, on Ubuntu 24.04 LTS. JBrowse 2 is a fast, modern browser for genomic data. It renders reference sequences, gene and feature annotation, read alignments, variants, quantitative data and whole-genome synteny. It streams directly from standard indexed bioinformatics formats such as BAM, CRAM, VCF, bigWig and tabix-indexed GFF3 and BED.
This image ships with a complete demo genome already configured and rendering, so the browser is useful the moment the VM boots. It also includes an authenticated admin surface: the JBrowse admin server, reached through a password-protected nginx vhost on port 8080. You add assemblies and tracks in the browser, and JBrowse writes the changes back to its own configuration.
Indexed genomic formats are read with HTTP range requests, and a host that does not answer them correctly gives you an empty browser with no error message. nginx serves the data directory with byte-range support and with compression disabled on the already-compressed formats. The image is validated at build time against real 206 Partial Content responses.
What you get:
- JBrowse 2 v4.3.0, the official pre-built JBrowse Web release, pinned by sha256 and served from
/var/www/jbrowse - A ready-to-use demo genome: E. coli K-12 MG1655 (NCBI RefSeq
GCF_000005845.2,NC_000913.3) with four real tracks - reference sequence, NCBI RefSeq gene annotation, Illumina MiSeq read alignments from ENA runSRR1770413, and variants called from those alignments - A trix name-search index, so the location box resolves gene names and locus tags such as
thrAorb0002 - nginx on port
80serving the browser and the data with byte-range support, plus an unauthenticated/healthzendpoint for Azure Load Balancer probes - A second nginx vhost on port
8080with HTTP Basic authentication and a per-VM credential, in front of the JBrowse admin server - The
@jbrowse/clicommand line tool installed globally, forjbrowse add-assembly,add-trackandtext-index - Per-VM credentials written to
/root/jbrowse-credentials.txton first boot, with nothing baked into the image - An Ubuntu 24.04 LTS base, patched at build time, with unattended security upgrades enabled, and Node.js 22 LTS from NodeSource so the JavaScript runtime keeps receiving updates
- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is the recommended size. With every service running and the demo genome loaded, the appliance uses well under 600 MB of RAM, because JBrowse renders in the visitor's browser and the server only streams byte ranges. The image occupies roughly 3 GB on the OS disk, of which about 81 MB is JBrowse and its demo dataset. The default 30 GB OS disk leaves plenty of room for your own genomes.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network, 80/tcp for the genome browser, and 8080/tcp from your management network only, for the admin surface. The appliance serves plain HTTP. For production or shared access, terminate TLS in front of it with your own domain, using a reverse proxy or Azure Application Gateway.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for JBrowse 2 by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80). Then select Review + create and Create.
Once the VM is up, add an inbound rule for 8080/tcp, scoped to your own address range.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:jbrowse:default:latest
az vm create \
--resource-group <your-rg> \
--name jbrowse \
--image sac1647283583153:jbrowse:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name jbrowse --port 80 --priority 1010
az vm open-port --resource-group <your-rg> --name jbrowse --port 8080 --priority 1020Step 3 - Connect to your VM
Connect to the VM over SSH as azureuser:
ssh azureuser@<vm-public-ip>Step 4 - Confirm the services are running
Four systemd units make up the appliance:
jbrowse-firstboot.servicecreates this VM's own admin credential before anything is published.nginx.serviceserves the browser on port80and the authenticated admin vhost on port8080.jbrowse-admin.serviceruns the JBrowse admin server on loopback.jbrowse-postboot.servicepublishes the credentials once the admin server has created its key.
Confirm that all four are active:
systemctl is-active nginx.service jbrowse-admin.service jbrowse-firstboot.service jbrowse-postboot.serviceEach unit reports active:
active active active activeCheck what is listening. nginx holds 80 and 8080. The admin server holds 9090. The process binds all interfaces, but systemd's address filter confines it to loopback, as Step 8 shows.
sudo ss -lntp | grep -E ':80 |:8080 |:9090 'LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5533,fd=5),("nginx",pid=5532,fd=5)) LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("nginx",pid=5533,fd=7),("nginx",pid=5532,fd=7)) LISTEN 0 511 [::]:80 [::]:* users:(("nginx",pid=5533,fd=6),("nginx",pid=5532,fd=6)) LISTEN 0 511 *:9090 *:* users:(("node",pid=5531,fd=21)) LISTEN 0 511 [::]:8080 [::]:* users:(("nginx",pid=5533,fd=8),("nginx",pid=5532,fd=8))Step 5 - Read your per-VM credentials
The genome browser has no login. It is a read-only viewer of the data you choose to publish, and JBrowse has no account model. Everything that can change the appliance sits behind the admin surface on port 8080. Its password is generated on this VM at first boot and written to a root-only file. Print the file with the secrets masked:
sudo sed -e 's/^\(JBROWSE_ADMIN_PASSWORD=\).*/\1****************/' -e 's/^\(JBROWSE_ADMIN_KEY=\).*/\1**********/' /root/jbrowse-credentials.txt# JBrowse 2 appliance by sac - per-VM access details (generated on first boot)
#
# The genome browser itself is a READ-ONLY viewer and is published without a login: anyone
# who can reach port 80 can look at the genomes you publish. Everything that can CHANGE the
# appliance - adding assemblies and tracks, editing config.json - lives behind the
# authenticated admin surface on port 8080 with the credentials below.
#
# The admin key is re-minted by jbrowse-admin.service on every start; this file is kept in
# step automatically. To read the live value:
sudo cat /var/lib/jbrowse/admin-key JBROWSE_URL=http://<vm-public-ip>/ JBROWSE_ADMIN_URL=http://<vm-public-ip>:8080/ JBROWSE_ADMIN_USER=jbadmin JBROWSE_ADMIN_PASSWORD=<set-at-first-boot> JBROWSE_ADMIN_KEY=**********Drop the sed filter to read the real values. The file is mode 0600, owned by root, and unique to this VM. The captured image contains no password, no admin key and no htpasswd entry.
Step 6 - Open the genome browser
Browse to http://<vm-public-ip>/. JBrowse opens on the bundled genome with the annotation, alignment and variant tracks already loaded. You need no configuration and no file uploads.
The first view shows named E. coli genes such as thrA, dnaK and nhaA from the NCBI RefSeq annotation, a full Illumina read pileup with its coverage histogram, and a called variant on the variants track.
To confirm the same thing from the command line, check the health endpoint and the configuration JBrowse loads:
curl -s -o /dev/null -w 'healthz: %{http_code}\n' http://127.0.0.1/healthz
curl -s http://127.0.0.1/config.json | python3 -c "import json,sys; c=json.load(sys.stdin); print('assembly:', c['assemblies'][0]['displayName']); [print('track: ', t['name'], ' [', t['trackId'], ']') for t in c['tracks']]; print('default view:', c['defaultSession']['views'][0]['init']['loc'])"healthz: 200 assembly: E. coli K-12 MG1655 (NCBI RefSeq GCF_000005845.2) track: NCBI RefSeq genes [ ecoli_refseq_genes ] track: Illumina MiSeq alignments (SRR1770413) [ ecoli_srr1770413_alignments ] track: Variants called from SRR1770413 [ ecoli_srr1770413_variants ] default view: NC_000913.3:1..25000Step 7 - Explore the demo genome
Type a region into the location box at the top of the view, for example NC_000913.3:3000..5500. JBrowse redraws at that scale. At a few kilobases the alignment track resolves into individual reads, colour-coded where a read disagrees with the reference, with the coverage histogram above it.
Zoom further, for example to NC_000913.3:4200..4360, to reach base resolution. Every read's insertions and mismatched bases are drawn.
The trix name-search index also lets the location box accept gene names and locus tags. Type thrA and JBrowse jumps to that gene. The underlying data is real, and you can query it directly on the VM:
sudo tabix /var/www/jbrowse/data/ecoli/ecoli_K12_MG1655.sorted.gff.gz NC_000913.3:1-6000 | awk -F'\t' '$3=="gene"' | cut -f1,3,4,5 | head -5 sudo samtools idxstats /var/www/jbrowse/data/ecoli/SRR1770413.sorted.bam | head -2 echo -n "variants called: ";
sudo bcftools view -H /var/www/jbrowse/data/ecoli/SRR1770413.variants.vcf.gz | wc -lNC_000913.3 gene 190 255 NC_000913.3 gene 337 2799 NC_000913.3 gene 2801 3733 NC_000913.3 gene 3734 5020 NC_000913.3 gene 5234 5530 NC_000913.3 4641652 212220 1738 * 0 0 86322 variants called: 349The output shows 212,220 reads mapped across the 4,641,652 bp chromosome and 349 variant calls, all generated from public archive data at build time.
Step 8 - Check byte ranges, admin authentication and loopback confinement
JBrowse reads BAM, CRAM, bigWig and tabix files by fetching small byte ranges instead of downloading whole files. Without range support the browser silently renders nothing. Confirm the server answers with 206 Partial Content:
curl -sI -H 'Range: bytes=0-1023' http://127.0.0.1/data/ecoli/SRR1770413.sorted.bam | grep -iE '^(HTTP|Content-Range|Content-Length)'HTTP/1.1 206 Partial Content Content-Length: 1024 Content-Range: bytes 0-1023/48800773The admin vhost accepts only this VM's credential. It refuses anonymous requests and common default passwords:
ADMIN_PW=$(sudo sed -n 's/^JBROWSE_ADMIN_PASSWORD=//p' /root/jbrowse-credentials.txt)
curl -s -o /dev/null -w 'no credentials : %{http_code}\n' http://127.0.0.1:8080/
curl -s -o /dev/null -w 'admin:password : %{http_code}\n' -u admin:password http://127.0.0.1:8080/
curl -s -o /dev/null -w 'this VM credential : %{http_code}\n' -u "jbadmin:${ADMIN_PW}" http://127.0.0.1:8080/no credentials : 401 admin:password : 401 this VM credential : 200The admin server behind that vhost is a small Node service. It binds all interfaces and has no option to bind loopback only, so the image confines it with systemd's address filter. It is reachable from 127.0.0.1 and refused everywhere else:
MYIP=$(hostname -I | awk '{print $1}')
curl -s -o /dev/null -m 5 "http://${MYIP}:9090/" && echo "REACHABLE off loopback - unexpected" || echo "refused on ${MYIP}:9090, as designed" curl -s -o /dev/null -w 'on 127.0.0.1:9090 : %{http_code}\n' -m 10 http://127.0.0.1:9090/config.json
sudo systemctl show jbrowse-admin.service -p IPAddressAllow -p IPAddressDenyrefused on 10.0.0.10:9090, as designed on 127.0.0.1:9090 : 200 IPAddressAllow=::1/128 127.0.0.0/8 IPAddressDeny=0.0.0.0/0 ::/0Step 9 - Use the admin surface
Browse to http://<vm-public-ip>:8080/?adminKey=<admin-key>. At the browser's authentication prompt, enter JBROWSE_ADMIN_USER and JBROWSE_ADMIN_PASSWORD from the credentials file. Use the JBROWSE_ADMIN_KEY value as the adminKey query parameter.
JBrowse loads in admin mode. The top bar shows an extra ADMIN menu that the public view on port 80 does not have. From there you can open the assembly manager, add and edit tracks, and set the default session. JBrowse writes every change back to /var/www/jbrowse/config.json through the admin server.
The admin key is regenerated every time jbrowse-admin.service starts, and /root/jbrowse-credentials.txt is kept in step automatically. After you restart the service, read the current value with:
sudo cat /var/lib/jbrowse/admin-keyStep 10 - Load your own genomes and tracks
The @jbrowse/cli tool is installed globally and is the scriptable way to load your own data. Confirm the tool and the search index that ships with the demo genome:
jbrowse --version ls -1 /var/www/jbrowse/trix/@jbrowse/cli version 4.3.0 ecoli_K12_MG1655.ix ecoli_K12_MG1655.ixx ecoli_K12_MG1655_meta.jsonCopy your files under the served data directory, then register them. Prepare your data as follows:
- Reference sequences: bgzip compressed, with
.faiand.gziindexes. - Annotation: position sorted and tabix indexed.
- Alignments and variants:
.baior.tbiindex alongside each file.
sudo mkdir -p /var/www/jbrowse/data/myorg
sudo cp mygenome.fa.gz mygenome.fa.gz.fai mygenome.fa.gz.gzi /var/www/jbrowse/data/myorg/ cd /var/www/jbrowse
sudo jbrowse add-assembly data/myorg/mygenome.fa.gz \
--out /var/www/jbrowse --load inPlace --type bgzipFasta \
--name myorg --displayName "My organism (v1)"
sudo jbrowse add-track data/myorg/mygenes.sorted.gff.gz \
--out /var/www/jbrowse --load inPlace \
--trackId myorg_genes --name "My gene models" \
--assemblyNames myorg --category Annotation
sudo jbrowse text-index --out /var/www/jbrowse --tracks myorg_genes --force sudo chown jbrowse:jbrowse /var/www/jbrowse/config.jsonReload the browser. The new assembly appears in the assembly selector. To prepare indexes first, use samtools, bcftools, tabix, bgzip and minimap2, which are all installed on the image.
Step 11 - Footprint
The appliance is light because rendering happens in the browser. Check memory, disk and the size of the JBrowse directory:
free -m | head -2 df -h / | tail -1 sudo du -sh /var/www/jbrowsetotal used free shared buff/cache available Mem: 3912 537 2058 3 1573 3374 /dev/root 29G 3.1G 25G 11% / 81M /var/www/jbrowseSecurity notes
The genome browser on port 80 is public by design and has no login. JBrowse is a viewer, and everything it serves is data you chose to publish. Treat port 80 and the /data/ directory as world readable, and place only data there that you are content to publish. If you need access control, put the VM behind a reverse proxy, Azure Application Gateway or Front Door that enforces your own authentication, and restrict 80/tcp in the NSG to the proxy.
Port 8080 is the only way to change the appliance. It is protected by an HTTP Basic credential generated on this VM at first boot and stored as a bcrypt hash. Restrict 8080/tcp in the NSG to your management network, and keep /root/jbrowse-credentials.txt confidential. The admin vhost carries the password over plain HTTP, so put TLS in front of it before you use it across an untrusted network.
The upstream JBrowse release archive contains the project's own test fixtures, which embed third-party OAuth application identifiers. The image removes them, and the build fails closed if they reappear.
Troubleshooting
The browser loads but every track is empty. The usual cause is a data host that does not honour range requests. Re-run the curl -sI -H 'Range: ...' check in Step 8 and confirm you see 206 Partial Content.
The page does not load at all. Check systemctl status nginx jbrowse-admin and journalctl -u jbrowse-firstboot -u jbrowse-postboot. Both first-boot units are one-shot, and their logs show what was created.
The admin surface returns 401. Re-read the credentials with sudo cat /root/jbrowse-credentials.txt.
The admin surface returns 502. The admin server is not running. Run sudo systemctl restart jbrowse-admin, then re-read the admin key.
Support
SecureAnyCloud images come with 24/7 support. For questions about this JBrowse 2 image or help with your deployment, contact us through the SecureAnyCloud website.
Deploy on Azure
Launch JBrowse 2 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com