npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

sandcontainer

v0.4.0

Published

Catalog-driven CLI for distributing devcontainer configurations

Readme

sandcontainer

Catalog-driven CLI for distributing devcontainer configurations. Resolves template IDs from a remote catalog, downloads devcontainer.json into .devcontainer/<id>/, and forwards lifecycle commands to @devcontainers/cli.

⚠️ Experiment: early alpha, expect breaking changes. Feedback welcome!

Requirements

  • Node >= 20
  • Docker
  • macOS / Linux (Windows via WSL2)

Install

npm install -g sandcontainer

Or run without installing:

npx sandcontainer <command>

Both sandcontainer and scx resolve to the same binary. Use scx for daily use.

Quick Start

# 1. Browse available templates
scx list

# 2. Download a template into your project
scx init claude-code

# 3. Start the container
scx up claude-code

# 4. Run claude inside it
scx exec claude-code claude

Command Reference

scx init <id> [--force]

Download a template from the catalog into .devcontainer/<id>/devcontainer.json.

$ scx init claude-code
Initialized template "claude-code" at .devcontainer/claude-code/devcontainer.json

$ scx init claude-code
error: .devcontainer/claude-code/devcontainer.json already exists. Use --force to overwrite.

$ scx init claude-code --force
Initialized template "claude-code" at .devcontainer/claude-code/devcontainer.json

$ scx init nonexistent
error: Template "nonexistent" not found in catalog.

scx list

List templates already initialized in the current project.

$ scx list
claude-code
copilot

$ scx list         # in a project without .devcontainer/
No templates found.

scx up <id> [...args]

Start the devcontainer. All args after <id> are forwarded to @devcontainers/cli.

$ scx up claude-code
# → devcontainer up --workspace-folder . --config .devcontainer/claude-code/devcontainer.json
# (streams @devcontainers/cli output)

$ scx up claude-code --build-no-cache
# → devcontainer up --workspace-folder . --config .devcontainer/claude-code/devcontainer.json --build-no-cache

scx exec <id> [...args]

Run a command inside the container.

$ scx exec claude-code bash
# → devcontainer exec --workspace-folder . --config .devcontainer/claude-code/devcontainer.json bash
# (interactive shell inside the container)

$ scx exec claude-code claude --dangerously-skip-permissions
# → devcontainer exec --workspace-folder . --config .devcontainer/claude-code/devcontainer.json claude --dangerously-skip-permissions
# (flags after <id> are NOT intercepted by sandcontainer)

$ scx exec claude-code bash -lc "pnpm install && pnpm test"

scx rebuild <id>

Rebuild the container from scratch.

$ scx rebuild claude-code
# → devcontainer up --workspace-folder . --config .devcontainer/claude-code/devcontainer.json --remove-existing-container

scx down <id>

Stop the running container.

$ scx down claude-code
# → docker stop <container with labels devcontainer.local_folder=<repo> AND devcontainer.config_file=<config>>

Using the long name or npx

$ sandcontainer init claude-code
$ sandcontainer up claude-code
# `sandcontainer` and `scx` are interchangeable; same binary, two names.

$ npx sandcontainer init claude-code
$ npx sandcontainer up claude-code

Global Flags

$ scx --version
0.1.0

$ scx --help
# prints top-level help listing init / list / up / exec / rebuild / down

$ scx exec --help
# prints sandcontainer's help for `exec` (not @devcontainers/cli help)

Available Templates

| ID | Description | |----|-------------| | claude-code | Devcontainer for Claude Code using the standard Microsoft JavaScript/Node base image and devcontainer-features. Larger first build (~2.3GB); no pre-built image on GHCR. | | claude-code-slim | Devcontainer for Claude Code with a hand-picked node:22-slim base and pre-built GHCR image (~800MB). Faster first run; no devcontainer-features. | | copilot | Devcontainer for GitHub Copilot CLI using the standard Microsoft JavaScript/Node base image and devcontainer-features. Larger first build (~2.3GB); no pre-built image on GHCR. | | copilot-slim | Devcontainer for GitHub Copilot CLI with a hand-picked node:22-slim base and pre-built GHCR image (~800MB). Faster first run; no devcontainer-features. |

Choosing a template

