I have Ubuntu server with Docker to serve MySQL and SSH/SFTP and I need all ports except 3306 and 22 to be firewalled, pretty standard and trivial requirement, right?
Now, I managed to find a sort of solution but it doesn't work fully for me as after applying it, either:
- I can't access any of the mentioned services (by default)
- I can't access the internet from within containers (if I put
iptables: false
in Docker's daemon.json config file)
I tried a number of other search results but they're mostly so complex that I don't understand what they're doing or they're heavily scripted making me, an iptables layman, an impossible task to take something from it.
Proposed solution looks rather simple and easy to comprehend but entire Docker networking complexity makes it much harder to debug.
Can someone please share their working iptables rules for Docker hosts or at least guide me in right direction?
I use docker-compose to launch services and this is my yaml:
version: '3.7'
services:
mysql:
container_name: 'mysql'
image: mysql:8.0.13
command: --default-authentication-plugin=mysql_native_password
user: 1000:1000
ports:
- "3306:3306"
volumes:
- ./data:/var/lib/mysql
- ./config/custom.cnf:/etc/mysql/conf.d/custom.cnf
networks:
- database
restart: always
networks:
database:
driver: bridge
Edit: What I found is that allowing Docker to manage iptables rules is recommended and less demanding, at least in a long term and its okay to let Docker open required ports even though I didn't do that in a way I prefer, its still valid. What I want at this point is to find out is it possible to use iptables to block the ports opened by Docker and how (via mangle prerouting perhaps?). Any suggestions? Thanks a ton!
Docker relies on iptables to configure its networking. This includes NAT rules to handle access to and from the external network, and lots of other rules to configure containers access to each other on docker networks. By default, this access is open, but there are options when creating networks to restrict outside access and inter container communication. Therefore I do not recommend setting the
iptables
option to false in docker since that will break all of that functionality as you've seen.Publishing a port on the host implicitly allows access from outside. So the easiest option to avoid outside access is to not publish the port. You could publish the port onto a specific interface, e.g.
127.0.0.1:8080:80
which would publish the port 8080 on the host's loopback interface (127.0.0.1
) to connect to a container's port 80, and that loopback interface is not externally accessible. However, if leaving the port unpublished is not an option, it is possible to do this with iptables.This can be done by modifying the
DOCKER-USER
filter chain. You can find examples of this, like:from the following docs: https://docs.docker.com/network/iptables/
Note that the port is changed by some mangling rules that run before the filter rules, so if you want to filter by port, you'll need to use conntrack to get the original destination port:
Note that there is a default rule in DOCKER-USER to accept all, so you'll want to insert rules at the top of the chain (
-I
) rather than appending to the end (-A
).See: Steps for limiting outside connections to docker container with iptables?
After a lot of research and tests, I found out that the best (and easiest) way to expose only the ports you want and don't expose docker ports is to set the IP in
docker-compose.yml
to 127.0.0.1 (expose only to the host)Then you can use nginx or any other reverse proxy to expose to the outside.
This way: