Sign inSign up

tundrasoft/node

By tundrasoft

โ€ขUpdated 3 days ago

Image
0

4.7K

tundrasoft/node repository overview

โ ๐ŸŸข TundraSoft Node.js Runtime Image

A lightweight Node.js runtime image built on Alpine Linux with S6 overlay, npm, and developer-friendly utilities.

Built on tundrasoft/alpineโ : Alpine Linux, s6-overlayโ  process supervision, and an unprivileged tundra user (UID/GID 1000) that your application runs as. Node's official Linux binaries are linked against glibc, which Alpine does not ship, so the image layers in the glibc loader and runtime libraries from gcr.io/distroless/cc-debian12 (the same approach as tundrasoft/denoโ ) rather than relying on a separately compiled musl build.

GitHub Workflow Status Security Scan Docker Pulls License


โ ๐Ÿ“‹ Table of Contents


โ ๐Ÿš€ Quick Start

โ ๐Ÿ“ฆ Available Registries

This image is available on multiple registries:

  • Docker Hub: tundrasoft/node
  • GitHub Container Registry: ghcr.io/tundrasoft/node
# Pull from Docker Hub (recommended)
docker pull tundrasoft/node:latest

# Pull from GitHub Container Registry
docker pull ghcr.io/tundrasoft/node:latest

# Run a local Node.js application (mount your code into /app)
docker run -d \
  -p 8080:8080 \
  -e FILE=/app/server.js \
  -v $(pwd):/app \
  --name node-app \
  tundrasoft/node:latest

# Run a package.json script with a custom timezone
docker run -d \
  -e TZ=Asia/Kolkata \
  -e SCRIPT=start \
  -v $(pwd):/app \
  --name my-node-app \
  tundrasoft/node:latest

With no FILE or SCRIPT set, the container runs a small built-in demo server on port 8080 so you can confirm the image works.


โ ๐Ÿท๏ธ Available Tags

โ Tags

Images are built weekly for the five newest Node.js LTS releases (from nodejs.org/dist/index.json) on each supported Alpine branch (the three newest stable branches plus edge):

TagMeaning
latestNewest LTS release on the latest stable Alpine branch
<major>.<minor>.<patch> (e.g. 24.19.0)Each built LTS release, on the latest stable Alpine branch
<major>.<minor>, <major> (e.g. 24.19, 24)Newest LTS release only, on the latest stable Alpine branch
alpine-<branch>-<major>.<minor>.<patch> (e.g. alpine-3.22-24.19.0)Each built LTS release on a specific Alpine branch (including edge)

โ โœจ Features

  • ๐ŸŸข Node.js LTS Runtime - Official Node.js binaries with npm and npx
  • ๐Ÿง Alpine Linux Base - Minimal, secure base OS
  • ๐Ÿ”ง S6 Overlay - Advanced process supervision and service management
  • ๐Ÿ‘ค Pre-configured User - Non-root tundra user (UID/GID: 1000)
  • ๐Ÿ“ฆ glibc compatibility shim - Node's official glibc binaries run on Alpine via a distroless glibc layer
  • ๐ŸŒ Timezone Support - Easy timezone configuration
  • โฐ Cron Support - Dynamic cron job loading with environment variables
  • ๐Ÿ“Š Health Monitoring - Built-in health checks for application monitoring
  • ๐Ÿ”„ envsubst - Environment variable substitution in config files

โ ๐Ÿ“– Usage

โ Basic Usage

Use as a base image in your Dockerfile. The application runs as the unprivileged tundra user (UID/GID 1000), so copy files with that ownership; otherwise the container recursively chowns /app at every boot, which is slow for a large node_modules.

Single-file app:

FROM tundrasoft/node:24

COPY --chown=tundra:tundra server.js /app/

ENV FILE=/app/server.js

package.json app:

FROM tundrasoft/node:24

COPY --chown=tundra:tundra package.json package-lock.json /app/
RUN npm ci && chown -R tundra:tundra /app
COPY --chown=tundra:tundra . /app

ENV SCRIPT=start

Pin the base image as tightly as you need:

