Docker
The native binary is recommended. It is one static file with no runtime, it sees your global git excludes file, it gets file events on every OS, and it relabels nothing on SELinux hosts. Use the image when you want isolation or a reproducible build.
The image
Built from the repo’s Dockerfile: a Go build stage, then gcr.io/distroless/static-debian12:nonroot. It holds only the static binary, CA certificates and the licenses (/usr/share/doc/scree), runs as uid 65532, and is about 18 MB.
- Entry point
scree; default commandwatch -notes /notes. XDG_DATA_HOME=/data, a volume, so indexes live under/data/scree/indexes/.
docker build -t scree . # or: podman build -t scree .
Planned: tagged releases push ghcr.io/jrapakko/scree:<version> and :<major>.<minor> for linux/amd64 and linux/arm64. No version is tagged yet.
File events reach the container through a bind mount on Linux. Docker Desktop on macOS doesn’t pass them through, so watch misses edits there.
SELinux: :z is required, and it relabels
On SELinux hosts (Fedora, RHEL, CentOS Stream), every bind mount needs :z. Without it the container can’t read the folder and scree fails with a permission error such as open /notes: permission denied.
:z permanently relabels every file in the folder on the host to container_file_t, even when the mount is read-only. Mount a repo or a notes folder, never your home directory. On systems without SELinux, drop :z. If you’d rather not relabel anything, use the native binary.
docker run
# background indexer for one folder
docker run -d --name scree-watch --restart unless-stopped \
-e OPENROUTER_API_KEY \
-v "$HOME/wiki:/notes:ro,z" -v scree-data:/data \
scree watch -notes /notes
# one-off commands against the same index
docker run --rm -e OPENROUTER_API_KEY -v "$HOME/wiki:/notes:ro,z" -v scree-data:/data scree doctor -notes /notes
docker run --rm -e OPENROUTER_API_KEY -v "$HOME/wiki:/notes:ro,z" -v scree-data:/data scree search -notes /notes "release checklist"
-e OPENROUTER_API_KEY with no value passes the variable through from your shell, so the key never appears on the command line.
Compose
compose.yaml has two services sharing the scree-data volume. It reads OPENROUTER_API_KEY from your shell or from .env next to compose.yaml, and the folder from SCREE_NOTES_DIR:
SCREE_NOTES_DIR=$HOME/wiki docker compose up -d # starts watch
docker compose logs -f watch
watchrunswatch -notes /notes, restarts unless stopped, and mounts the folderro,z.serverunsserve -notes /noteswith stdin open, behind themcpprofile, soupdoesn’t start it.- Both pass
SCREE_MODEL,SCREE_DIMENSIONS,SCREE_MAX_FILE_BYTES,SCREE_ZDRandSCREE_EMBED_URL, with the same defaults as the binary;servealso passesSCREE_HTTP_TOKEN.
The two containers share the index through the volume, with one writer at a time as for native processes.
Claude Code with Docker
contrib/scree-mcp-docker runs one container per Claude Code session over stdio. A repo’s committed .mcp.json names scree-mcp, so install the Docker wrapper under that name:
install -m 755 contrib/scree-mcp-docker ~/.local/bin/scree-mcp
claude mcp add scree --scope project -- scree-mcp # once per repo, from its root
It loads the settings file like the native wrapper, then runs:
docker run -i --rm --pull=never \
-e OPENROUTER_API_KEY [-e SCREE_MODEL …] \
-v <repo>:<repo>:<SCREE_MOUNT_OPTS> -v scree-data:/data \
scree:latest serve -notes <repo>
--pull=never: a missing image is never pulled from a registry, since the container receives your key. Build or pull the image yourself first.- The repo mounts at its own path, read-only, so each repo hashes to its own index in the shared volume.
<repo>is the git work tree aboveCLAUDE_PROJECT_DIR, found only through a.gityou own that is not a symlink, never in or above a sticky folder, so.gitignoreand.git/info/excludeare visible. - Refused mounts: your home directory, a folder above it,
/, a relativeCLAUDE_PROJECT_DIR, and paths containing:. - Variables by name only:
OPENROUTER_API_KEY,SCREE_MODEL,SCREE_DIMENSIONS,SCREE_ZDR,SCREE_MAX_FILE_BYTES,SCREE_EMBED_URLandSCREE_HTTP_TOKEN, each only if set. Proxy variables are not passed. - Your global git excludes file is not visible in the container, so it may index files that native scree ignores. Put such patterns in the repo’s
.screeignore. - Like
scree-mcp, it ignores inherited settings and takes no arguments.
Settings for it, in the settings file:
| Variable | Default | Accepted |
|---|---|---|
SCREE_DOCKER |
docker |
docker or podman |
SCREE_IMAGE |
scree:latest |
any local image |
SCREE_DATA_VOLUME |
scree-data |
a volume name, not a path |
SCREE_MOUNT_OPTS |
ro |
ro, ro,z, ro,Z; use ro,z on SELinux hosts |
HTTP inside a container
Inside a container, serve -http has to bind a non-loopback address to be reachable through a published port, so it needs -allow-remote and SCREE_HTTP_TOKEN. Publish the port on the host’s loopback only, for example -p 127.0.0.1:7077:7077.