Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Static checks for the repository.
#
# - shellcheck: every shell script (build.sh, entrypoint-containerd.sh,
# tools/transfer.sh, scripts/*.sh, tests/*.sh). scripts/lib.sh is sourced by
# tools/*.sh, scripts/*.sh, tests/*.sh). scripts/lib.sh is sourced by
# the install scripts, which point shellcheck at it with a source directive.
# - hadolint: Dockerfile and Dockerfile.slim, with the rules in .hadolint.yaml.
# hadolint 2.12 cannot parse `--mount=type=secret,...,env=...`; 2.14 can.
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
shellcheck --external-sources --source-path=SCRIPTDIR \
build.sh \
entrypoint-containerd.sh \
tools/transfer.sh \
tools/*.sh \
scripts/*.sh \
tests/*.sh

Expand Down
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ Both Dockerfiles start with a named `uv` stage (`FROM ghcr.io/astral-sh/uv:<ver>
```text
uv (ghcr.io/astral-sh/uv@sha256) ──COPY /uv /uvx──┐
▼
debian:trixie@sha256 → base → docker
debian:trixie-slim@sha256 → base → docker
→ podman
→ nerdctl
→ containerd
```

The base image is pinned as `debian:trixie@sha256:<digest>` (tag kept so Dependabot bumps the digest), in both Dockerfiles. `.github/workflows/dependabot-auto-merge.yml` never auto-merges docker-ecosystem PRs: the gatekeeper does not build Dependabot PRs, so a maintainer approves, re-runs the build (smoke test) and merges by hand.
The base image is pinned as `debian:trixie-slim@sha256:<digest>` (the slim variant: no docs, man pages or translations in the base layer) (tag kept so Dependabot bumps the digest), in both Dockerfiles. `.github/workflows/dependabot-auto-merge.yml` never auto-merges docker-ecosystem PRs: the gatekeeper does not build Dependabot PRs, so a maintainer approves, re-runs the build (smoke test) and merges by hand.

- **`base`**: Installs all networking/system tools, the upstream-release binaries, Zsh with Oh My Zsh + Powerlevel10k, plugins, and helper scripts
- **`docker`**: Docker CLI only (`docker-ce-cli`, `docker-buildx-plugin`, `docker-compose-plugin`) from Docker's official apt repo. No `dockerd`, no `containerd.io`: users mount the host socket (`-v /var/run/docker.sock:/var/run/docker.sock`)
Expand Down Expand Up @@ -136,7 +136,8 @@ Version override build args (all default to empty = latest):
- **Shell**: `SHELL ["/bin/bash", "-o", "pipefail", "-c"]` in the base stage, and heredoc RUN bodies use `set -eux`.
- **gitstatus binary persistence**: The Powerlevel10k gitstatus install uses `GITSTATUS_CACHE_DIR=$ZSH/custom/themes/powerlevel10k/gitstatus/usrbin`, a path *outside* the `/root/.cache` cache mount. Without this, the compiled binary would live only in the cache mount and be absent from the final image layer.
- **Locale**: `LANG=C.UTF-8`, the only UTF-8 locale built into Debian without the `locales` package (`en_US.UTF-8` is not generated, and `man` complained about it on every call).
- **man pages**: `man-db` is installed in the full image. Before the install, any dpkg `path-exclude=/usr/share/man` rule in `/etc/dpkg/dpkg.cfg.d/` is removed so pages actually land (the pinned image has none today; it is a guard).
- **man pages**: `man-db` is installed in the full image. Before the install, any dpkg `path-exclude /usr/share/man` rule in `/etc/dpkg/dpkg.cfg.d/` is removed so pages actually land. debian:trixie-slim ships one in the space-separated form, so the sed matches both `path-exclude=` and `path-exclude `. Man pages of packages already in the base layer (bash, coreutils...) stay missing.
- **dpkg excludes**: `configs/dpkg-excludes` is copied to `/etc/dpkg/dpkg.cfg.d/netshoot-excludes` in both Dockerfiles before any apt step, so `/usr/share/doc` (except `copyright`) and `/usr/share/locale` are skipped. Man pages are not excluded. Use `mtr-tiny`, never `mtr` (GTK build): the smoke test fails if `libgtk-3-0t64` is installed.

### Shell Layout (works for root and any uid)

Expand All @@ -155,6 +156,9 @@ Version override build args (all default to empty = latest):
- `configs/p10k.zsh` — Powerlevel10k theme configuration (installed as `/etc/zsh/netshoot/.p10k.zsh`)
- `configs/podman-storage.conf` — Podman storage config for rootless operation
- `tools/transfer.sh` — File transfer helper script (installed to `/usr/local/bin/transfer.sh`)
- `tools/install-dockerd.sh`: docker variant only, installed as `/usr/local/bin/install-dockerd`: installs `docker-ce` + `containerd.io` on demand from the already configured Docker apt repo; `--start` launches dockerd and waits for it (needs `--privileged`)
- `configs/motd/`: message of the day fragments, copied to `/etc/netshoot/motd.d/` (`10-netshoot` common text, `50-variant` replaced by each variant stage, `90-footer`); `configs/zshrc` prints them before the p10k instant prompt, on a tty only, once per `docker run`/`docker exec` (exported guard `_NETSHOOT_MOTD_SHOWN`), never with `NETSHOOT_NO_MOTD` set
- `configs/dpkg-excludes`: dpkg path-exclude rules (docs and translations), see Key Design Patterns
- `entrypoint-containerd.sh` — Entrypoint for containerd variant (starts containerd daemon, then exec's the user command)

### Installation Scripts
Expand Down Expand Up @@ -269,10 +273,10 @@ The CI workflow uses `type=gha` (GitHub Actions cache, `scope=buildkit-<variant>
There is no `make`, `npm`, or test runner; the checks are plain scripts and CI workflows.

- **Smoke test**: `tests/smoke.sh <variant> <image>` (variants: `base`, `slim`, `docker`, `podman`, `nerdctl`, `containerd`) runs every check in `docker run --rm --entrypoint /bin/bash <image> -c '...'` (entrypoint overridden so the containerd variant needs no privileges) and reports PASS/FAIL per check, exiting 1 on any failure. It checks: expected binaries present and removed ones (dstat, sudo) absent (pip/pip3 are not checked: `httpie` and `python3-venv` depend on `python3-pip` in trixie, so pip stays installed), per variant; functional checks (grpcurl, witr, doggo, websocat, kubectl, `check-tls --help` as uid 1000, `python3 -m venv`, `man -w tcpdump` on the full image); an interactive zsh as root and as `--user 1000:1000`, both without a terminal and on a `docker run -t` pty (`timeout --foreground -k`), which must print a probe marker proving the bundled config loaded (`ZSH=/opt/oh-my-zsh`, `p10k` defined, `ZSH_COMPDUMP` set), with no error output and no newuser/p10k wizard; variant tools (docker CLI + buildx + compose with `dockerd` absent, podman, nerdctl, containerd + nerdctl). Run it on a locally built image; CI runs it for every variant before pushing.
- **Lint** (`.github/workflows/lint.yaml`, on `pull_request` and push to `main`, `contents: read`): shellcheck on `build.sh`, `entrypoint-containerd.sh`, `tools/transfer.sh`, `scripts/*.sh` and `tests/*.sh` (`scripts/lib.sh` is sourced, the install scripts point shellcheck at it with a `source` directive); hadolint on `Dockerfile` and `Dockerfile.slim` with `.hadolint.yaml` (it ignores no rule globally: rules are ignored inline with `# hadolint ignore=<rule>` above the instruction, with a justification comment); actionlint on the workflows. Linter binaries are pinned by version and SHA256 in the workflow.
- **Lint** (`.github/workflows/lint.yaml`, on `pull_request` and push to `main`, `contents: read`): shellcheck on `build.sh`, `entrypoint-containerd.sh`, `tools/*.sh`, `scripts/*.sh` and `tests/*.sh` (`scripts/lib.sh` is sourced, the install scripts point shellcheck at it with a `source` directive); hadolint on `Dockerfile` and `Dockerfile.slim` with `.hadolint.yaml` (it ignores no rule globally: rules are ignored inline with `# hadolint ignore=<rule>` above the instruction, with a justification comment); actionlint on the workflows. Linter binaries are pinned by version and SHA256 in the workflow.
- README.md must stay markdownlint-clean per `.markdownlint.json` (80-column lines outside code blocks and tables).

All shell scripts (`build.sh`, `entrypoint-containerd.sh`, `tools/transfer.sh`, `scripts/*.sh`, `tests/*.sh`) use `bash` and must pass shellcheck.
All shell scripts (`build.sh`, `entrypoint-containerd.sh`, `tools/*.sh`, `scripts/*.sh`, `tests/*.sh`) use `bash` and must pass shellcheck.

## Development Notes

Expand Down
38 changes: 32 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ FROM ghcr.io/astral-sh/uv:0.12.18@sha256:3adc3706091ce7c2fe595e669628caedd6d9515
# Base Stage: Debian Trixie with Essential Tools and Shell Enhancements
# ==============================================================================
# The tag is kept next to the digest so Dependabot can bump the digest.
FROM debian:trixie@sha256:9cc080028c43b27d2074d63a5f9caf7166d731494965616c1a6d2827a004585c AS base
FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS base

ARG TARGETARCH

Expand All @@ -48,6 +48,11 @@ RUN <<EOT
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
EOT

# Do not install package docs and message translations (~150 MB): nobody reads
# /usr/share/doc in a debug container. Copyright files stay for license
# compliance. Man pages are kept on purpose (man-db is installed below).
COPY --link configs/dpkg-excludes /etc/dpkg/dpkg.cfg.d/netshoot-excludes

# Upgrade all packages to their latest versions.
# Uses BuildKit cache mounts with sharing=locked for parallel builds safety.
# CACHE_BUST (the ISO week, passed by CI and build.sh) invalidates this layer
Expand All @@ -70,9 +75,11 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
set -eux

# Make sure man pages are not excluded by a dpkg path-exclude rule, so that
# man-db has something to index. The pinned debian:trixie image ships no such
# rule today: this is a no-op guard in case a future digest adds one.
sed -i '\|^path-exclude=/usr/share/man|d' /etc/dpkg/dpkg.cfg.d/*
# man-db has something to index. debian:trixie-slim ships such a rule
# (`path-exclude /usr/share/man/*`, space-separated); dpkg also accepts the
# `=` form, so match both. Man pages of packages already in the base layer
# stay missing; every package installed below gets its pages.
sed -i -E '\|^path-exclude[= ]/usr/share/man|d' /etc/dpkg/dpkg.cfg.d/*

# Core system tools (rarely change)
CORE_TOOLS=(
Expand Down Expand Up @@ -104,14 +111,14 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
jq
kitty-terminfo
lsof
magic-wormhole
ncdu
python3-venv
rsync
strace
sysstat
thefuck
tmux
wormhole-rs
)

# Networking tools
Expand Down Expand Up @@ -139,7 +146,7 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
ipvsadm
ldap-utils
masscan
mtr
mtr-tiny
net-tools
netcat-openbsd
nethogs
Expand Down Expand Up @@ -174,6 +181,10 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
"${CORE_TOOLS[@]}" \
"${SYSTEM_TOOLS[@]}" \
"${NETWORKING_TOOLS[@]}"

# wormhole-rs replaces the Python magic-wormhole (and its Twisted stack);
# keep the usual `wormhole` command name. Same send/receive protocol.
ln -s /usr/bin/wormhole-rs /usr/local/bin/wormhole
EOT

# Install official Ookla speedtest CLI.
Expand Down Expand Up @@ -298,6 +309,12 @@ COPY --link configs/zshrc /etc/zsh/netshoot/.zshrc
COPY --link configs/p10k.zsh /etc/zsh/netshoot/.p10k.zsh
COPY --link --chmod=755 tools/transfer.sh /usr/local/bin/transfer.sh

# Message of the day, printed by the zshrc in name order. Each variant stage
# replaces 50-variant with its own notes.
COPY --link configs/motd/netshoot /etc/netshoot/motd.d/10-netshoot
COPY --link configs/motd/variant-base /etc/netshoot/motd.d/50-variant
COPY --link configs/motd/footer /etc/netshoot/motd.d/90-footer

# Set the working directory and default command.
WORKDIR /root
CMD ["zsh"]
Expand Down Expand Up @@ -341,6 +358,11 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
docker-compose-plugin
EOT

# On-demand engine: `install-dockerd [--start]` installs docker-ce and
# containerd.io from the repository configured above (needs --privileged to run).
COPY --link --chmod=755 tools/install-dockerd.sh /usr/local/bin/install-dockerd
COPY --link configs/motd/variant-docker /etc/netshoot/motd.d/50-variant

# ==============================================================================
# Podman Stage: Installs Podman and related tools
# ==============================================================================
Expand All @@ -364,6 +386,7 @@ EOT

# Copy Podman storage configuration file.
COPY --link configs/podman-storage.conf /root/.config/containers/storage.conf
COPY --link configs/motd/variant-podman /etc/netshoot/motd.d/50-variant

# ==============================================================================
# nerdctl Stage: Installs nerdctl (client only version)
Expand All @@ -383,6 +406,8 @@ RUN --mount=type=bind,source=scripts,target=/tmp/scripts \
--mount=type=secret,id=github_token,env=GITHUB_TOKEN \
bash /tmp/scripts/install-nerdctl.sh client

COPY --link configs/motd/variant-nerdctl /etc/netshoot/motd.d/50-variant

# ==============================================================================
# Containerd/nerdctl Stage: Installs nerdctl (full version with containerd)
# ==============================================================================
Expand All @@ -403,5 +428,6 @@ RUN --mount=type=bind,source=scripts,target=/tmp/scripts \

# Copy the entrypoint script for containerd.
COPY --link --chmod=755 entrypoint-containerd.sh /entrypoint.sh
COPY --link configs/motd/variant-containerd /etc/netshoot/motd.d/50-variant

ENTRYPOINT ["/entrypoint.sh"]
12 changes: 10 additions & 2 deletions Dockerfile.slim
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ FROM ghcr.io/astral-sh/uv:0.12.18@sha256:3adc3706091ce7c2fe595e669628caedd6d9515
# Base Stage: Debian Trixie with a focused set of tools
# ==============================================================================
# The tag is kept next to the digest so Dependabot can bump the digest.
FROM debian:trixie@sha256:9cc080028c43b27d2074d63a5f9caf7166d731494965616c1a6d2827a004585c AS base
FROM debian:trixie-slim@sha256:a29215f6a35e51e22adffa17f89e9d2ef06214e64a2bad10d765c46aea49f11f AS base

ARG TARGETARCH

Expand All @@ -41,6 +41,10 @@ RUN <<EOT
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
EOT

# Do not install package docs and message translations; copyright files stay
# for license compliance. Same rules as the full image.
COPY --link configs/dpkg-excludes /etc/dpkg/dpkg.cfg.d/netshoot-excludes

# Upgrade all packages to their latest versions.
# Uses BuildKit cache mounts with sharing=locked for parallel builds safety.
# CACHE_BUST (the ISO week, passed by CI and build.sh) invalidates this layer
Expand Down Expand Up @@ -74,7 +78,7 @@ RUN --mount=type=cache,target=/var/cache/apt,id=apt-cache-${TARGETARCH},sharing=
iproute2
iputils-ping
iputils-tracepath
mtr
mtr-tiny
net-tools
netcat-openbsd
nmap
Expand Down Expand Up @@ -190,6 +194,10 @@ COPY --link configs/zshrc /etc/zsh/netshoot/.zshrc
COPY --link configs/p10k.zsh /etc/zsh/netshoot/.p10k.zsh
COPY --link --chmod=755 tools/transfer.sh /usr/local/bin/transfer.sh

# Message of the day, printed by the zshrc in name order (slim text and footer).
COPY --link configs/motd/slim /etc/netshoot/motd.d/10-netshoot
COPY --link configs/motd/footer /etc/netshoot/motd.d/90-footer

# Set the working directory and default command.
WORKDIR /root
CMD ["zsh"]
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ variants extend from a common `base` stage:
```mermaid
flowchart TB
U["ghcr.io/astral-sh/uv (pinned)"] -. "COPY uv, uvx" .-> B
A["debian:trixie (pinned digest)"] --> B["base"]
A["debian:trixie-slim (pinned digest)"] --> B["base"]
B --> C["docker"]
B --> D["podman"]
B --> E["nerdctl"]
Expand Down Expand Up @@ -200,6 +200,27 @@ The `docker` variant ships the client only
`containerd` in the image. Mount the socket of the
engine you want to talk to.

When you do need a local engine, `install-dockerd`
installs `docker-ce` and `containerd.io` on demand
from the Docker repository already configured in the
image. Running it needs a privileged container:

```bash
docker run -it --rm --privileged obeoneorg/netshoot:docker
install-dockerd --start # installs, starts dockerd, waits for the socket
docker run --rm hello-world
```

### Message of the Day

Every variant prints a short message when an
interactive shell starts on a terminal: a few
example commands and the notes for that variant.
It shows once per `docker run` or `docker exec`,
not again in nested shells or tmux panes. Hide it
with `-e NETSHOOT_NO_MOTD=1`. The fragments live in
`/etc/netshoot/motd.d/`.

### Pulling Specific Variants

```bash
Expand Down Expand Up @@ -244,7 +265,7 @@ organized by category:
| HTTP/HTTPS | curl, wget, httpie, apache2-utils (ab) |
| WebSocket | websocat |
| Remote Access | openssh-client, telnet |
| Data Transfer | socat, rsync, magic-wormhole |
| Data Transfer | socat, rsync, wormhole-rs (as `wormhole`) |
| VPN | wireguard-tools |
| SMTP Testing | swaks |
| LDAP | ldap-utils (ldapsearch, ldapwhoami, ...) |
Expand Down Expand Up @@ -313,7 +334,7 @@ bind9-dnsutils, bmon, bridge-utils, conntrack, curl,
dhcpdump, ethtool, fping, hping3, httpie, iftop,
iperf, iperf3, iproute2, ipset, iptables,
iputils-ping, iputils-tracepath, ipvsadm, ldap-utils,
masscan, mtr, netcat-openbsd, net-tools, nethogs,
masscan, mtr-tiny, netcat-openbsd, net-tools, nethogs,
netperf, nftables, ngrep, nload, nmap,
openssh-client, sipcalc, snmp, socat, speedtest,
sslscan, swaks, tcpdump, tcpflow, tcptraceroute,
Expand All @@ -322,9 +343,10 @@ wireguard-tools

**System**: bash, btop, ca-certificates, coreutils,
file, fzf, git, gnupg, htop, iotop, jq,
kitty-terminfo, less, lsof, magic-wormhole, man-db,
kitty-terminfo, less, lsof, man-db,
ncdu, openssl, procps, python3-venv, rsync, strace,
sysstat, thefuck, tmux, unzip, util-linux, vim, zip,
sysstat, thefuck, tmux, unzip, util-linux, vim,
wormhole-rs, zip,
zsh

**Upstream releases** (not packaged, or too old, in
Expand All @@ -339,7 +361,7 @@ fast-syntax-highlighting
bind9-dnsutils, btop, ca-certificates, coreutils,
curl, ethtool, file, fping, git, httpie, iperf3,
iproute2, iputils-ping, iputils-tracepath, jq,
kitty-terminfo, less, lsof, mtr, netcat-openbsd,
kitty-terminfo, less, lsof, mtr-tiny, netcat-openbsd,
net-tools, nmap, openssh-client, procps, rsync,
socat, strace, tcpdump, telnet, tmux, traceroute,
unzip, util-linux, vim, wget, whois, zip, zsh, plus
Expand Down
6 changes: 6 additions & 0 deletions configs/dpkg-excludes
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Netshoot: skip package docs and message translations to keep the image small.
# Man pages are deliberately NOT excluded (man-db is part of the toolset).
path-exclude=/usr/share/doc/*
path-include=/usr/share/doc/*/copyright
path-exclude=/usr/share/locale/*
path-include=/usr/share/locale/locale.alias
1 change: 1 addition & 0 deletions configs/motd/footer
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Hide this message with -e NETSHOOT_NO_MOTD=1
6 changes: 6 additions & 0 deletions configs/motd/netshoot
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
netshoot: network troubleshooting toolkit (https://github.com/obeone/netshoot)
Capture tcpdump -ni any port 53 termshark -i eth0
DNS doggo example.com dig +short example.com
Path mtr 1.1.1.1 ss -tulpn
HTTP/TLS curl -v https://host check-tls host
Share wormhole send <file> transfer.sh send <file>
7 changes: 7 additions & 0 deletions configs/motd/slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
netshoot slim: reduced network troubleshooting toolkit (https://github.com/obeone/netshoot)
Capture tcpdump -ni any port 53
DNS dig +short example.com
Path mtr 1.1.1.1 ss -tulpn
HTTP/TLS curl -v https://host check-tls host
Share transfer.sh send <file>
Variant slim: reduced toolset, no container runtime
1 change: 1 addition & 0 deletions configs/motd/variant-base
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Variant base: no container runtime (see the docker, podman, nerdctl and containerd tags)
2 changes: 2 additions & 0 deletions configs/motd/variant-containerd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Variant containerd: nerdctl full, containerd started by the entrypoint (needs --privileged)
Logs: /var/log/containerd.log
Loading
Loading