Sign inSign up

thenetworkfactory/tiny-tools

By thenetworkfactory

•Updated about 1 month ago

For debugging and testing network and API connectivity, ~30 MB Alpine Linux "Swiss Army knife".

Image
0

1.1K

thenetworkfactory/tiny-tools repository overview

⁠tiny-tools

A ~30 MB Alpine "Swiss Army knife" for debugging network and API connectivity from inside a container network.

Exec into it from inside the network you are debugging, when something will not talk to something else.

⁠Get it

docker pull thenetworkfactory/tiny-tools

⁠Run

Ad-hoc, against a stack network — this is the common case, because the whole point is to be inside the network you are debugging:

docker run --rm -it --network myproject_default \
    thenetworkfactory/tiny-tools bash

Against the host's network stack instead of a container one:

docker run --rm -it --network host thenetworkfactory/tiny-tools bash

As a long-lived service in a compose stack — give it no ports and hold it open with sleep infinity so exec works:

docker compose exec tiny-tools bash

Why the sleep: the image's CMD is an interactive shell, which exits immediately under compose (no TTY) and would leave the container restart-looping.

fish is installed too, if you prefer it: docker run -it … fish.


⁠Debugging recipes

⁠DNS
# Does the name resolve at all, and to what?
dig +short api.example.internal
host api.example.internal

# Which resolver is the container actually using?
cat /etc/resolv.conf

# Ask a specific resolver — is it the resolver that is broken, or the record?
dig @8.8.8.8 example.com
dig @127.0.0.11 api          # Docker's embedded DNS, inside a compose network

# Full chain, including CNAMEs and the authoritative answer
dig +trace example.com

# Is this a DNS problem or a connectivity problem? drill is terser than dig.
drill -V0 example.com

# Reverse lookup
dig -x 10.42.0.7

Inside a compose network, service names resolve via Docker's embedded DNS at 127.0.0.11. If dig api works but curl http://api does not, the problem is not DNS.

⁠Is the port even open?
# Fastest yes/no on a single port
nc -zv api 8000

# Range, on one host
nmap -p 1-1000 api

# What is listening in THIS container / on this host (--network host)
ss -tulnp

# Which process holds a port open
lsof -i :8000

nmap's scripting engine (NSE) is not included — Alpine splits it into nmap-scripting. apk add --no-cache nmap-scripting if needed.

⁠HTTP / ingress
# Status, timings and the redirect chain, no body
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}s\n' https://example.com
curl -sSIL https://example.com

# Bypass DNS and hit a specific backend, keeping the Host header —
# the single most useful trick for debugging reverse-proxy routing
curl -sS -H 'Host: api.example.internal' http://<container-ip>:8000/

# Same, without needing the IP by hand
curl -sS --resolve api.example.internal:80:172.18.0.5 \
     http://api.example.internal/

# What security headers is the proxy actually adding?
curl -sSI https://example.com | grep -iE 'content-security|strict-transport|referrer|x-frame'

# Is the proxy serving the app, or a maintenance/error page?
curl -sS https://example.com | head -20

# Pretty-print a JSON API
curl -sS http://api.example.internal/health | jq .

# A reverse proxy's own admin API — which route matched?
# (Traefik shown; adjust the path for nginx/Caddy/HAProxy.)
curl -sS http://proxy-admin.example.internal:8080/api/http/routers | jq -r '.[].name'
⁠TLS
# Full handshake, chain and expiry
openssl s_client -connect example.com:443 -servername example.com </dev/null

# Just the dates
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
  | openssl x509 -noout -dates -subject -issuer

# Does it negotiate TLS 1.3?
openssl s_client -connect example.com:443 -tls1_3 </dev/null 2>&1 | grep -E 'Protocol|Cipher'