FROM ghcr.io/tundrasoft/node:24            # GitHub Container Registry mirror
FROM tundrasoft/node:24.19.0               # exact Node.js release
FROM tundrasoft/node:alpine-3.22-24.19.0   # exact Node.js release on a specific Alpine branch
โ ๐ŸŽฏ Running Applications

The image decides what to run based on two environment variables:

  • SCRIPT โ€” run a script defined in package.json (npm run <SCRIPT>)
  • FILE โ€” run a single file directly (node <FILE>)

SCRIPT takes precedence over FILE. If neither is set, a minimal demo server is started on port 8080.

Run a file:

docker run -p 8080:8080 \
  -e FILE=/app/server.js \
  -v $(pwd):/app \
  tundrasoft/node:latest

Run a package.json script:

docker run -v $(pwd):/app \
  -e SCRIPT=start \
  tundrasoft/node:latest

Run with environment variables:

docker run -d \
  -e FILE=/app/server.js \
  -e NODE_OPTIONS=--max-old-space-size=512 \
  -e PUID=1001 \
  -e PGID=1001 \
  -e TZ=America/New_York \
  -v $(pwd):/app \
  tundrasoft/node:latest
โ Environment Variables
VariableDescriptionDefault
SCRIPTRun a script from package.json via npm run (takes precedence over FILE)N/A
FILEThe file to run directly with nodeN/A
NODE_ENVNode.js environment hint; also makes npm install/npm ci skip devDependenciesproduction
NODE_OPTIONSExtra Node.js CLI flags applied to every node process (e.g. --max-old-space-size=512)empty
NPM_CONFIG_CACHEnpm cache directory/npm-cache
NPM_CONFIG_UPDATE_NOTIFIERnpm "new version available" noticefalse
PUIDUser ID for the tundra user1000
PGIDGroup ID for the tundra group1000
TZTimezone (e.g., Asia/Kolkata, America/New_York)UTC
DEBUGEnable debug mode with verbose output (1 to enable)N/A
WATCHRestart on file changes via node --watch (1 to enable; FILE and demo modes only)N/A
S6_CMD_WAIT_FOR_SERVICES_MAXTIMEMax time (ms) to wait for services to start (0 = infinite)0
S6_KILL_FINISH_MAXTIMEGrace period (ms) for graceful shutdown5000

Any other NPM_CONFIG_* variable is honoured by npm as usual (for example NPM_CONFIG_LOGLEVEL=warn).

๐Ÿ“š Reference: Node.js CLI optionsโ  ยท npm configโ 

โ Volumes
PathDescription
/appApplication root directory (recommended to mount as volume)
/cronsDirectory for cron job files (automatically loaded)
/npm-cachenpm cache directory (NPM_CONFIG_CACHE, for persisting downloads)

โ ๐Ÿ—๏ธ Build-Time Optimization

โ Install dependencies during the build

Install dependencies while building the image to eliminate cold-start downloads:

Install from a lockfile (best layer caching):

FROM tundrasoft/node:24

# Copy manifests first so this layer is cached until they change
COPY --chown=tundra:tundra package.json package-lock.json /app/
RUN npm ci && chown -R tundra:tundra /app

COPY --chown=tundra:tundra . /app

ENV FILE=/app/index.js

NODE_ENV=production is set in the image, so npm ci installs only production dependencies. Pass --include=dev when a build step needs devDependencies.

Build with devDependencies, ship without them:

FROM tundrasoft/node:24 AS build

COPY package.json package-lock.json /app/
RUN npm ci --include=dev
COPY . /app
RUN npm run build

FROM tundrasoft/node:24

COPY --chown=tundra:tundra package.json package-lock.json /app/
RUN npm ci && chown -R tundra:tundra /app
COPY --from=build --chown=tundra:tundra /app/dist /app/dist

ENV FILE=/app/dist/index.js
โ Benefits
  • โšก Faster cold starts - No dependency downloads at runtime
  • ๐Ÿ“ฆ Reproducible builds - npm ci pins exact versions from the lockfile
  • ๐Ÿ”’ Offline compatible - Works in isolated environments
  • ๐ŸŽฏ Layer caching - Separate install layer for better Docker caching
  • ๐Ÿš€ Production ready - No surprise downloads in production

