Set up this repo as a base image registry. Each image is self-contained in
its own directory under images/ (Dockerfile + README.md + optional test.sh);
CI discovers them by glob, so adding an image needs no workflow changes.
template/ is the skeleton to copy.
The Makefile is the single entry point for both local work and CI, so a green
`make all` locally means a green pipeline.
Workflows:
* lint.yaml — layout check, hadolint, shellcheck
* build.yaml — diffs against the base commit to build only the images that
changed, smoke-tests each one before anything is published, then pushes.
Releases are per-image tags (<image>/vX.Y.Z); main publishes :edge.
First image, node-agent: node:22-alpine plus a GNU userland (Alpine ships
BusyBox, whose applets take narrower flags than scripts and models expect),
helm, kubectl, jq, yq, bind-tools, curl, git, ripgrep, fd and friends.
Nothing is version-pinned — rebuilding is how upstream updates land, and the
published tag is what pins things for consumers.
Its smoke test asserts the deployment contract as well as tool presence: the
image must work non-root, with a read-only root filesystem and all
capabilities dropped, which is how ToolHive runs it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
node-agent
A general-purpose command execution environment for AI agents: Node 22 on Alpine, with the shell, text, network and Kubernetes tooling an agent actually reaches for already installed.
gitea.libretechconsulting.com/rmcguire/node-agent
Alpine's stock userland is BusyBox, whose applets accept a narrower set of flags
than the GNU tools most scripts and models assume. This image replaces the ones
that matter with their GNU equivalents, so grep -P, sed -i, find -printf,
sort -h and friends behave as expected.
Base: node:22-alpine · ~400 MB uncompressed (kubectl and helm are about
110 MB of that)
Contents
| Group | Tools |
|---|---|
| Shell | bash, GNU coreutils, findutils, diffutils, gawk, grep, sed |
| Text & search | jq, yq, ripgrep (rg), fd, less, file, patch, tree |
| Network | curl, wget, dig/nslookup/host (bind-tools), nc, socat, rsync, ping, openssl |
| Kubernetes | helm, kubectl |
| Source control | git, git-lfs, ssh/scp/sftp |
| Archives | tar, gzip, xz, zip, unzip |
| System | ps/top/free (procps-ng), make, su-exec, tini, tzdata, ca-certificates |
| Runtime | node, npm, npx (from the base image) |
Versions track Alpine's repositories and upstream releases at build time — nothing
is pinned. Run <tool> --version in the image for what a given tag actually shipped.
yq comes from upstream releases because Alpine does not package it; everything
else is an Alpine package.
Environment
| Variable | Value | Why |
|---|---|---|
PAGER, GIT_PAGER |
cat |
git, kubectl and helm otherwise block on less when there is no TTY |
GIT_TERMINAL_PROMPT |
0 |
a remote needing credentials fails fast instead of hanging on a prompt |
HELM_CACHE_HOME, HELM_CONFIG_HOME, HELM_DATA_HOME |
under /tmp |
lets helm run with a read-only root filesystem |
ENTRYPOINT is tini -- docker-entrypoint.sh, so orphaned grandchildren of a
shell command get reaped. CMD is bash.
Usage
docker run --rm -it gitea.libretechconsulting.com/rmcguire/node-agent:latest
The image is built to run locked down, and is tested that way — non-root, read-only root filesystem, all capabilities dropped:
docker run --rm -it \
--user 1000:1000 --read-only --tmpfs /tmp --cap-drop ALL \
-e HOME=/tmp \
gitea.libretechconsulting.com/rmcguire/node-agent:latest
In Kubernetes, /tmp must be a writable volume; uid 1000 is the base image's
node user. See toolhive/mcpserver-shell.yaml in the 50W/kube-manifests repo
for the deployed example.
Notes
pingneedsCAP_NET_RAW. Withcapabilities: drop: [ALL]it fails;dig,curlandncare the alternatives that work without it.- No
kubeconfigis baked in.kubectlandhelmare present as tools; they only reach a cluster if credentials are mounted. - No shell completions. They are dead weight in a non-interactive environment.
Extending
The image does not set USER, so it builds as root just like its base — add what
you need and let the deployment enforce non-root at runtime.
FROM gitea.libretechconsulting.com/rmcguire/node-agent:latest
# hadolint ignore=DL3018
RUN apk add --no-cache sops age