I have a new application that I've created via a docker-compose file. This file contains 2 networks:
version: '2.1'
# ----------------------------------
# Services
# ----------------------------------
services:
application:
image: tianon/true
volumes:
- ${APPLICATION_PATH}:/var/www
nginx:
build:
context: ./docker/nginx
volumes_from:
- application
volumes:
- ${DOCKER_STORAGE}/nginx-logs:/var/log/nginx
- ${NGINX_SITES_PATH}:/etc/nginx/sites-available
ports:
- "${NGINX_HTTP_PORT}:80"
- "${NGINX_HTTPS_PORT}:443"
networks:
- frontend
- backend
redis:
build:
context: ./docker/redis
volumes:
- ${DOCKER_STORAGE}/redis:/data
ports:
- "${REDIS_PORT}:6379"
networks:
- backend
# ----------------------------------
# Networks
# ----------------------------------
networks:
frontend:
driver: "bridge"
backend:
driver: "bridge"
# ----------------------------------
# Volumes
# ----------------------------------
volumes:
redis:
driver: "local"
You'll notice I have 2 networks here, frontend
and backend
. The question is simple:
- How do I expose
frontend
to the world, but allowbackend
services to communicate with each other without exposing them to the world? (I'm assuming using iptables, or specifying in docker which network I want to expose to the host) - This is a simple 1 server setup (Digital Ocean), running Ubuntu 18.04 LTS
- In the example above, I should be able to access nginx from the outside world on port 80 or 443, but NOT redis. Nginx container should be able to access redis service 6379 internally.
- To be more clear: My intent right now is for the backend services to communicate with each other for now, so removing EXPOSE would work now, but ultimately, I want to partition the 2 networks. The backend services, I want to restrict IP ranges to (Allow some other non docker networks to communicate), and the frontend services I want to open exposed ports to the world.
For the bounty: There's clearly a way in docker to define multiple overlay networks. I had assumed, it would be simple to just expose one network to the outside world. I'm trying to find an easy way in docker or iptables to do that. The current answer gives me some direction, but requires manual rules for each port. I'd like a proper answer on how to secure my "frontend" facing network, without having to specify specific ports in iptables.
Best Answer
To complete the answer of @BMitch for Your demand..
You can do this with an additional inter-container network (or mutiple if You see the need or usecase for that..), because for such setup to work reliably it is essential that each container only has one non-internal network assigned. Otherwise You cannot be really certain which bridge network will be chosen for the incoming traffic on mapped port (or at least it's not really configurable).
Example compose-file that shows such setup:
It creates one bridge ([docker]front) for Your public services, one bridge ([docker]back) for Your backend services and one internal bridge (that does not publish ports even when requested and thus can be safely added as additional network) ([docker]intern) and assignt these to the containers.
In effect c2 will not be reachable on port 5001 from outside so the ports can be omitted for that container. It's just to show the result:
Now You can add the access rule for
dockerback
bridge toDOCKER-USER
chain:Take care: the current version 19.03.3 has bug that
DOCKER-USER
chain is not created, it's already fixed for 19.03.4 which shall become available soon. If You have that version You can add the Chain by Yourself (has to be done after each restart of docker daemon):Possibly You find a more appropriate place for such rules, but this is the way docker suggests. (I could think about fixating the networks for the bridges and pu the rule in a generic forward rule not applicable to docker manipulation on restarts, interface re-creates or such.)
In addition (or instead) You could use an additional IP on Your machine to bin the backend service to another IP then frontend services and filter at that level. In addiion means the exact same docker setup as above but additionally specifying
com.docker.network.bridge.host_binding_ipv4
driver-option (which is just the default IP for published ports) - or instead use only one network and specify bind-address acccording to service type. Then block FORWARD in DOCKER-USER or wherever appropriate with conntrack modules ctorigdst matching for the incoming interface.