Containers for CLI Tools:When a Box Helps
Not every CLI needs a container. Some benefit from one: complex native dependencies, locked runtimes, or identical behaviour in CI and laptops. Containers are a distribution and isolation tool, not automatic security.
When a Container Earns Its Keep
Good reasons:
- Native libraries that are painful to install
- Guaranteed runtime version
- CI jobs that should not pollute the runner
- Customers who already standardise on images
Bad reasons:
- "Everyone uses Docker"
- Hiding a broken install story forever
- Running as root because the Dockerfile was copied from a wiki
Prefer a plain package (pip/npm/brew) when it works.
Pin What You Build From
- Pin base image digests or immutable tags
- Rebuild deliberately; do not float on
latest - Scan images in CI when you publish them
- Prefer minimal bases (distroless/alpine/slim) when compatible
Your image is a dependency tree in a trench coat.
Run as Non-Root
Drop root in the final image. Mount only the directories the tool needs. Read-only root filesystem when practical. Least privilege applies inside containers too.
Keep the Attack Surface Small
- Multi-stage builds so compilers never ship
- No leftover secrets in layers (
docker historyremembers) - Explicit entrypoint; avoid kitchen-sink images
- Document required volume mounts and ports (usually none for CLIs)
Version and Sign Like Other Artefacts
Tag images with the same version as the CLI release. Publish digests. Sign or attest when your pipeline supports it. Users should verify what they pull.
Document Escape Hatches
Explain how to run with local files mounted, how to pass config, and when not to use the image. A container that fights host keychains and sockets needs clear guidance.
Closing Thoughts
Containers can make CLI distribution boring and repeatable. Pin bases, run as non-root, keep layers lean, and still offer a non-container install when you can. The box should help users, not become the product.