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"
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
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"
I changed to:
ports:
- "127.0.0.1:2525:2525"
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"
Always do this:
ports:
- "127.0.0.1:3000:3000"
Deployment is not coding…
Deployment is security.
And security mistakes don’t crash your app. They silently make your app public.
Top comments (0)