Host a Dataset on GitHub#
A public GitHub repository is a free, permanent home for a dataset that anyone can open in Cellucid from a link: no server, no sign-in, no upload service. Publish it as a Zarr store folder. This page explains why, walks through the steps, and gives the numbers measured against GitHub’s own hosts.
Important
A public repository publishes everything in it: coordinates, metadata, gene values, graphs and provenance. Check the files for private identifiers before you push. For data that must stay private, use Server Mode (CLI + Python).
Why a Zarr folder#
GitHub refuses any file over 100 MiB and warns above 50 MiB
(About large files on GitHub),
so the .h5ad (or .zarr.zip) of a real atlas cannot live in an ordinary
repository. A Zarr store folder holds the same AnnData as many small files:
Every file is small.
prepare()andcellucid_prepare()write chunks of at most a few MB. The 561,947-cell Suo atlas is 1,961 files, 198 MB in all, the largest 1.6 MB.It is read in place. Both GitHub hosts that serve a repository’s files (GitHub Pages and
raw.githubusercontent.com) answer over HTTP/2, allow other sites to read them (CORS) and serve byte ranges, so Cellucid fetches only the chunks each view needs, up to 100 at once on one connection. A small file is also quick to fetch the first time: GitHub’s edge fetches a large file whole before it answers the first request for any part of it (0.8–3.2 s measured for 16–66 MB files onraw.githubusercontent.com).It costs nothing to serve. Ordinary repository files use no Git LFS bandwidth (see Git LFS).
The written format is the standard AnnData Zarr layout that anndata.read_zarr
reads; its consolidated metadata lets a browser learn every array with one
request, since a web host cannot list a folder.
Step by step#
1. Write the store#
From Python:
import cellucid
cellucid.prepare(
"atlas.zarr",
format="zarr",
embeddings={"X_umap": adata.obsm["X_umap"]},
dataset_id="my-atlas",
dataset_name="My atlas",
obs=adata.obs,
var=adata.var,
gene_expression=adata.X,
connectivities=adata.obsp["connectivities"],
)
From R:
cellucid_prepare(
"atlas.zarr",
format = "zarr",
embeddings = list(X_umap = umap),
dataset_id = "my-atlas",
dataset_name = "My atlas",
obs = obs,
gene_expression = expr
)
The default layout is the one to publish: Zarr v2, the expression matrix stored gene-major (CSC) in chunks of 16,384 values, every array with one row per cell in chunks of 65,536 and every other in chunks of 262,144, byte-shuffled and deflated (the expression values unshuffled), each chunk its own file, with consolidated metadata. How fast it reads explains why no other layout is faster on GitHub. Every argument is described in Preparing an AnnData for Cellucid and R Package Guide.
2. Add .nojekyll (for GitHub Pages)#
A Zarr store keeps its metadata in dot-files (.zmetadata, .zgroup,
.zattrs, .zarray) and AnnData names each table’s index _index. GitHub
Pages runs Jekyll by default, which publishes neither files whose names start
with . nor folders whose names start with _, so a store served from Pages
without the marker below is missing its metadata and its cell names. An empty
.nojekyll file at the root of the published branch turns Jekyll off:
touch .nojekyll
raw.githubusercontent.com serves every file regardless; the marker only
matters for Pages.
3. Commit and push#
Use Git from the command line: the github.com upload page takes at most 25 MiB and 100 files at a time.
git add .nojekyll atlas.zarr
git commit -m "Add atlas"
git push
4. Open it in Cellucid#
Paste either address into Session › Open data › Link or server and press Open:
Address |
Served by |
Example |
|---|---|---|
|
|
|
|
the same host, another branch, tag or commit |
|
|
GitHub Pages, once enabled (step 5) |
|
A github.com/owner/repo/tree/<branch>/… link to the folder works too.
Opened by its owner/repo/… address, the folder is read at the commit that
last changed it (one request to GitHub’s API), so what a visit reads of it
is kept in the browser and the next visit downloads none of it until you
push a change to the folder; past the API’s 60 requests an hour for a
browser without a sign-in, and from a GitHub Pages address, it is read
anew at each visit. To share it, put the address in a Cellucid link (gh:
before the short form; a Pages address URL-encoded):
https://www.cellucid.com/?data=gh:my-lab/atlas-data/atlas.zarr
https://www.cellucid.com/?data=https%3A%2F%2Fmy-lab.github.io%2Fatlas-data%2Fatlas.zarr%2F
To try one first: the reference repository publishes a 72-cell dataset as a
Zarr folder,
gh:theislab/cellucid-demo-custom-datasets/catalog/synthetic-cell-types-2d.zarr,
and the same dataset as one file,
gh:theislab/cellucid-demo-custom-datasets/files/synthetic-cell-types-2d.h5ad.
For several datasets in one repository, publish a dataset catalog
(datasets.json beside the stores) and link its folder instead:
Publish a Custom Dataset Repository.
5. Optional: enable GitHub Pages#
Pages answers a first request faster than raw.githubusercontent.com (below).
Open the repository’s Settings › Pages, choose Deploy from a branch,
pick the branch and / (root) (or /docs, and then leave docs/ out of the
Pages address), and Save. After the deployment finishes, the store is at
https://<owner>.github.io/<repo>/<path>/atlas.zarr/; check that
…/atlas.zarr/.zmetadata opens in a browser tab.
How fast it reads#
Measured on 22 October 2026 from Munich (a line delivering 4.3 MB/s; GitHub’s nearest edge 19 ms away), with real requests to GitHub’s hosts:
GitHub Pages |
|
|
|---|---|---|
Protocol |
HTTP/2 (no HTTP/3) |
HTTP/2 (no HTTP/3) |
Requests in flight on one connection |
100 |
100 |
First request for a file (edge cache miss), median |
150 ms |
240 ms |
A file the edge has cached (for 10 min on Pages, 5 min on raw) |
35–45 ms |
35–45 ms |
First request for a large single file (16–66 MB), which the edge fetches whole |
0.7 s (25 MB) |
0.8–3.2 s |
Uncached small files per second, 100 in flight |
416 |
317 |
Bytes per second |
as fast as the line |
as fast as the line |
Bursts of thousands of requests |
one 503 in 3,000 (Cellucid asks again) |
no refusal in 10,000 |
Each view therefore costs one or two round trips plus its bytes over your line. For Suo (561,947 cells, 8,192 genes) written with the default layout (198 MB), Cellucid’s reader takes these times over links simulated from those measurements (HTTP/2, 100 requests per connection, every CORS preflight refused; measured 23 October 2026):
What |
Bytes |
150 ms round trip, 5 MB/s |
80 ms, 50 MB/s |
|---|---|---|---|
Open (the store’s structure) |
0.1 MB |
0.3 s |
0.2 s |
Every |
1.3 MB |
0.4 s |
0.2 s |
2-D UMAP |
3.7 MB |
0.9 s |
0.2 s |
First gene |
0.1 MB |
0.2 s |
0.09 s |
Next gene |
0.1 MB |
0.2 s |
0.08 s |
99 genes at once |
3.6 MB |
0.9 s |
0.2 s |
Neighbour graph |
66.5 MB |
13.8 s |
1.8 s |
Every gene in two cell types (a differential expression test) |
62.5 MB |
13.0 s |
1.7 s |
Over GitHub’s HTTP/2 the folder reads as fast as one .h5ad or .zarr.zip
of the same data (46.1 s against 46.9 and 46.7 s for every step above and a
few more at 5 MB/s; 8.0 s against 8.3 and 8.2 s at 50 MB/s). Over HTTP/1.1,
which most other hosts speak, a browser fetches six files at a time and the
same folder took 46.8 s against 9.0 s at 50 MB/s: there, publish one file.
Everything but the first round trip is your line, not GitHub. A sharded Zarr v3 store (many chunks per file) was measured too: it reads fewer files when a view needs the whole matrix, but every gene from a shard not yet touched waits for the shard’s index first (0.35 s instead of 0.09 s at 80 ms), so the default unsharded layout is the faster one for what Cellucid reads.
Limits#
Limit |
Value |
What it means here |
|---|---|---|
One file |
blocked over 100 MiB, warned over 50 MiB |
a Zarr store written by |
Repository |
ideally under 1 GB, strongly recommended under 5 GB |
about the size of four Suo-sized atlases per repository |
Published Pages site |
at most 1 GB |
everything in the published branch or folder counts |
Pages bandwidth |
100 GB per month (soft) |
about 400 complete Suo downloads; most visitors read far less |
Pages builds |
10 per hour (soft) |
batch your pushes |
Sources: large files, GitHub Pages limits. Beyond these, publish on a static object host or use Server Mode (CLI + Python).
Updating a published store#
The edges cache each file for up to 10 minutes (Pages) or 5 minutes (raw), so
for a few minutes after a push a reader may see old and new files side by
side. Write an update as a new store (atlas-v2.zarr) or on a new branch or
tag, and share that address; a link with @<tag> or @<commit> never
changes under its readers.
Troubleshooting#
Symptom |
Cause and fix |
|---|---|
“… no Zarr store is at this address …” |
Nothing is published at that path: the owner, repository, branch or path is wrong, the repository is private, or the push has not finished. Open |
Opens from |
No |
“… has no consolidated metadata (.zmetadata) …” |
The store was written without consolidated metadata. Write it with |
“… was not found at raw.githubusercontent.com (HTTP 404)” |
A single |
Files on another branch are not found |
Without |
A file read returns a few lines of text |
The file was committed with Git LFS: its pointer is in the repository, the file on the LFS host. Cellucid follows the pointer for a single |
Works at home, not on the institute network |
A proxy or firewall blocks |
More in Troubleshooting (data loading).
Git LFS: an alternative with a quota#
A single .h5ad or .zarr.zip over 100 MiB can be committed with
Git LFS. Cellucid opens it from the same link
(owner/repo/path/atlas.h5ad): it reads the pointer, then reads the file in
place from media.githubusercontent.com, which serves LFS content over HTTP/2
with ranges and CORS (a first read of a file there took about 0.5 s).
The cost is bandwidth. Every byte any visitor’s browser reads counts against the repository owner’s LFS bandwidth: 10 GiB a month on GitHub Free, Pro and Free for organizations, 250 GiB on Team and Enterprise Cloud. When it is used up, GitHub either bills the overage or, without a payment method or with a zero budget, disables Git LFS for the owner’s account or organization until the next month, for every repository it owns (Git LFS billing). One visitor reading Suo’s neighbour graph and a few dozen genes is already about 70 MB. Prefer a Zarr folder: it is read as fast and costs no LFS bandwidth at all.