# Is the chain complete, or is an intermediate missing?
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | grep -c 'BEGIN CERT'
⁠Routing and interfaces
ip -brief addr                 # what IPs does this container have
ip route                       # default gateway
ip neigh                       # ARP cache
traceroute api                 # where does it stop
mtr -rwc 20 8.8.8.8            # sustained path + loss (report mode)
ethtool eth0                   # link speed / duplex (--network host)

ip and ss are the modern replacements for ifconfig and netstat; net-tools is installed too, so both work.

⁠Packet capture

Needs NET_ADMIN + NET_RAW — see the note below.

# Everything to/from a host, no name resolution (-n keeps it fast)
tcpdump -ni any host api

# Just the HTTP port, with payload
tcpdump -nAs0 -i any port 8000

# Write a pcap out to the host for Wireshark
tcpdump -ni any -w /tmp/cap.pcap port 5672
⁠Throughput and load
# Between two containers: server on one, client on the other
iperf3 -s                                   # on A
iperf3 -c A -t 10                           # on B

# Quick HTTP load check
ab -n 200 -c 10 https://example.com/

# Generate a bcrypt htpasswd entry (basic-auth middleware)
htpasswd -nbB admin 'somepassword'
⁠Subnets
sipcalc 10.42.0.0/22           # range, broadcast, usable addresses
sipcalc -s 26 10.42.0.0/22     # split into /26s
⁠Process and host state

procps is installed, so these are the real tools rather than the busybox applets:

ps aux
top
free -m
vmstat 1 5
⁠Connection tracking / NAT
conntrack -L                   # live NAT + connection table
conntrack -E                   # follow events as they happen
⁠Reaching services from inside
# Postgres / MySQL / Redis clients are NOT installed (too large) — but
# you can still prove the port answers and speaks the right protocol:
nc -zv db 5432
(printf 'PING\r\n'; sleep 1) | nc redis 6379     # expect +PONG

# Forward a container-internal port to your shell for a one-off
socat TCP-LISTEN:15432,fork TCP:db:5432
⁠Copying files out
rsync -av /data/ user@host:/backup/

⁠Packet capture needs capabilities

tcpdump and mtr need NET_ADMIN + NET_RAW. These are not granted by default — a debug shell should not carry capabilities it does not need.

Without them tcpdump does not fail outright; it warns (That device doesn't support promiscuous mode) and captures only what the container itself sends or receives. If a capture looks suspiciously empty, check the capabilities before chasing the network.

# Ad-hoc
docker run --rm -it --cap-add NET_ADMIN --cap-add NET_RAW \
    --network myproject_default thenetworkfactory/tiny-tools bash

In compose, add the same via cap_add: [NET_ADMIN, NET_RAW].


⁠What is in the image

AreaTools
Shellsbash, fish, coreutils, nano
DNSdig, host, nslookup (bind-tools), drill
HTTP / transfercurl, rsync
Socketsnc (openbsd), socat, openssl
Reachabilityping, traceroute, mtr
Interfaces / routingip, ss (iproute2), ifconfig, netstat, route (net-tools), ethtool
Firewall / NATconntrack
Scanning / capturenmap, tcpdump
Throughput / loadiperf3, ab, htpasswd
Process / hostps, top, free, vmstat (procps), lsof
Data / filesjq, tree
Addressingsipcalc
SSH / gitssh, ssh-keygen, sshpass, git
⁠Deliberately excluded

The image is meant to stay small enough to pull without thinking about it. These were measured and rejected — each would dominate a ~30 MB image:

PackageInstalled size
httpie70 MB
tshark65 MB
rclone56 MB
nodejs39 MB
python329 MB
mariadb-client17 MB
yq-go8 MB
bat5 MB
postgresql-client3.4 MB
fd3.2 MB
redis (CLI)2.7 MB
ripgrep2.6 MB
strace1.9 MB

Any of them can be added for the life of a container:

apk add --no-cache postgresql-client

Maintained by AxisOps.

Tag summary

Content type

Image

Digest

sha256:91bb11a23…

Size

31.3 MB

Last updated

about 1 month ago

docker pull thenetworkfactory/tiny-tools