Both templates produce an identical in-container experience: the same name, remoteUser, initializeCommand, environment variables, bind mounts (including ~/.claude and ~/.claude.json), and workspaceFolder. The only difference is the build path:

  • claude-code (features-based): starts from mcr.microsoft.com/devcontainers/javascript-node:22 and installs Claude Code via devcontainer-features. No pre-built image. First build downloads and installs features (~2.3GB). Standard base image — good if you want to layer in additional features.
  • claude-code-slim (Dockerfile-based): starts from a pre-built GHCR image (ghcr.io/thaitype/sandcontainer-claude-code-slim:latest) based on node:22-slim (~800MB). Faster first pull; no features step.

The copilot and copilot-slim templates follow the same split for GitHub Copilot CLI. Both templates share the same name, remoteUser, containerEnv, and bind mounts (~/.copilot and ~/.gitconfig); only the build path differs:

  • copilot (features-based): starts from mcr.microsoft.com/devcontainers/javascript-node:22 and installs Copilot CLI via the copilot-cli and github-cli devcontainer-features. No pre-built image. First build downloads and installs features (~2.3GB).
  • copilot-slim (Dockerfile-based): starts from a pre-built GHCR image (ghcr.io/thaitype/sandcontainer-copilot-slim:latest) based on node:22-slim (~800MB). Faster first pull; no features step.

Note — GH_TOKEN required at invocation time. Copilot CLI authenticates via a GitHub token. Set GH_TOKEN in your host shell on every scx call:

GH_TOKEN=$(gh auth token) scx up copilot
GH_TOKEN=$(gh auth token) scx exec copilot copilot
GH_TOKEN=$(gh auth token) scx rebuild copilot

The same pattern applies to copilot-slim (replace copilot with copilot-slim).

Pre-building the features-based template (optional)

scx does not expose a build subcommand today. The features-based claude-code template installs features during the first scx up, which can take several minutes. If you want to pre-build the image separately (for CI, or to warm the Docker cache before working offline), invoke @devcontainers/cli directly:

npx @devcontainers/cli build \
  --workspace-folder . \
  --config .devcontainer/claude-code/devcontainer.json

This builds the image — including feature installation — without starting a container. Subsequent scx up claude-code will reuse the cached layers and start quickly. The slim template does not need this step; it pulls a pre-built image from GHCR.

Project Layout

After running scx init, your project will look like:

<your-project>/
  .devcontainer/
    claude-code/
      devcontainer.json

Multiple templates coexist side by side. The downloaded devcontainer.json is the single source of truth — edit it directly if you need to customize.

Template Images

Templates that ship a Dockerfile are built and published to the GitHub Container Registry automatically. The features-based claude-code template has no Dockerfile and therefore has no pre-built image — devcontainers handles installation via devcontainer-features at first build time.

Published image name: ghcr.io/thaitype/sandcontainer-<id>:latest

For example, the claude-code-slim template publishes to:

ghcr.io/thaitype/sandcontainer-claude-code-slim:latest

How publishing works

A GitHub Actions workflow (.github/workflows/publish-templates.yml) triggers on every push to main that touches files under templates/**, plus on manual workflow_dispatch. It discovers which templates contain a Dockerfile, then builds and pushes a multi-arch image (linux/amd64, linux/arm64) for each one. Only the :latest tag is published.

One-time visibility flip (required after first publish)

GHCR creates new packages as private by default. After the workflow runs for the first time for a template, a maintainer must make the package public manually:

GitHub → your profile → Packages → sandcontainer-<id>
  → Package settings → Change visibility → Public

This is a one-time step per template. Once public, the image can be pulled anonymously — end users do not need a GHCR account or docker login.

devcontainer.json rule for templates

Templates that have a Dockerfile must reference the already-published image via "image": in their devcontainer.json. Using "build": or "dockerFile": is not allowed in a template, because the template travels to the end user as a plain JSON file — the user never sees the Dockerfile.

Correct (example using the slim template):

{
  "name": "claude-code-slim",
  "image": "ghcr.io/thaitype/sandcontainer-claude-code-slim:latest",
  "remoteUser": "node"
}

Not allowed in a template:

{
  "build": { "dockerfile": "Dockerfile" }
}

License

MIT