You run docker run -p 8080:80 nginx and Docker answers with an error like port is already allocated or address already in use. Something on your machine already owns port 8080. This guide explains what the error means, how to find the culprit in under a minute, and the fixes that actually work, including the ones that stay out of your way when you run many projects at once.
Updated October 2026: I rewrote this 2024 post. It still taught the old docker-compose command and version: '3' files (Compose files no longer need a version line), published a MySQL port to the whole network in its examples, and presented custom Docker networks as a fix for port conflicts. Networks do not fix host port conflicts, because only published ports use host ports. It also missed the most common surprise on Macs: AirPlay Receiver sitting on port 5000. The commands below are current, and the ones I could run locally, I did.
What a Port Conflict Is
A computer can have only one program listening on a given port and address at a time. When you publish a container port with -p HOST:CONTAINER, Docker must claim HOST on your machine. If something else already has it, the container fails to start. The container’s own port (the right-hand number) never conflicts across containers, since each container has its own network namespace; only the left-hand, host port can collide.
The two errors you will see most often:
Bind for 0.0.0.0:8080 failed: port is already allocatedusually means another container already publishes that host port.listen tcp4 0.0.0.0:8080: bind: address already in useusually means a non-Docker process on your machine has it.
Those are the typical messages, and the exact wording varies a little by Docker version and platform.
Step 1: Find Out Who Has the Port
Is it another container?
docker ps --filter "publish=8080"
This lists running containers that publish host port 8080. If one shows up, stop or remove it (docker stop NAME), or choose another port for the new one. Remember that stopped-but-existing containers do not hold ports, but a container stuck in a restart loop can.
Is it a program on your machine?
| System | Command |
|---|---|
| macOS | lsof -nP -iTCP:8080 -sTCP:LISTEN |
| Linux | sudo ss -ltnp 'sport = :8080' |
| Windows (PowerShell) | Get-NetTCPConnection -LocalPort 8080 -State Listen, then Get-Process -Id <OwningProcess> |
I ran the macOS command while a test server held port 8080, and it printed the process name and ID. In the same test, trying to bind that port from Python failed with OSError: [Errno 48] Address already in use, which is the same underlying error Docker reports. I did not run the Linux and Windows commands for this post, but they are the standard equivalents.
Common culprits that surprise people
- macOS AirPlay Receiver uses ports 5000 and 7000. Port 5000 is the default for Flask and many sample apps. On my Mac,
lsofshowedControlCenterlistening on port 5000 right now. Either choose a different port, or switch off AirPlay Receiver in System Settings (the toggle is in the AirDrop & Handoff or Sharing pane, depending on your macOS version). - A local database or web server you forgot about. A native PostgreSQL on 5432, MySQL on 3306, Redis on 6379 or Apache/nginx on 80 are classic collisions with their container versions.
- A previous run of your dev server that never exited. The
lsoforssoutput shows the process to stop. - Windows reserved port ranges. On Windows, a container can fail with “an attempt was made to access a socket in a way forbidden by its access permissions” even though nothing is listening. Windows (often with Hyper-V or WSL) can reserve ranges of ports. Check them with
netsh interface ipv4 show excludedportrange protocol=tcpand pick a port outside those ranges. This is community-reported behaviour that I could not test here.
Fix 1: Choose a Different Host Port
The simplest fix is to change only the left number. The container still listens on 80; only the door you use on your machine changes:
docker run -d -p 8081:80 nginx
Open http://localhost:8081. Keep a short list of which port belongs to which project so you do not rediscover the conflict next month, and prefer ports in the 3000-9000 range, away from the system services.
Fix 2: Let Docker Pick a Free Port
If you do not care which port you get, leave out the host port. Docker picks an available one:
docker run -d -p 80 --name web nginx
docker port web
docker port web prints the mapping, for example 80/tcp -> 0.0.0.0:32768. The -P (capital) flag does the same for every port the image declares with EXPOSE. Per the Compose reference, giving only a container port in ports: also makes the engine pick a free host port. This is ideal for tests, CI and anything started by scripts, because it can never collide. It is a poor fit for a service you need to bookmark, since the port changes each run.
Fix 3: Make the Port Configurable in Compose
Hard-coded ports are what break the second copy of a project. Use an environment variable with a default, so each developer or environment can override it without editing the file:
services:
web:
image: nginx:stable-alpine
ports:
- "${WEB_PORT:-8080}:80"
db:
image: postgres:18-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
# no "ports:" here - the web service reaches it as db:5432 on the Compose network
volumes:
- dbdata:/var/lib/postgresql
secrets:
db_password:
file: ./db_password.txt
volumes:
dbdata:
docker compose up -d # uses 8080
WEB_PORT=8090 docker compose up -d # uses 8090
docker compose ps # shows the published ports
Notes on this file, which I validated as YAML:
- The
${WEB_PORT:-8080}syntax means “useWEB_PORT, or 8080 if it is not set”, as the Compose file reference describes. You can also putWEB_PORT=8090in a.envfile next tocompose.yaml. - Use
docker compose(with a space), not the olddocker-composecommand, and leave out theversion:line, which is obsolete. - The database has no
ports:entry at all. The web service reaches it atdb:5432over the Compose network, so nothing on your machine needs port 5432, and nothing outside can connect. The original post published the database port, which both invites conflicts with a local database and exposes it. Publish a database port only when you really need to connect from a tool on your host, and then bind it to localhost (see below). - The Postgres image changed in version 18: per the official image documentation, the data volume is now mounted at
/var/lib/postgresql(for 17 and below it was/var/lib/postgresql/data). Mounting at the wrong path means your data does not persist. - Compose also gives each project its own network, named after the project directory, so two copies of a project in different folders do not clash on container names or networks, as long as you do not set a fixed
container_name. Only host ports can still collide.
Fix 4: Bind to localhost Only
By default, publishing a port makes it reachable on all of your machine’s network addresses. Docker’s port publishing documentation says that, when no host address is given, ports are published on all addresses (0.0.0.0 and [::]), and it describes publishing as insecure by default for that reason. If a service is only for you, say so:
docker run -d -p 127.0.0.1:8080:80 nginx
services:
web:
image: nginx:stable-alpine
ports:
- "127.0.0.1:8080:80"
Binding to 127.0.0.1 does not make a conflict go away (a different program using 8080 on all addresses still collides), but it removes the exposure to other computers, and it lets two services share a port number on different addresses. One more warning from Docker’s own documentation: Docker manipulates firewall rules in a way that can bypass tools like ufw, so a ufw rule alone may not block a published port. See the packet filtering and firewalls page. Binding to localhost is the safe default on a server.
Fix 5: Run Several Copies of a Service
Scaling a service that has a single fixed host port cannot work: the second copy tries to claim the same port. Give Compose a range of host ports, and each replica takes the next free one:
services:
web:
image: nginx:stable-alpine
ports:
- "8080-8082:80"
docker compose up -d --scale web=3
docker compose ps
The short syntax allows ranges such as "8080-8082:80". This is fine for local experiments. For real load balancing, put a proxy in front (next section) rather than asking clients to pick a port. I validated the file’s syntax but did not run the scale command here.
Fix 6: Put a Reverse Proxy in Front
When you have many web services, stop publishing a port for each. Run one proxy that owns ports 80 and 443 and routes by host name; every other container publishes nothing and is reached over the Docker network. This is the pattern that scales:
services:
proxy:
image: caddy:2-alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
app:
image: ghcr.io/your-user/app:latest # no published ports
api:
image: ghcr.io/your-user/api:latest # no published ports
volumes:
caddy_data:
app.example.com {
reverse_proxy app:3000
}
api.example.com {
reverse_proxy api:8000
}
Caddy forwards app.example.com to app:3000 and api.example.com to api:8000, using Compose service names as host names (see the reverse_proxy directive). It also obtains HTTPS certificates automatically for real domains. I ran caddy validate on this Caddyfile and it is valid, and I checked the Compose file parses; I did not start the stack. For local development you can use names like app.localhost. Nginx, Traefik and other proxies work the same way: one published entry point, many unpublished services behind it.
What Does Not Fix It
- Custom Docker networks. They are great for isolating services and letting containers find each other by name, but they do not change host port usage. A conflict is between a published port and another program on the host.
EXPOSEin a Dockerfile. It documents which port the app listens on. It does not publish anything and cannot conflict.- Restarting Docker. It sometimes frees a stuck port, but it is a guess. Find the owner with the commands above.
Quick Checklist
- Read the error: allocated usually means another container, address already in use usually means a host process.
- Run
docker ps --filter "publish=PORT"and the system command for your OS. - Stop the owner, or change your host port (the left number).
- In Compose, use
${VAR:-default}for ports and do not publish databases unless you must. - Bind to
127.0.0.1for anything that is only for you. - For many web services, use one reverse proxy and publish only 80 and 443.
Conclusion
Port conflicts look mysterious but have one cause: two things want the same host port. Find the owner, move your side to a free port, and reduce how many host ports you use at all by keeping databases and internal services unpublished and fronting web apps with a single proxy. Make ports configurable in Compose, and a second copy of your project stops being a problem. If you are wiring Docker into automated deployments, see how to build a CI/CD pipeline with Docker for a deployment that publishes its port on localhost behind a proxy.
What does “port is already allocated” mean in Docker?
Another container is already publishing the same host port. Find it with docker ps –filter “publish=PORT”, stop it or choose a different host port for the new container.
What does “address already in use” mean in Docker?
A program outside Docker is already listening on that port on your machine. Find it with lsof on macOS, ss on Linux or Get-NetTCPConnection on Windows, then stop it or use another host port.
Why does port 5000 not work on my Mac?
macOS AirPlay Receiver listens on ports 5000 and 7000 when it is enabled. Use a different host port, or turn off AirPlay Receiver in System Settings.
Can I run two containers on the same port?
Two containers can both listen on port 80 inside their own networks, but only one can publish a given host port on the same address. Publish them on different host ports, or put a reverse proxy in front and route by host name.
How do I let Docker choose a free port?
Use -p CONTAINER_PORT without a host port (for example -p 80), or -P to publish all exposed ports. Then run docker port NAME to see the assigned ports. In Compose, list only the container port under ports.