Spyglass is an open-source framework for managing and analyzing data in neuroscience research. The Export feature allows users to generate scripts1 to recreate both their conda environmen and the database, as well as upload data to DANDI Archive.
This repository is intended to be used with the Docker to create and share a reproducible environment for replicating a paper's analyses.
- Pre-requisites:
make,docker, Docker Compose v2, andconda.makeis available on most Unix systems as part of GNU Make, or available withchoco(Windows) orbrew(macOS).- Docker builds, runs, and manages
containers. Docker Compose v2 is required (
docker compose, notdocker-compose). It ships with Docker Desktop and Docker Engine ≥ 20.10. Verify with:docker compose version. BuildKit is also required; it is enabled by default in Docker Desktop and Docker Engine ≥ 23. Verify with:docker buildx version. conda(e.g. Miniforge) with a working environment. The default environment name isspyglass; setSPYGLASS_CONDA_ENVin.envto use a different name.
- Register for Docker Hub and run
docker login. - Clone this repository to your local machine.
- Copy
example.envto.envand edit the values. On systems that requiresudoto run Docker (common on shared Linux servers), setSUDO_DOCKER=1. - Copy the paper's notebooks to
notebooks/2. - If the build fails due to GPU-only packages (e.g.
jax,jaxlib), remove them from${SPYGLASS_PAPER_DIR}/environment.yml. Edits must be made to the source file —make buildoverwritesexport_files/environment.ymlon every run, so changes there are lost on rebuild. If GPU packages are pulled in as transitive dependencies (e.g. vianon_local_detector), removing them fromenvironment.ymlwon't help; instead, uncomment themamba removeline inDocker_hub.Dockerfileto strip them after install (see Speed). - Run
make buildto build the docker image. This can take 20–40 minutes the first time. When complete, the JupyterLab URL will be printed automatically. Runmake portat any time to reprint it. - Navigate to the printed URL (e.g.
http://localhost:12345/lab), using the paper ID as the password. Wait ~30 seconds after build for the server to start. - Test the notebooks.
- When finished, run
make stopto pause,make removeto stop and remove containers, ormake cleanto remove all cached data (see Teardown). - Run
make publishto publish the image. - Share the image with collaborators, who can run
make runto start the container and visit the same URL (runmake portto get it). When finished, they should runmake stop,make remove, ormake clean. They will need ...- The
.envfile you used. - The
docker-compose-collab.ymlfile for building from the published images. - The
Makefilefor the command to run the container from the published images.
- The
Makefile: Contains commands for building and publishing the docker image.copy_files: Copies the exportsqlandymlfiles to theexport_files/directory.stop: Stops containers without removing them.remove: Runsstop, then removes containers (preserves volumes).clean: Runsremove, then removes all associated Docker volumes (full teardown).up: Runsremove, then starts the docker container.build: Alias forup.enter: Enters the running docker container for debugging.port: Prints the JupyterLab URL for this paper.
docker-compose.yaml: Defines the docker containers and volumes.db: Service. MySQL database container.hub: Service. Jupyter notebook server container.${PAPER_ID}_conda: Volume. Cache of the hub's conda environment.${PAPER_ID}_db_data: Volume. Cache of the database's data.
docker-compose-collab.yml: Similar todocker-compose.yaml, but using thehubimage from Docker Hub. This file is intended for collaborators.Docker_hub.Dockerfile: Adds additional instructions to thehubcontainer.- Copies in datajoint and jupyter configuration files.
- Installs
gitfor possible git installs in the conda environment. For a faster build time, remove this line if no such installs are needed. - Installs the paper's conda environment.
- Runs
entrypoint.pyto configure the datajoint connection.
example.env: Example environment variables for the.envfile. Must be copied to.envand edited. Key optional flags:SUDO_DOCKER=1— prefix all Docker commands withsudo. Set this on shared Linux servers where Docker requires elevated privileges.DANDI_API_KEY— required only for embargoed DANDI datasets.
config: Contains additional configuration files..datajoint_config.py: Default configuration for the datajoint connection.entrypoint.py: Edits the datajoint config based on environment variables.entrypoint_db.sh: Loads exportedsqlfiles. Run my thedbservice.jupyter_server_config.py: Configures the jupyter notebook server. - Sets the default kernel to the paper's conda environment. - Sets the server password.
The first time you run make build, the docker image will be built from
scratch. This can take a while, depending on the size of the conda environment.
Subsequent builds will be faster, as docker will cache the layers.
To speed up the process, projects that do not use the position pipeline can
remove the line in Docker_hub.Dockerfile that installs ffmpeg and other
dependencies.
If your build is still slow, or errors with conflicting packages, try removing
unnecessary packages from your conda environment.yml file. Note that running
make build will copy the file from its original location.
This repository is intended for use in a secure environment. It is not intended for use in a production environment.
By default the jupyter notebook server password is the paper ID variable.
There are three levels of teardown depending on how much you want to free.
| Command | Stops containers | Removes containers | Removes volumes |
|---|---|---|---|
make stop |
✓ | ||
make remove |
✓ | ✓ | |
make clean |
✓ | ✓ | ✓ |
Stopping containers (make stop) frees CPU and memory but keeps containers
and volumes on disk. Restarting with make build or make run will resume
quickly, skipping the conda install and database import.
Removing containers (make remove) additionally removes the stopped
containers. Volumes are preserved, so the next make build or make run will
still skip the slow conda install and database import.
Removing volumes (make clean) frees all disk space used by this paper's
environment. The next make build or make run will rebuild from scratch,
including a full conda install (20–40 minutes).
What is stored in each volume?
Volume data is stored under SPYGLASS_VOLUME_DIR (default:
${SPYGLASS_PAPER_DIR}/volumes), keeping it on the same filesystem as your
paper data rather than filling up /var/lib/docker/volumes/. Three
subdirectories are created per paper:
${PAPER_ID}_conda— the full conda environment (~5–15 GB depending on packages). Removing this means the next build re-downloads and re-installs all packages. Keep this if you expect to restart the container again soon.${PAPER_ID}_notebooks— any edits made to notebooks inside the running container are saved here. Remove this only if you are sure you no longer need those edits, or have saved them elsewhere.${PAPER_ID}_db_data— the MySQL database populated from the exported.sqlfiles. Removing this means the next start re-imports the database from scratch (typically fast, a few minutes).
To inspect volume sizes before deciding:
du -sh ${SPYGLASS_VOLUME_DIR}/*/If you encounter any issues, please check the status of the docker containers
with docker ps -a. This will show the status of containers ${PAPER_ID}_db
and ${PAPER_ID}_hub. If either is 'restarting', check the logs with
docker logs <name>. Use make enter to open a shell inside the running hub
container for further debugging.
If conda environment creation fails, you may need to remove items from the
environment.yml that require GPU support like jax.
make build automatically patches each .sql file in export_files/ via
config/patch_sql.sh, which removes encoding specifications to prevent
OperationalError and SQL ERROR 3780 (HY000).
What does this do?
patch_sql.sh removes DEFAULT CHARSET and DEFAULT COLLATE clauses:
CREATE TABLE your_table (
...
) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE swedish_latin=ci COMMENT='X';Becomes:
CREATE TABLE your_table (
...
) ENGINE=InnoDB COMMENT='X';Spyglass instances declared before version 0.4.3 permit longer keys than MySQL
defaults allow. This causes Excessive key length errors on import.
make build automatically shortens these via config/patch_sql.sh, mirroring
the adjustments from
PR #664.
If you encounter key length errors for a column not listed in patch_sql.sh,
add a substitution to the replacements list in that file to match. Run
python config/check_key_length.py export_files/ to scan your SQL dump for
over-limit columns and get the exact substitution to add.
Notebooks that access analysis NWB files should use spyglass APIs rather than calling DataJoint or pynwb directly.
Correct patterns
Getting the file path — get_abs_path uses a multi-step fallback that
works even when the export omits external-table rows:
# ✗ fragile: requires the external-table row to exist in the export
path = (AnalysisNwbfile() & {"analysis_file_name": fname}).fetch1("analysis_file_abs_path")
# ✓ correct
path = AnalysisNwbfile().get_abs_path(fname)Opening a file that may only exist on DANDI — get_nwb_file falls back to
DANDI streaming when the file is not present locally:
# ✗ breaks when the file is not on disk
with pynwb.NWBHDF5IO(path, "r") as io:
nwbf = io.read()
# ✓ correct: local → kachery → DANDI
from spyglass.utils.nwb_helper_fn import get_nwb_file
nwbf = get_nwb_file(path)For embargoed DANDI datasets, set DANDI_API_KEY in .env.
The default hub container does not have sudo access. If you need to install additional package or debug within the container, you may wish do the following:
Admin within the container
Add sudo for the default user, mysql credentials to Docker_hub.Dockerfile, and add
mysql-client to allow command line access to the database.
USER root
# Allow sudo
RUN echo "jovyan:jovyanpassword" | chpasswd
RUN echo "jovyan ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/jovyan
# Add mysql credentials - Vars must also be added to docker-compose.yml
ARG MYSQL_HOST
ARG MYSQL_USER
ARG MYSQL_ROOT_PASSWORD
# Add default mysql credentials
RUN echo -e "\
[client]\n\
host=${MYSQL_HOST}\n\
user=${MYSQL_USER}\n\
password=${MYSQL_ROOT_PASSWORD}\n\n\
[mysqld]\n\
character-set-server = latin1\n\
collation-server = latin1_swedish_ci" > ${HOME}/.my.cnf
RUN apt update && apt install mysql-client -y
USER ${NB_UID}Each ARG item must also be added to the docker-compose.yml file under the
hub service:
build:
context: .
dockerfile: Docker_hub.Dockerfile
args:
MYSQL_HOST: db
MYSQL_USER: root
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}And add GRANT_SUDO=yes to the .env file.
This section covers features intended for those maintaining or debugging this repository, rather than end users running a published paper environment.
- BuildKit cache mounts — persists conda/pip packages across builds so
re-running
make buildafter smallenvironment.ymlchanges doesn't re-download everything. make quick-build— rebuilds the hub image without re-copying files from the paper directory or tearing down running containers. Use when iterating on config or Dockerfile changes andexport_files/is already up to date.make enter— opens a bash shell inside the running hub container. Useful for inspecting the conda environment, testing imports, or checking file paths.- Port hashing —
PAPER_IDis hashed (SHA-256, range 10240–60000) to assign unique host ports, allowing multiple papers to run concurrently on shared infrastructure without manual port coordination. - Tests —
config/utilities (hash_port.py,patch_env.py,patch_sql.py) are covered by a pytest suite intest/. Run withpytest test/ -v. A secondlintjob checks shell script syntax (bash -n config/*.sh) and validates both Docker Compose files against a stub.env(docker compose config -q). Both jobs run on every push and pull request via.github/workflows/test.yml; no conda environment is required since all utilities use the Python standard library.
BuildKit cache mounts
Docker_hub.Dockerfile uses --mount=type=cache on the mamba env create
step:
RUN --mount=type=cache,target=/opt/conda/pkgs,uid=1000 \
--mount=type=cache,target=/home/jovyan/.cache/pip,uid=1000 \
conda update conda -y \
&& conda init bash \
&& mamba env create -f /tmp/environment.yml \
&& echo "conda activate ${PAPER_ID}" >> ~/.bashrcThe two cache mounts:
/opt/conda/pkgs— mamba's downloaded package tarballs. Reused on the next build, so only changed or new packages are fetched./home/jovyan/.cache/pip— pip's HTTP cache. Same benefit for the pip section ofenvironment.yml.
The mounts are host-side caches that do not become part of the image layer —
the final image is identical to one built without them. They persist across
make clean (which only removes named volumes, not BuildKit cache) and are
scoped to the host's Docker cache.
Requires Docker Engine ≥ 23 or Docker Desktop (BuildKit is enabled by default).
If you see unknown flag: --mount, add # syntax=docker/dockerfile:1 as the
first line of the Dockerfile and ensure BuildKit is active:
DOCKER_BUILDKIT=1 make build.
make quick-build
make quick-build
Equivalent to running docker compose up --build -d directly — skips the
copy_files step (which re-copies environment.yml and .sql files from the
paper directory) and skips down (which tears down running containers).
When to use: You've already run make build at least once, export_files/
contains the correct files, and you're iterating on changes to
Docker_hub.Dockerfile, config/, or notebooks/ that don't require
re-exporting from the paper directory.
When not to use: If environment.yml or the .sql files in the paper
directory have changed since the last make build, run make build instead so
copy_files re-copies and patches them.
Docker's layer cache means that if environment.yml has not changed, the slow
mamba layer is skipped automatically — even with make quick-build, a cache hit
on that layer completes in seconds.
make enter
make enter
Note: make enter (no arguments) is the full build-then-enter flow. To enter an
already-running container without rebuilding, run:
docker exec -it ${PAPER_ID}_hub /bin/bashOnce inside, the paper conda environment is on PATH. Useful commands:
conda list # inspect installed packages
python -c "import spyglass" # test an import
jupyter kernelspec list # verify the paper kernel is registered
cat /tmp/environment.yml # inspect the patched environment specPort hashing
Each paper's JupyterLab and MySQL ports are derived deterministically from
PAPER_ID using SHA-256:
import hashlib
h = hashlib.sha256(PAPER_ID.encode()).hexdigest()
SPYGLASS_HUB_PORT = 10240 + (int(h, 16) % 49761) # range: 10240–60000
SPYGLASS_DB_PORT = 10240 + (int(hashlib.sha256((PAPER_ID + "_db").encode()).hexdigest(), 16) % 49761)This mirrors the logic in
spyglass/tests/container.py:DockerMySQLManager.string_to_port. The 49,761-port
range gives a collision probability of ~0.006% at 3 concurrent users —
negligible for the expected usage of this repository.
Ports are computed in the Makefile and exported as
SPYGLASS_HUB_PORT/SPYGLASS_DB_PORT so that docker compose can substitute
them into docker-compose.yaml. Run make port to print the URL for the
current PAPER_ID.
Footnotes
-
The
.shscripts generated by Spyglass must first be run by a database administrator to create the database and tables. The resulting.sqlwill then be used to populate the Docker database. ↩ -
If your paper depends on a specific version of Spyglass or additional custom packages, please link to these in your notebooks, and ensure they are included in the
environment.ymlfile in the export directory. You can find the version of Spyglass at the top of any.sqlfile, and find the link in the list of Spyglass tags. ↩