A production-ready Node.js development container image with zsh, Oh My Zsh, Starship prompt, and essential development tools. Designed for VSCode devcontainers, GitHub Codespaces, and Coder.com.
- Node.js (Debian Trixie Slim base)
- pnpm package manager (latest via Corepack)
- GitHub CLI
- Python 3, make, g++, build-essential
- ripgrep (rg) - Fast grep alternative for code search
- jq - JSON processor for API responses and config files
- zsh with Oh My Zsh
- Starship prompt
- Oh My Zsh plugins: autoupdate, z, git, docker, docker-compose, npm, node, zsh-autosuggestions, fast-syntax-highlighting, zsh-autocomplete, gh
- Persistent shell history support
- OpenCode CLI for AI-assisted development
- uv/uvx (Python package runner for AI tools like Serena MCP)
- OpenSSL development libraries (for Prisma and other native modules)
- Git with optimized configuration
- Build tools for native Node.js modules
.devcontainer/devcontainer.json in this repo is a fully commented reference configuration. Read through it to understand what's available and why each setting exists, then copy and adapt it for your project.
It covers:
- Image selection and version pinning strategy
- Docker-outside-of-Docker setup (
moby: false+enableNonRootDocker) - Host network mode and when to use it
HOST_PROJECT_PATHfor docker-compose bind mounts inside the container- Custom CA certificates for corporate or self-signed TLS
- Persistent volumes for shell history and pnpm store
- Git, SSH, GPG, and OpenCode config mounting
- VS Code extensions and settings
- Lifecycle scripts (
postCreateCommand,postStartCommand)
{
"image": "ghcr.io/arthurfiorette/devwork:lts-node",
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached",
"remoteUser": "node",
"containerUser": "node"
}services:
devcontainer:
image: ghcr.io/arthurfiorette/devwork:lts-node
volumes:
- .:/workspace
command: sleep infinitydocker run -it --rm ghcr.io/arthurfiorette/devwork:lts-node zshThe image is published with tags for different Node.js versions:
-
lts-node- Node.js LTS version -
lts-node-abc1234- Node.js LTS at specific commit -
24-node- Node.js 24.x (recommended) -
24-node-abc1234- Node.js 24.x at specific commit -
22-node- Node.js 22.x -
22-node-abc1234- Node.js 22.x at specific commit
Use version tags (e.g., lts-node) for the latest build, or commit-specific tags (e.g., lts-node-abc1234) to pin to a specific version. Images are rebuilt weekly with security updates.
- zsh as default shell with persistent history
- Starship prompt with Node.js, git, and Docker awareness
- Oh My Zsh with curated plugins for Node.js development
- Syntax highlighting and autosuggestions
- Git integration showing branch, status, and changes
- Pre-installed build tools for native modules
- pnpm configured and ready (no interactive prompts)
- Proper permissions for non-root user
- Optimized for monorepos and workspaces
- Fast code search with ripgrep
- JSON processing with jq
- uv/uvx installed for running AI coding assistants
- Compatible with Serena MCP, Cursor, and other AI tools
- Supports GitHub Codespaces natively
- Ready for OpenCode, Aider, and similar tools
Mount a volume to preserve shell history across container rebuilds:
"mounts": [
"source=shell-history,target=/home/node/shell-history,type=volume"
]Mount a volume for faster package installations:
"mounts": [
"source=pnpm-store,target=/home/node/.local/share/pnpm/store,type=volume"
]The image includes optimized Git defaults (rebase on pull, auto-setup remote, etc.).
To use your own Git config (recommended for personal settings like name/email):
"mounts": [
"source=${localEnv:HOME}/.gitconfig,target=/home/node/.gitconfig,type=bind,readonly"
]Your personal config will be merged with the built-in defaults (your settings take precedence).
If not mounted, the container uses the included defaults:
- Pull with rebase
- Auto-setup remote on push
- GPG signing enabled (requires GPG keys mounted separately)
- Optimized log, diff, and rebase settings
The node user has a default password of node. To change it:
sudo passwd nodeNote: the password resets to node on every container rebuild.
oc- Shorthand for opencode
p- Shorthand for pnpmpw- Run command in workspace root (pnpm run --workspace-root)
wip- Quick commit all changes, pull with rebase, and pushwipf- Same as wip but skips git hooks (use with caution)
notty- Run command without TTY (strips ANSI colors, useful for piping to files)
The image uses pnpm@latest, but Corepack automatically respects the packageManager field in your project's package.json. If your project specifies pnpm@9.15.9, Corepack will download and use that version automatically. This allows the same base image to work with different pnpm versions across projects.
Use the included test.sh script to build and test images locally, mirroring what CI does:
# Build and test all Node versions (22, 24, lts)
./test.sh
# Build and test a specific version
./test.sh 24
# Build and test multiple versions
./test.sh 22 24The script will:
- Lint shell scripts with shellcheck (if installed)
- Build the image for each version
- Run
devwork-versionsto verify all tools are installed - Report image size
- Print a pass/fail summary
To build manually without testing:
# Build for a specific Node version
docker build --build-arg NODE_VERSION=24 -t devwork:24-node .
# Run tools verification
docker run --rm devwork:24-node devwork-versions- Docker Desktop or Docker Engine
- VSCode with Dev Containers extension (for devcontainer usage)
- At least 2GB disk space for the image
This is a personal project but issues and suggestions are welcome. The image is rebuilt weekly with the latest security updates and Oh My Zsh plugins.
To set up the pre-commit hook that auto-formats shell scripts:
git config core.hooksPath .githooksMIT License - See LICENSE file for details.
The image includes a devwork-versions command to verify all tools are installed:
# Run inside container
devwork-versions
# Or from outside
docker run --rm ghcr.io/arthurfiorette/devwork:24-node devwork-versionsThis script can also be used as a healthcheck in docker-compose:
healthcheck:
test: ['CMD', 'devwork-versions']
interval: 30s
timeout: 3s