โ ๐Ÿ”ง Development Mode

โ Development setup

Development mode combines DEBUG, WATCH, and a volume mount for a fast feedback loop:

Development setup:

docker run -it \
  -e DEBUG=1 \
  -e WATCH=1 \
  -e NODE_ENV=development \
  -e FILE=/app/main.js \
  -v $(pwd):/app \
  -p 8080:8080 \
  tundrasoft/node:latest

What this enables:

  • ๐Ÿ› DEBUG=1: Verbose startup output with argument inspection
  • ๐Ÿ‘๏ธ WATCH=1: File watching with auto-restart on changes (node --watch)
  • ๐Ÿ“‹ NODE_ENV=development: Overrides the production default for frameworks that key off it

With a package.json script:

npm run does not forward --watch to node, so WATCH=1 is ignored in SCRIPT mode. Put the flag in the script instead:

{
  "scripts": {
    "dev": "node --watch src/main.js"
  }
}
docker run -it \
  -e DEBUG=1 \
  -e SCRIPT=dev \
  -v $(pwd):/app \
  -p 8080:8080 \
  tundrasoft/node:latest

โ โš™๏ธ Service Management

This image uses S6 Overlayโ  for advanced process supervision and service management. S6 is a lightweight init system that provides reliable service supervision, dependency management, and graceful shutdown handling.

The Node service runs your application via the S6 system, ensuring:

  • Automatic restart on failure
  • Graceful shutdown handling
  • Proper signal handling
  • Logging integration
  • Health monitoring
โ Service Startup & Shutdown Configuration

Control S6 service supervision timeouts:

# Custom startup timeout (30 seconds max wait)
docker run -d \
  -e S6_CMD_WAIT_FOR_SERVICES_MAXTIME=30000 \
  -e FILE=/app/server.js \
  tundrasoft/node:latest

# Extended graceful shutdown (10 seconds)
docker run -d \
  -e S6_KILL_FINISH_MAXTIME=10000 \
  -e FILE=/app/server.js \
  tundrasoft/node:latest

# Infinite startup wait (for slow-starting apps)
docker run -d \
  -e S6_CMD_WAIT_FOR_SERVICES_MAXTIME=0 \
  -e FILE=/app/server.js \
  tundrasoft/node:latest
โ ๐ŸŽฏ Service Triggers

S6 provides dependency management through trigger points:

TriggerDescription
os-readyContainer booted, basic setup complete
config-startStart configuration changes
config-readyConfiguration complete
service-startApplication services begin
service-readyAll services initialized
โ Adding Custom Services

You can extend the Node image with additional services:

FROM tundrasoft/node:latest

# Install additional tools
RUN apk add --no-cache redis

# Create Redis service
RUN mkdir -p /etc/s6-overlay/s6-rc.d/redis/dependencies.d
RUN echo "longrun" > /etc/s6-overlay/s6-rc.d/redis/type

RUN cat > /etc/s6-overlay/s6-rc.d/redis/run << 'EOF'
#!/command/with-contenv sh
exec 2>&1
exec redis-server --bind 127.0.0.1
EOF

RUN chmod +x /etc/s6-overlay/s6-rc.d/redis/run
RUN touch /etc/s6-overlay/s6-rc.d/redis/dependencies.d/service-start
RUN touch /etc/s6-overlay/s6-rc.d/user/contents.d/redis

โ โฐ Cron Jobs

โ Dynamic Cron Setup

This image provides dynamic cron job loading with environment variable substitution support:

  1. Create cron files in the /crons directory
  2. Use environment variables with $VARIABLE_NAME syntax
  3. Pass environment variables when running the container
  4. S6 automatically loads and installs jobs at startup
โ Cron Examples
โ Example 1: Basic Scheduled Task

File: /crons/daily-cleanup

# Run cleanup at 3 AM daily
0 3 * * * find /tmp -type f -mtime +7 -delete

Run container:

docker run -d \
  -v /host/crons:/crons:ro \
  tundrasoft/node:latest
โ Example 2: Application Health Check

File: /crons/health-check

# Check application health every 5 minutes
*/5 * * * * wget -q -O /dev/null http://127.0.0.1:8080/health || exit 1

