Commonly used CLI tools exposed through Podman shims for POSIX-oriented shells.
Shimmy wraps popular CLI tools in lightweight Podman containers, providing:
- No local installations required — tools run in containers
- Consistent environments across different machines and projects
- Customizable — override container images via environment variables
- Transparent usage — add to PATH and use tools as if they were installed locally
For tools that do not ship a usable upstream container image, Shimmy can build and cache a local image from a checked-in Containerfile context. The image tag is derived from the build-context hash, so Podman reuses the cached image until the Containerfile or its supporting files change.
Contributor guidance lives in CONTRIBUTING.md.
That document is the contributor source of truth, including naming conventions for files, functions, and variables. It is also referenced from AGENTS.md and the shared project prompt so future AI contributors pick it up automatically.
| Tool | Purpose | Default Image | Usage |
|---|---|---|---|
| aws | AWS CLI | public.ecr.aws/aws-cli/aws-cli:2.31.21 |
aws s3 ls, aws sts get-caller-identity |
| go | Go toolchain CLI | docker.io/library/golang:latest |
go version, go test ./... |
| jq | JSON processor | docker.io/stedolan/jq:latest |
jq .foo file.json |
| netcat | TCP/UDP debugging client | local build from images/netcat/Containerfile |
netcat --help, netcat example.com 443 |
| rg | Ripgrep search | docker.io/vszl/ripgrep:latest |
rg "pattern" . |
| task | Taskfile task runner | local build from images/task/Containerfile |
task --version, task --list |
| terraform | Infrastructure as Code | docker.io/hashicorp/terraform:latest |
terraform plan, terraform apply |
| textual | Textual developer CLI | local build from images/textual/Containerfile |
textual --help, textual run app.py |
- POSIX shell —
/bin/shor another POSIX-compatible shell for the current proof-of-concept rewrite - Podman CLI — Explicit required dependency. Podman Desktop is not required.
For macOS run
podman machine initandpodman machine startafter installation. Install and configure for rootless operation separately before using Shimmy. Official install guide: https://podman.io/docs/installation If Podman is installed from the macOS pkg installer, the binary may live at/opt/podman/bin/podman.shimmy activateaccounts for that path for interactive shell activation, and Shimmy's shared Podman preflight also checks it directly for runtime shims plus Podman-backed lifecycle commands such asshimmy update --pull,shimmy update --build, andshimmy test. When Podman is installed but unreachable, Shimmy now fails with shared guidance that points topodman info,podman machine start,podman system connection list, andCONTAINER_HOSTverification.
Shimmy expects a working rootless Podman engine setup. On some minimal Linux environments, including Chromebook's Crostini, rootless requirements for subordinate id ranges do not exist. In this scenario Podman will warn "no subuid ranges found" and fall back to a single UID/GID mapping.
Check your configuration (should output a range of id values, eg: 10000:65536):
grep "^$(whoami):" /etc/subuid /etc/subgid
When only a single id is present run this command to correct.
- sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $(whoami)
- podman system migrate
Use the repo-root shimmy wrapper as the primary control surface:
./shimmy install
./shimmy status
./shimmy update --pull --build
./shimmy test
./shimmy uninstallThe wrapper delegates to script-based interfaces in scripts/.
After ./shimmy install, activate the installed Shimmy paths in the current shell immediately with:
eval "$(./shimmy activate)"./shimmy install updates your shell startup file by default so shells can find the installed shims automatically. It cannot change your current shell session, so use eval "$(./shimmy activate)" to make the install available immediately.
By default, install chooses the startup file from your shell:
bash->~/.bashrczsh->~/.zshrcsh,ksh, andmksh->~/.profile
You can override or skip that behavior:
./shimmy install --shell zsh
./shimmy install --startup-file "$HOME/.config/shimmy/profile"
./shimmy install --no-startupShimmy writes one managed startup block, so rerunning install refreshes that block idempotently instead of appending duplicates.
If you prefer not to modify your startup files, use --no-startup and add activation logic manually.
./shimmy install --no-startup
eval "$(./shimmy activate)"If your startup file ever needs to be rewritten or repaired later, use update:
./shimmy update --repair-startup
./shimmy update --repair-startup --shell zsh
./shimmy update --repair-startup --startup-file "$HOME/.config/shimmy/profile"Without --repair-startup, update refreshes installed assets only and leaves startup files alone.
The active install layout is always derived from one install root, which defaults to ~/.config/shimmy and can be overridden with --install-dir.
Common install arguments still pass through to the installer:
./shimmy install --install-dir "$HOME/.local/share/shimmy"
./shimmy install --shim aws --shim terraformUse the underlying scripts directly when you want the lower-level interfaces explicitly:
sh ./scripts/install-shimmy.sh
sh ./scripts/status-shimmy.sh
sh ./scripts/update-shimmy.sh --pull --build
sh ./scripts/test-shimmy.sh
sh ./scripts/install-shimmy.sh --uninstallThis is the same functionality the wrapper exposes, without the repo-root dispatcher.
Once shims are in your PATH, use tools naturally:
# Terraform
terraform version
terraform -chdir=examples/dev plan
# AWS CLI
aws s3 ls
aws sts get-caller-identity
# Go CLI
go version
go test ./...
# jq
echo '{"name": "shimmy"}' | jq .name
# ripgrep
rg "pattern" .
# Task
task --version
task --list
# Textual CLI
textual --help
textual run app.pyEach shim respects environment variables for customization.
AWS_IMAGE— Container image (default:public.ecr.aws/aws-cli/aws-cli:2.31.21)AWS_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
AWS_IMAGE=public.ecr.aws/aws-cli/aws-cli:2.31.21 aws --versionMounts:
$PWD→/work(read-write)~/.aws→/root/.aws(read-only, if exists)
Environment variables forwarded:
AWS_*
GO_IMAGE— Container image (default:docker.io/library/golang:latest)GO_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
GO_IMAGE=docker.io/library/golang:latest go versionMounts:
$PWD→/work(read-write)
JQ_IMAGE— Container image (default:docker.io/stedolan/jq:latest)JQ_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
JQ_IMAGE=ghcr.io/jqlang/jq:latest jq --versionMounts:
$PWD→/work(read-write)
NETCAT_IMAGE— Override the runtime image entirelyNETCAT_IMAGE_BUILD— Set toalwaysto rebuild the local Netcat image even if it is already cachedNETCAT_IMAGE_PULL— Set toalwaysto force pullingNETCAT_IMAGEwhen using an explicit remote overrideNETCAT_BASE_IMAGE— Override theContainerfilebase image (default build arg:registry.access.redhat.com/ubi9/ubi-minimal:latest)
Example:
NETCAT_BASE_IMAGE=registry.access.redhat.com/ubi9/ubi-minimal:latest netcat --helpThe default Netcat image is built locally from images/netcat/Containerfile, which starts from UBI 9 minimal and installs the nmap-ncat package. This keeps the base image small while still using a practical Red Hat-supported package manager for the install. Shimmy tags the resulting image under localhost/shimmy-netcat:<context-hash> so Podman keeps a reusable local cache and automatically rebuilds when the build context changes.
Mounts:
$PWD→/work(read-write)
RG_IMAGE— Container image (default:docker.io/vszl/ripgrep:latest)RG_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
RG_IMAGE=docker.io/vszl/ripgrep:latest rg --versionMounts:
$PWD→/work(read-write)
TASK_IMAGE— Override the runtime image entirelyTASK_IMAGE_BUILD— Set toalwaysto rebuild the local Task image even if it is already cachedTASK_IMAGE_PULL— Set toalwaysto force pullingTASK_IMAGEwhen using an explicit remote overrideTASK_BASE_IMAGE— Override theContainerfilebase image (default build arg:alpine:3.22)TASK_VERSION— Override the Task release version installed into the local image (default build arg:v3.45.5)
Example:
TASK_VERSION=v3.45.5 task --versionThe default Task image is built locally from images/task/Containerfile, which starts from Alpine and installs the official Task release binary from GitHub Releases. Shimmy tags the resulting image under localhost/shimmy-task:<context-hash> so Podman keeps a reusable local cache and automatically rebuilds when the build context changes.
Mounts:
$PWD→$PWD(read-write)$PWD→/work(read-write)$HOME→$HOME(read-write, if it exists)/tmp→/tmp(read-write, if it exists)
When CONTAINER_HOST points at a unix-domain Podman socket, the task shim also forwards that socket into the container so Task-driven automation can launch other shims.
Environment variables forwarded:
CONTAINER_HOSTwhen explicitly setSHIMMY_HOST_PATHHOMEwhen the home directory mount is enabled
TF_IMAGE— Container image (default:docker.io/hashicorp/terraform:latest)TF_IMAGE_PULL— Set toalwaysto force pulling the latest image
Example:
TF_IMAGE=docker.io/hashicorp/terraform:latest
TF_IMAGE_PULL=always
terraform version
terraform planMounts:
$PWD→/work(read-write)~/.aws→/root/.aws(read-only, if exists)~/.terraform.d/plugin-cache→/root/.terraform.d/plugin-cache(if exists)
Environment variables forwarded:
AWS_*TF_VAR_*
TEXTUAL_IMAGE— Override the runtime image entirelyTEXTUAL_IMAGE_BUILD— Set toalwaysto rebuild the local Textual image even if it is already cachedTEXTUAL_IMAGE_PULL— Set toalwaysto force pullingTEXTUAL_IMAGEwhen using an explicit remote overrideTEXTUAL_BASE_IMAGE— Override theContainerfilebase image (default build arg:python:3.13-slim-bookworm)
Example:
TEXTUAL_BASE_IMAGE=python:3.13-slim-bookworm textual --helpThe default Textual image is built locally from images/textual/Containerfile, which starts from python:3.13-slim-bookworm and installs textual plus textual-dev. This matches the official Textual docs, where the textual command comes from the developer tools package. Shimmy tags the resulting image under localhost/shimmy-textual:<context-hash> so Podman keeps a reusable local cache and automatically rebuilds when the build context changes.
Mounts:
$PWD→/work(read-write)
The interactive shims (aws, task, terraform, and textual) request -it only when both stdin and stdout are attached to a terminal, so version and help commands still work cleanly in scripts and smoke tests.
Run the test suite to validate that shim containers run via Podman:
./shimmy test
# or
sh ./scripts/test-shimmy.shTests verify:
/bin/shparser compatibility for the repo wrapper, shared shim helpers, repo lifecycle scripts, and all supported in-scope shims- install, activate, status, update, startup-file repair, and uninstall behavior for the single-root manifest layout
- live Podman execution for the supported shim set:
aws,jq,netcat,rg,task,terraform, andtextual
shimmy/
├── shimmy # Repo-root wrapper command
├── shims/ # OCI wrapper scripts
│ ├── aws
│ ├── jq
│ ├── netcat
│ ├── rg
│ ├── task
│ ├── textual
│ └── terraform
├── images/ # Custom shim image build contexts
│ ├── netcat
│ ├── task
│ └── textual
├── lib/
│ ├── repo/ # Repo-only sourced helpers for wrapper/scripts
│ └── shims/ # Installed shared helper scripts for shims
├── scripts/
│ ├── install-shimmy.sh # Installation script
│ ├── status-shimmy.sh # Status script
│ ├── test-shimmy.sh # Test suite
│ └── update-shimmy.sh # Update script
├── .pre-commit-config.yaml # Git https://github.com/pre-commit/pre-commit-hooks
├── .github/
│ └── workflows/
│ └── test.yml # CI/CD workflow
└── README.md # This file
This code was and human-reviewed/curated in concert with Codex GPT-5.4.
See LICENSE file for details.