Every Docker error I've ever hit has followed the same ritual.
*The daemon spits out something like this:
*
- docker: Error response from daemon: Bind for 0.0.0.0:8080 failed:
- port is already allocated.
- See 'docker run --help '
I stare at it. I copy it. I open a new tab. I paste it into Google.
I scan five Stack Overflow threads. I find the fix buried in a comment
from 2019. I run it. I move on.
I did this so many times I started to feel like I was failing some kind
of basic competence test. Surely there's a better way. Surely Docker
could just... tell me what's wrong.
It doesn't. So I built something that does.
**Introducing bugtalk
**
**bugtalk **is a transparent terminal wrapper that sits in front of your
docker binary and translates failures into plain-English fixes in
real time.
This is what hitting a port conflict looks like after you install it:
$ docker run -p 8080:80 nginx
docker: Error response from daemon: Bind for 0.0.0.0:8080 failed:
port is already allocated.
π§ [PORT_CONFLICT] Port 8080 is already in use by another process
π‘ Fix: sudo lsof -ti:8080 | xargs kill -9
β οΈ Risk: medium β review before running
The raw Docker error still shows β you might want it for logs. bugtalk
adds the fix underneath. Right there. In your terminal. No tab
switching. No Googling. No Stack Overflow from 2019.
To install it:
pip install bugtalk
bugtalk setup
Restart your shell. That's it. Docker commands work exactly as
before β same exit codes, same TTY sessions, same CI/CD pipelines.
You just never google a Docker error again.
How it works under the hood
The core idea is simple: prepend a wrapper script to your PATH that
intercepts Docker commands, captures stderr on failure, and matches
it against a library of known patterns.
Here's the full flow:
User types: docker run -p 8080:80 nginx
β
PATH resolution: /usr/local/lib/bugtalk/bin/docker β wrapper
β
Wrapper finds real docker via stored absolute path
(e.g. /usr/local/bin/docker or /opt/homebrew/bin/docker)
β
Runs real docker with original args, captures stderr
β
Exit code != 0 β match stderr against errors.json
β
Pattern found β print plain-English fix to stderr
β
Exit with original exit code (CI/CD unaffected)
The real docker binary path is stored as an absolute path during
bugtalk setup β never a bare "docker" string. That one decision
prevents infinite recursion, which is the most common way this class
of tool fails.
The TTY problem
The trickiest part of building this was interactive commands.
docker exec -it container bash needs a live terminal. If you capture
stdin/stdout naively, the session hangs or errors.
bugtalk solves this by detecting TTY flags before deciding whether
to capture:
def _is_interactive_command(cmd, rest):
# Check for -i, -t, -it, --interactive, --tty
if _has_tty_flags(rest):
return True
# Also check if stdin is actually a terminal
if cmd in ("exec", "run") and sys.stdin.isatty():
return True
return False
If the command is interactive, bugtalk calls os.execv() β complete
process replacement, no capture, zero interference. Your shell session
works exactly as if bugtalk wasn't there.
Keeping CI/CD safe
This was non-negotiable. If bugtalk ever returned exit code 0 for a
failing docker command, it would silently swallow failures in
production pipelines. Unacceptable.
The wrapper always exits with docker's original exit code:
result = subprocess.run([real_docker] + args, capture_output=True, text=True)
# ... translate the error ...
sys.exit(result.returncode) # always the original code
A CI pipeline running docker run myimage pytest will still fail
exactly as expected. bugtalk just adds context on stderr.
Pattern matching with named capture groups
Each error pattern in errors.json uses named regex groups so the
fix command can reference extracted values directly:
"PORT_CONFLICT": {
"regex": "Bind for [^:]+:(?P<port>\\d+) failed|port is already allocated",
"message": "Port {port} is already in use by another process",
"fixes": {
"darwin": "lsof -ti:{port} | xargs kill -9",
"linux": "sudo lsof -ti:{port} | xargs kill -9",
"windows": "netstat -ano | findstr :{port}",
"default": "lsof -ti:{port} | xargs kill -9"
},
"risk_level": "medium"
}
The translate() function builds a defaultdict(str) from both
positional and named capture groups, then uses format_map β so a
template with {port} never crashes if the port wasn't captured in
that particular alternation branch.
Auto-updates that don't block your terminal
New error patterns ship via a errors.json update on GitHub. The
wrapper checks for updates weekly β but crucially, this never adds
latency to your commands:
def schedule_update():
t = threading.Thread(target=_do_update, daemon=True)
t.start() # returns immediately
def main():
schedule_update() # fires and forgets
# rest of wrapper runs at full speed
The update runs in a daemon thread in the background. If the network
is down, it fails silently and tries again next week. Your terminal
is never waiting on it.
What v1 covers
25 error patterns covering the errors I hit most often:
| Pattern | What it catches |
|---|---|
PORT_CONFLICT |
Port already allocated |
DAEMON_NOT_RUNNING |
Docker daemon not started |
IMAGE_NOT_FOUND |
Image not pulled locally |
VOLUME_PERMISSION |
Permission denied on mounted volume |
PULL_RATE_LIMIT |
Docker Hub rate limit hit |
DISK_SPACE |
No space left on device |
CGROUP_OOM |
Container OOM-killed |
MANIFEST_PLATFORM |
No image for this CPU architecture |
NETWORK_CONFLICT |
Network already exists |
EXEC_NOT_RUNNING |
exec on a stopped container |
HEALTHCHECK_FAIL |
Container healthcheck failing |
SECCOMP_DENIED |
Syscall blocked by seccomp |
BUILD_NO_DOCKERFILE |
No Dockerfile in build context |
LAYER_CACHE_MOUNT |
BuildKit not enabled |
DNS_RESOLUTION |
DNS failure inside container |
AUTH_REQUIRED |
Registry login needed |
| ...and 9 more |
OS-specific fixes for each β the command to kill a process on macOS
is different from Linux, and bugtalk knows which one to print.
The design decisions I'm most proud of
It never modifies the real docker binary. The original docker
is untouched. bugtalk only adds a wrapper earlier in PATH. Running
bugtalk unsetup removes the wrapper and cleans the PATH entry β a
complete, clean uninstall. Nothing left behind.
It fails safe. Every code path in the wrapper is wrapped in a
try/except that falls back to calling real docker directly via
os.execv. If bugtalk crashes, it gets out of the way and lets
Docker run normally. The worst case is you see a raw error message
instead of a translated one.
It has no runtime dependencies. The wrapper uses only Python's
standard library β subprocess, re, threading, json,
urllib.request. No third-party packages. Nothing to break. Nothing
to update separately from bugtalk itself.
Unknown errors ask for help. When stderr doesn't match any
known pattern, bugtalk nudges you to run bugtalk report, which
opens a pre-filled GitHub issue in your browser. No token required,
no copy-pasting. That's how the pattern library grows over time.
What I learned building it
The PATH wrapper pattern is underused. Sitting transparently in
front of a binary and intercepting failures is a surprisingly powerful
primitive. The same approach could work for npm, git, kubectl, pip β
any CLI tool with cryptic error messages. bugtalk v1 is Docker-only,
but the architecture is deliberately generic.
Interactive terminal detection is subtle. My first version broke
docker exec -it silently β the session would hang because
capture_output=True destroyed the TTY. The fix required checking
both the command flags and whether stdin was actually a terminal.
Seemingly obvious in hindsight. Very non-obvious at 2am.
The moat isn't the regex patterns. Anyone can copy a
errors.json. The moat is distribution β having a deployed piece of
software on developers' machines that intercepts their Docker commands.
That's a platform. The patterns are just the first thing that platform
does.
Install it
pip install bugtalk
bugtalk setup
Restart your shell. Then trigger a Docker error you've seen before and
watch it get translated.
If it catches something that saves you a Google search, run:
bugtalk status # see what version and how many patterns you have
If it misses an error, run:
bugtalk report # opens a pre-filled GitHub issue
That's how v2's pattern list gets built.
Links
- GitHub: github.com/Mikemiol17/bugtalk
- PyPI: pypi.org/project/bugtalk
If you've ever copy-pasted a Docker error into Google, this is for
you. Would love to hear which errors you hit most β drop them in the
comments and I'll make sure they're in the next update.
Top comments (0)