A reusable Node.js development container base image with modern tooling and shell configuration. Built for VSCode devcontainers, GitHub Codespaces, and Coder.com.
Single Responsibility: Provide a consistent Node.js development environment with modern tooling and shell configuration.
Reusability: Generic base image usable across projects. Project-specific configuration (services, volumes, extensions) stays in project repos.
Version Flexibility: pnpm version is project-controlled via packageManager field in package.json. Corepack automatically downloads and uses the specified version.
Multi-Platform: Builds for both amd64 and arm64 architectures for broad compatibility.
Uses official Node.js Debian images (not Alpine) for better compatibility with native modules and standard tooling. The ARG NODE_VERSION allows building multiple Node.js versions from a single Dockerfile.
Runs as the node user (non-root) provided by the official Node.js image. System-wide tools are installed as root, user-specific tools (Oh My Zsh, plugins, uv, OpenCode) are installed after switching to the node user.
Three-tier config:
.profile- Shared configuration (PATH, aliases, functions) loaded by all shells.zshrc- zsh-specific (Oh My Zsh, Starship, loads .profile).bashrc- Appended (not replaced) to load .profile, preserving base image defaults
Why this pattern:
- Single source of truth for shared config
- Preserves base image configurations
- Works with both bash and zsh
- Easy to extend or override
Git defaults are in /etc/gitconfig (system-level), not ~/.gitconfig. This allows users to mount their personal config to ~/.gitconfig without losing the defaults. Git merges both configs with user settings taking precedence.
Included defaults:
- Pull with rebase
- Auto-setup remote on push
- GPG signing disabled by default
- Optimized rebase, log, and diff settings
See .gitconfig for complete list.
System-wide (installed as root, available to all users):
- Starship: Installed via official script to system bin
- System packages: Via apt-get
- pnpm: Via Corepack (system-wide)
- Version check:
/usr/local/bin/devwork-versions
User-specific (installed as node user):
- uv/uvx:
~/.cargo/bin/ - OpenCode:
~/.opencode/bin/ - Oh My Zsh:
~/.oh-my-zsh/ - Plugins:
~/.oh-my-zsh/custom/plugins/
PATH setup:
PNPM_HOMEin Dockerfile ENV- User bins (
~/.cargo/bin,~/.opencode/bin) in Dockerfile ENV - Additional paths in
.profilefor completeness
Multi-version builds:
The workflow builds three variants using a matrix strategy. See .github/workflows/build.yml for the current matrix and tag strategy.
Caching: Registry-based caching is used per Node version for faster rebuilds. Cache keys are based on the Node version to avoid cross-version pollution.
Testing:
All builds are tested using the devwork-versions script which verifies all tools are installed and accessible. This same script can be used as a healthcheck in projects.
Tags follow the pattern {NODE_VERSION}-node for stable tags and {NODE_VERSION}-node-{GIT_SHA} for commit-specific tags. The -node suffix allows future language variants (e.g., -rust, -go, -polyglot).
See the workflow file for the exact metadata-action configuration.
Configuration files:
Dockerfile- Image build definition with ARG for Node version.profile- Shared shell configuration.zshrc- zsh-specific configuration.gitconfig- System-level Git defaultsdevwork-versions- Version verification script
Build automation:
.github/workflows/build.yml- Multi-version build, test, and publish.github/dependabot.yml- Weekly dependency updates
Documentation:
README.md- User-facing documentation and usage examplesCLAUDE.md- This file (technical reference)
System-wide tools:
Add to the apt-get install list in the Dockerfile or install via curl as root before the USER node line.
User-specific tools:
Install after USER node. If the tool installs to a non-standard location, add the bin directory to the PATH ENV or to .profile.
Shared config (aliases, functions, PATH):
Edit .profile - affects both bash and zsh.
zsh-specific config:
Edit .zshrc - only affects zsh users.
Important: Don't replace .bashrc, append to it. The base image may have important bash-specific configuration.
The devwork-versions script serves multiple purposes:
- CI testing (ensures all tools are installed)
- Manual verification (users can run it to check their environment)
- Healthcheck (can be used in docker-compose healthcheck configuration)
- Documentation (shows what tools are expected)
When adding new tools, update this script to include them in verification.
Builds are triggered on:
- Pushes to main that modify build-related files (see workflow paths filter)
- Weekly schedule for security updates
- Manual dispatch for on-demand builds
The weekly rebuild ensures the image includes the latest security patches from the base image and updated Oh My Zsh plugins, even if the Dockerfile hasn't changed.
Projects should reference the image in their devcontainer.json or docker-compose.yml and add project-specific configuration:
- Services (databases, caches, queues)
- Volume mounts (workspace, persistent data)
- VSCode extensions
- Environment variables
- Port forwarding
The base image provides the development environment. Projects add their specific requirements on top.
Testing changes locally:
Build the image locally with the desired Node version (see README for build commands) and run devwork-versions to verify all tools are installed.
Before pushing: Ensure all configuration files that are copied in the Dockerfile exist in the repo and are properly formatted.
CI will:
- Build for all Node versions in the matrix
- Test using
devwork-versions - Push to registry if tests pass
- Tag appropriately based on metadata action configuration
Non-root user:
The container runs as the node user (UID 1000) to avoid permission issues with mounted volumes and follow security best practices.
Read-only Git config: When users mount their gitconfig, it should be read-only to prevent accidental modifications.
GPG signing: Git is configured to sign commits, but GPG keys must be mounted separately by the project. The base image doesn't include or assume GPG key availability.
Weekly rebuilds: Automated weekly builds ensure the image stays current with security patches and Oh My Zsh plugin updates.
Dependabot: Monitors base image updates and GitHub Actions versions. Review and merge dependabot PRs to keep dependencies current.
Node version support: The matrix in the build workflow defines which Node versions are supported. Update the matrix when Node versions reach EOL or when new LTS versions are released.