How to Give Each Docker Container Its Own Mobile Proxy IP (2026)
How to Give Each Docker Container Its Own Mobile Proxy IP (2026)
You scaled your scraper to ten containers, felt clever about it, and then watched all ten get blocked at the same minute. Here is why: all ten containers share your host machine’s ip. The target site does not see ten workers, it sees one machine doing the work of ten, hammering the same pages from the same address, so it blocks the lot in one move. The fix is to give each container its own proxy exit ip. One container, one address, one identity, wired into a docker compose file that does it cleanly.
I run a hardware mobile proxy farm in Singapore, real phones on real Singtel, M1, and StarHub sims, not a rack of datacenter ranges. I run my own scrapers in Docker containers behind these proxies every day, so everything here is what I actually do, not theory I read somewhere.
Why Docker does not give each container its own ip
A container on the default bridge network still leaves through the host’s single public ip. Most people get this wrong: they assume Docker hands each container its own internet identity. It does not. The container gets its own internal address on the bridge, but when packets go out to the real internet they get translated back to the one host ip. So setting a proxy at the host level helps nobody, because every container funnels through the same exit anyway. The trick is to set the proxy per container, so each one reaches out through a different exit and holds a different sticky ip.
The first tool: proxy environment variables
Most well behaved http clients read three environment variables, so this is the quickest way to proxy a container without touching application code. The variables are http_proxy, https_proxy, and no_proxy. You point the first two at your proxy endpoint, and you set the third to a list of hosts that should skip the proxy, things like localhost and your internal service names. When a client respects these, every request it makes goes out through the proxy you named.
In Docker you pass these per container, not globally. With docker run you add --env, and with compose you put them under the environment block of each service. The important word is each. If container a and container b get the same endpoint, they share an exit and you are back where you started, so the endpoint, or the session id inside it, has to differ per container.
Set both upper and lower case
One thing that trips people up is uppercase versus lowercase. Some tools read http_proxy in lowercase, some read HTTP_PROXY in uppercase, and a few only read one or the other. The safe move is to set both forms.
environment:
http_proxy: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
https_proxy: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
HTTP_PROXY: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
HTTPS_PROXY: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
no_proxy: "localhost,127.0.0.1"
NO_PROXY: "localhost,127.0.0.1"
It is six lines instead of three, but it removes a whole category of “why is this one container not proxied” bugs.
The honest caveat
Environment variables are a convention, not a law, so plenty of tools ignore them completely. curl reads them, most python http libraries read them, but some database drivers, some custom binaries, and a few scrapers with their own network stack do not look at them at all. Do not assume your traffic is proxied just because you set the variables. You have to verify, and I will show you how below. If a tool ignores the variables, you either configure its proxy setting directly or you wrap it so its traffic has nowhere else to go.
The piece that makes each container distinct: the sticky session id
The sticky session id is what actually gives each container its own address. With a mobile proxy you connect to one gateway host and port, and you choose your exit ip through the username. You embed a session token in the auth string, and the proxy pins that token to one mobile ip for as long as you keep using it. So container one uses a username with session-c1 in it, container two uses session-c2, and so on. Same gateway, same password, different session token, different exit ip. That is how ten containers behind one proxy account each get their own ip.
The clean way to wire this is to template it. Give each service its own session value and build the proxy username from a base plus that value. In compose you set an environment entry like PROXY_SESSION=c1 on the first service, c2 on the second, and have your entrypoint script assemble the full url. That keeps the password out of your application logic and keeps every container’s identity obvious at a glance.
services:
scraper-one:
image: yourscraper:latest
environment:
http_proxy: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
https_proxy: "socks5h://user-session-c1:PASS@gateway.yourproxy.com:1080"
no_proxy: "localhost,127.0.0.1"
scraper-two:
image: yourscraper:latest
environment:
http_proxy: "socks5h://user-session-c2:PASS@gateway.yourproxy.com:1080"
https_proxy: "socks5h://user-session-c2:PASS@gateway.yourproxy.com:1080"
no_proxy: "localhost,127.0.0.1"
Copy the service block and change only the session from c1 to c2. That one character change is what gives the second container a different mobile ip.
Why it must be socks5h, not socks5
Notice the scheme is socks5h with an h on the end, not plain socks5, and that h matters more than almost anything else in this setup. With socks5h, the hostname you are visiting gets resolved on the proxy side, through the tunnel, so the dns lookup leaves from the mobile ip too. With plain socks5, your container resolves the name locally first, using Docker’s own resolver and ultimately your host’s dns, and only then sends the connection through the proxy. That local lookup is a leak. The site cannot see it, but your isp and the resolver can, and worse, you can end up with the proxy carrying your traffic while your host quietly carries your dns. Always use socks5h so the name resolution rides the same tunnel as everything else.
This brings up the Docker resolver directly. Every container by default uses Docker’s embedded dns at 127.0.0.11, which forwards to whatever your host uses. So two rules. One, prefer socks5h so resolution happens at the exit. Two, put only your internal service names in no_proxy, never real external domains, because anything you exclude there gets resolved and connected locally, outside the tunnel. This matters as much for mobile proxies in general as it does inside Docker.
The sidecar pattern for stubborn tools
For tools that ignore proxy variables, run a sidecar: a tiny second container next to your app whose only job is to hold the proxy connection and the auth. It listens on localhost, and your app container points at that local port as if it were a plain local proxy, with no username or password in the app at all. The sidecar handles the sticky session and the mobile gateway. This is great for two reasons. Your application code never touches credentials, and you can give each app its own sidecar with its own session id, so the per container identity lives in one small, swappable place.
If you go the sidecar route, share a network namespace between the app and its sidecar so localhost means the same thing to both. In compose you set network_mode to service: the sidecar name on the app service.
services:
proxy-c1:
image: yoursidecar:latest
environment:
PROXY_SESSION: c1
app-c1:
image: yourscraper:latest
network_mode: "service:proxy-c1"
environment:
http_proxy: "http://127.0.0.1:1080"
https_proxy: "http://127.0.0.1:1080"
Now when the app talks to 127.0.0.1 and the proxy port, it is really talking to the sidecar sitting beside it. Each pair is one identity. Spin up five pairs and you have five exits.
Verify from inside every container
Never trust that a container is proxied just because you configured it, check from inside the container itself. You exec into a container and run curl against an ip echo service, something that just returns the address it sees. The address that comes back must be your mobile exit, not your host’s home ip. Then run the exact same command in the next container, and the address must be different. If both containers show the same ip, your sessions collided and you have one identity pretending to be two.
docker exec scraper-one curl --silent https://api.ipify.org?format=json
docker exec scraper-two curl --silent https://api.ipify.org?format=json
If you want to be sure the proxy variables are even being honored, add --verbose and watch that curl connects to the proxy host and not straight to the target. And check dns specifically: run curl against a service that reports the resolver location and confirm it shows the mobile network, not your local isp. Three checks, two minutes, and you actually know your setup is sound instead of hoping. The same verify discipline carries over to any Docker-based scraping work.
The failure I see most: ip collisions
Two containers ending up on the same exit ip is the most common failure, and it happens when two containers share a session token, or when you reuse a token after the previous container released it and the proxy hands you the same sticky ip. The cure is discipline. Give every container a unique session value, derive it from something stable like the service name or the replica index, and never hardcode the same token in two places. If you scale a service to several replicas, make the session include the replica number so replica one and replica two do not both claim session c1.
One more practical note on rotation. A sticky mobile ip stays put while you use it, but mobile networks do reassign addresses, and your exit can change when the carrier rotates. That is normal for mobile and usually a feature, since it looks like a real phone moving through the network. Just make sure your scraper treats an ip change gracefully, retries on a fresh connection, and does not panic if container three’s address shifts mid run. Build for rotation rather than fighting it.
The whole recipe
Give each container its own proxy through per container environment variables or a sidecar. Make each one distinct with its own sticky session id in the username. Use socks5h so dns rides the tunnel and never leaks through the Docker resolver. And verify from inside every container with curl before you trust a single request. Do that, and your ten containers look like ten different phones instead of one very busy machine.
If you want exits that hold up under this kind of work, that is exactly what we run: real SG mobile ips on Singtel, M1, and StarHub sims, sticky sessions you can pin per container, and dns that routes through the tunnel so you stop leaking your real resolver. Start a free trial of Singapore Mobile Proxy with code YT30, point a couple of containers at it, and run the curl check yourself.
Get new guides and videos first — join the Telegram channel.