You clone a new project. docker compose up. And then it hits you.
Error starting userland proxy: listen tcp4 0.0.0.0:8080: bind: address already in use
You sigh. You know the drill. Now you have to figure out what's squatting on port 8080. Is it another Docker container? A rogue dev server you forgot about? That one time you started Postgres globally and never stopped it?
You run lsof -i :8080 or docker ps and grep around. Five minutes later you find the culprit. You kill it. You run docker compose up again. It works. You move on.
Until the next project. And the next port. And the next five minutes you'll never get back.
The problem with Docker Compose and ports
I run a lot of side projects. Each one has its own docker-compose.yml — databases, caches, reverse proxies, the works. Over time they accumulate like digital barnacles on my machine.
The issue isn't that ports conflict. The issue is that Docker tells you after you try to start the container. At that point you're already mentally committed to the task. The context switch to debug a port collision is pure friction.
What I wanted was something that tells me before I run the stack. Something that reads my docker-compose.yml, extracts the host ports, and says "hey, 8080 and 6379 are taken, deal with it now or pick different ports."
So I built portscope.
What it does
portscope is a single Bash script. No dependencies beyond what you probably already have — docker, ss, awk, grep. It reads your Compose file, finds the host ports you've mapped, and checks if anything is already using them.
The install is one line:
curl -o portscope https://raw.githubusercontent.com/bnap00/portscope/main/portscope.sh
chmod +x portscope
sudo mv portscope /usr/local/bin/portscope
Then you run it against any project:
portscope ./my-project
And it tells you upfront:
Using docker-compose file: ./my-project/docker-compose.yml
Required host ports: 80 6379
Info: Port 80 is already used by the same project – skipping
Conflict: Port 6379 is already in use by another container
No surprises. No context switches. Just information you can act on.
It knows when to shut up
One detail I cared about: if a port is already in use by a container from the same project, that's not a conflict. That's just you running docker compose up twice because you forgot it was already running. portscope skips those automatically.
It only flags ports that are genuinely blocked by other containers or system processes.
The flags worth knowing
| Flag | Behaviour |
|---|---|
| (none) | Checks current directory's docker-compose.yml |
portscope /path/to/dir | Checks compose file inside that directory |
portscope custom.yml | Parses a specific file directly |
--free-port 8080 | Finds and stops whatever is using port 8080 |
--free-port 8080 --yes | Same, but without prompts — CI friendly |
--version | Shows version |
The --free-port flag is my favourite. Instead of manually hunting down the process, portscope does it for you. It checks Docker containers first — because that's usually the culprit — and falls back to regular processes if Docker isn't using it. Then it asks if you want to kill it. Or you pass --yes and it just handles it.
CI/CD ready
I added --yes and an environment variable PORTSCOPE_YES=1 because I wanted this in CI pipelines. Before deploying a stack, run portscope. If there's a conflict, the build fails early with a clear message instead of cryptic Docker errors halfway through startup.
- name: Check for port conflicts
run: |
curl -o portscope https://raw.githubusercontent.com/bnap00/portscope/main/portscope.sh
chmod +x portscope
./portscope ./infrastructure --yes
Why Bash?
I could have built this in Node.js or Go. But Bash is already there. No node_modules, no binary downloads, no dependency hell. Just a script you curl and run. It parses YAML with grep and sed — which is admittedly crude, but Docker Compose files are predictable. The port declarations follow a simple pattern. Why over-engineer it?
As an Indian I love to save money — and saving disk space and install time counts too.
What it doesn't do
This is intentionally small. It doesn't validate your YAML schema. It doesn't generate compose files. It doesn't monitor ports in real time. It just answers one question: "will these ports work?"
Sometimes the best tools are the ones that do one thing and get out of the way.
Install it
curl -o portscope https://raw.githubusercontent.com/bnap00/portscope/main/portscope.sh
chmod +x portscope
sudo mv portscope /usr/local/bin/portscope
The source is on GitHub under MIT license. If you run into edge cases — maybe your compose file uses some exotic port syntax I didn't account for — open an issue. I actively maintain it.
Docker was supposed to make local development reproducible. And it does, mostly. But the gap between "my machine" and "your machine" isn't just about containers — it's about all the invisible state left behind from yesterday's experiments. portscope is my tiny attempt to make that state visible before it bites you.
Feel free to connect or reach out if you have questions, feature ideas, or just want to complain about Docker networking.