Run container:

docker run -d \
  -p 8080:8080 \
  -e FILE=/app/server.js \
  -v /host/crons:/crons:ro \
  -v $(pwd):/app \
  tundrasoft/node:latest
โ Example 3: Complex Configuration

File: /crons/maintenance-jobs

# Database backup
$BACKUP_TIME /usr/local/bin/backup.sh >> /var/log/cron-backup.log 2>&1

# Log rotation
$LOG_ROTATE_TIME logrotate /etc/logrotate.conf

# Cleanup caches
$CLEANUP_TIME rm -rf /npm-cache/_logs/*

Run container with environment substitution:

docker run -d \
  -e BACKUP_TIME='0 2 * * *' \
  -e LOG_ROTATE_TIME='0 0 * * *' \
  -e CLEANUP_TIME='0 4 * * 0' \
  -v /host/crons:/crons:ro \
  tundrasoft/node:latest

โ ๐Ÿ”ง Building

โ ๐Ÿ—๏ธ Build Command
docker build \
  --build-arg ALPINE_VERSION=latest \
  --build-arg NODE_VERSION=24.19.0 \
  -t my-node-image .
โ โš™๏ธ Build Arguments
ArgumentDescriptionExample
ALPINE_VERSIONAlpine Linux version (base image)latest, 3.22, 3.21
NODE_VERSIONNode.js runtime version (official linux tarball)24.19.0, 22.18.0

Node's official Linux binaries are linked against glibc, so the image layers in the dynamic linker and runtime libraries from gcr.io/distroless/cc-debian12 (/usr/local/lib, with /lib and /lib64 loader symlinks and LD_LIBRARY_PATH set). 32-bit ARM (armv7) is not supported.

โ ๐Ÿงช Testing
docker build --build-arg NODE_VERSION=24.19.0 -t tundrasoft/node:test .
tests/smoke.sh tundrasoft/node:test 24.19.0

โ ๐Ÿ”’ Security

This repository implements comprehensive security scanning:

  • ๐Ÿ›ก๏ธ Multi-layered scanning with Trivy, CodeQL, Semgrep, and Grype
  • ๐Ÿ” Secret detection with GitLeaks (runs early in build process)
  • ๐Ÿ“Š Automated reporting to GitHub Security tab
  • ๐Ÿ”„ Daily security scans and vulnerability monitoring
โ Security Model

Node.js has no built-in permission sandbox enabled by default, so isolation is enforced at the container level rather than by the runtime. Node's experimental permission modelโ  can be opted into via NODE_OPTIONS when your application supports it.

For security issues, please use GitHub's private vulnerability reportingโ .

โ ๐Ÿ›ก๏ธ Security Best Practices

Container Runtime Security:

# Run with read-only root filesystem
docker run --read-only --tmpfs /tmp --tmpfs /run tundrasoft/node:latest

# Use specific user and drop capabilities
docker run --user 1000:1000 --cap-drop=ALL tundrasoft/node:latest

# Limit resources
docker run --memory=512m --cpus=1 --pids-limit=100 tundrasoft/node:latest

File System Security:

# Mount application files as read-only
docker run -v $(pwd):/app:ro tundrasoft/node:latest

# Mount secrets securely
docker run -v /host/secrets:/secrets:ro,Z tundrasoft/node:latest

Production Deployment:

# Always use specific version tags
docker run tundrasoft/node:24.19.0 # Not 'latest'

# Use custom networks
docker network create --driver bridge secure-app-net
docker run --network secure-app-net tundrasoft/node:24.19.0

# Enable logging
docker run --log-driver=json-file --log-opt max-size=10m tundrasoft/node:24.19.0

For security issues, please use GitHub's private vulnerability reportingโ .


โ ๐Ÿ“š Components

โ Base System
  • Alpine Linux - Minimal, secure, and reliable
  • S6 Overlay v3 - Process supervision with lifecycle management
  • OpenSSL 3.x - Cryptographic and TLS support
โ Runtime
  • Node.js LTS - Official nodejs.org Linux binaries
  • npm / npx - Bundled package manager (/usr/local/lib/node_modules/npm)
  • glibc shim - Loader and runtime libraries from gcr.io/distroless/cc-debian12
โ Utilities
  • wget - HTTP client (from the base image)
  • Bash/sh - Shell scripting
  • Healthcheck script - S6-integrated service monitoring

โ ๐Ÿ“– Reference

โ Container Lifecycle
StageDescriptionServices
BootInitialize system and useros-ready โ†’ service-ready
ConfigLoad configurationconfig-start โ†’ config-ready
MainRun application/cronnode or crond
ShutdownClean terminationS6 async handlers
โ Directory Structure
/npm-cache/       - npm cache directory (mounted volume)
/app/             - Application code
/usr/local/bin/   - node, npm, npx
/usr/local/lib/   - glibc shim + node_modules/npm
/etc/s6-overlay/  - S6 service definitions
/etc/crontabs/    - Cron jobs (if using cron)
/etc/timezone     - TZ configuration
/run/s6/          - S6 runtime (temporary)
โ Docker Compose Example
services:
  app:
    image: tundrasoft/node:latest
    environment:
      - FILE=/app/src/main.js
      - TZ=UTC
    volumes:
      - ./src:/app
      - npm-cache:/npm-cache
    ports:
      - "8000:8000"
    healthcheck:
      test: ["CMD", "/usr/bin/healthcheck.sh"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  npm-cache:
โ Troubleshooting
โ Application not starting

Symptoms: Container exits immediately or hangs

Debug steps:

# View logs to see startup errors
docker logs <container-id>

# Run with DEBUG mode for verbose output
docker run -it -e DEBUG=1 -e FILE=/app/main.js tundrasoft/node:latest

# Check healthcheck status
docker exec <container-id> /usr/bin/healthcheck.sh

# Verify file exists and is readable
docker exec <container-id> ls -la /app/main.js
โ "Cannot find module" errors

Symptoms: Error: Cannot find module 'express' (or any dependency)

Solutions:

# Ensure dependencies are installed into /app/node_modules
docker run -v $(pwd):/app -w /app --entrypoint="" tundrasoft/node:latest npm ci
# Or install dependencies during the build
FROM tundrasoft/node:24
COPY --chown=tundra:tundra package.json package-lock.json /app/
RUN npm ci && chown -R tundra:tundra /app
COPY --chown=tundra:tundra . /app
ENV FILE=/app/main.js
โ Missing devDependencies

Symptoms: A build tool (e.g. tsc, vite) is not found during npm run build

Cause: NODE_ENV=production is set in the image, so npm ci/npm install skip devDependencies.

Solution:

RUN npm ci --include=dev
โ Slow cold start

Symptoms: First run takes a long time to download dependencies

Solution:

# Persist the npm cache using a volume
docker run -v npm-cache:/npm-cache \
  -e SCRIPT=start tundrasoft/node:latest
โ Watch mode not restarting

Symptoms: File changes don't trigger app restart with WATCH=1

Check:

# Verify watch mode is working (FILE mode only)
docker run -it -e WATCH=1 -e DEBUG=1 \
  -e FILE=/app/main.js \
  -v $(pwd):/app \
  tundrasoft/node:latest

# Look for "Restarting" messages in logs. In SCRIPT mode, add --watch to the
# script in package.json instead; WATCH=1 is ignored there.

โ ๐Ÿค Contributing

  1. ๐Ÿด Fork the repository
  2. ๐ŸŒŸ Create a feature branch: git checkout -b feature/amazing-feature
  3. ๐Ÿ’พ Commit changes: git commit -m 'Add amazing feature'
  4. ๐Ÿ“ค Push to branch: git push origin feature/amazing-feature
  5. ๐Ÿ”„ Open a Pull Request
โ ๐Ÿ“‹ Changelog

See CHANGELOG.mdโ  for release notes and CHANGELOG-GUIDE.mdโ  for contribution guidelines.


Built with โค๏ธ by TundraSoftโ 

View on GitHubโ  โ€ข Docker Hubโ  โ€ข Report Issueโ 

Tag summary

Content type

Image

Digest

sha256:9722c281bโ€ฆ

Size

58 MB

Last updated

3 days ago

docker pull tundrasoft/node