DEV Community

Cover image for Docker Port Exposing: My Real Production Mistake
Prashanta Chakraborty
Prashanta Chakraborty

Posted on

Docker Port Exposing: My Real Production Mistake

I’m developing a Bangladesh-based healthcare system, Gooddoktor.
Recently, I deployed my backend in a VPS using Docker.

I don’t have hardcore DevOps knowledge. I mostly:
learn → try → break → fix.
I set up nginx for the subdomain, all is ok. So yesterday I randomly tried a port scan on my own server. And guess what? I found multiple OPEN PORTS. Even worse…

I could access my project using: http://SERVER_IP:PORT. No domain, no SSL, Nothing. Anyone on the internet could directly access my services.

My First Thought
I asked ChatGPT:

  • GPT gave firewall rules → I applied them → still accessible.

  • Then I Googled → again firewall → again same result.

So clearly, the issue was not the firewall. That means something else was exposing the port.

The Real Problem (Docker Did It)
In my docker-compose I wrote:

ports:
 - "2525:2525"

Enter fullscreen mode Exit fullscreen mode

Looks normal, right? But this line is VERY dangerous in production.

What actually happens
Docker doesn’t just run inside your machine. When you map a port like this:

2525:2525
Enter fullscreen mode Exit fullscreen mode

Docker tells Linux: Bind container port 2525 to ALL NETWORK INTERFACES, Meaning: 0.0.0.0:2525 And 0.0.0.0 means: Accept connections from anywhere in the world

So the firewall allowed 80 & 443 only. But Docker bypassed it by opening its own socket. That’s why I could access:

http://ip:2525
Why the Firewall Didn’t Save Me
Important lesson:

Docker publishes ports BEFORE your firewall filtering in many cases (nat table). So, UFW rules ≠ protection if Docker exposes ports publicly. That’s why even after blocking, it still worked.

The Fix (Actual Solution)
Instead of:

ports:
- "2525:2525"
Enter fullscreen mode Exit fullscreen mode

I changed to:

ports:
- "127.0.0.1:2525:2525"
Enter fullscreen mode Exit fullscreen mode

Now Docker binds to:

127.0.0.1:2525
Meaning:

Only accessible from inside the server. Nginx can access. The Internet cannot. And boom. IP access stopped working.

Why This Works

Network scope difference:

Binding: 0.0.0.0, Meaning: Public internet | Binding: SERVER_IP, Meaning: Public internet | Binding: 127.0.0.1, Meaning: Only local machine

So now the flow becomes:

User → Domain → Nginx → localhost:2525 → Docker → App

Instead of:

User → Directly → Backend (very bad)

What I Learned

Docker is not just a container; it’s a network gateway
Port mapping is public by default
A firewall alone cannot save a bad Docker config
Production server should NEVER expose app ports
Always expose only nginx (80/443)
Final Advice

If you’re deploying backend/services with Docker and nginx:

Never do this in production

ports:
- "3000:3000"
Enter fullscreen mode Exit fullscreen mode

Always do this:

ports:
- "127.0.0.1:3000:3000"
Enter fullscreen mode Exit fullscreen mode

Deployment is not coding…
Deployment is security.

And security mistakes don’t crash your app. They silently make your app public.

Top comments (0)