3

I have a following docker compose file:

version: "3.8"

services:
    postgres:
        image: postgres:11
        volumes:
            - myapp_postgres_volume:/var/lib/postgresql/data
            - type: tmpfs
              target: /dev/shm
              tmpfs:
                size: 536870912 # 512MB
        environment:
            POSTGRES_DB: elearning_academy
            POSTGRES_USER: myapp
            POSTGRES_PASSWORD: myapp123
        networks:
            - myapp_network

    pgadmin:
        image: dpage/pgadmin4:5.4
        volumes:
            - myapp_pgadmin_volume:/var/lib/pgadmin
        environment:
            PGADMIN_DEFAULT_EMAIL: [email protected]
            PGADMIN_DEFAULT_PASSWORD: myapp123
        ports:
            - 8080:80
        networks:
            - myapp_network
            
    redis:
        image: redis:6.2.4
        volumes:
            - myapp_redis_volume:/data
        networks:
            - myapp_network

    wsgi:
        image: wsgi:myapp3
        volumes:
            - /myapp/frontend/static/
            - ./wsgi/myapp:/myapp
            - /myapp/frontend/clientApp/node_modules
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
            - /var/run/docker.sock:/var/run/docker.sock:ro
        depends_on:
            - postgres
            - redis
        ports:
            - 9090
            - 3000:3000
            - 8000:8000
        environment:
            C_FORCE_ROOT: 'true'
            SERVICE_PORTS: 9090
        networks:
            - myapp_network
        deploy:
            replicas: 1
            update_config:
                parallelism: 1
                delay: 10s
            restart_policy:
                condition: on-failure
                max_attempts: 3
                window: 120s

    nodejs:
        image: nodejs:myapp3
        volumes:
            - ./nodejs/frontend:/frontend
            - /frontend/node_modules
        depends_on:
            - wsgi
        ports:
            - 9000:9000 # development
            - 9999:9999 # production
        environment:
            BACKEND_API_URL: http://0.0.0.0:3000
        networks:
            - myapp_network

    nginx:
        image: mydockeraccount/nginx-brotli:1.21.0
        volumes:
            - ./nginx:/etc/nginx/conf.d:ro
            - ./wsgi/myapp:/myapp:ro
            - myapp_nginx_volume:/var/log/nginx/
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        networks:
            - myapp_network
            
    haproxy:
        image: haproxy:2.3.9
        volumes:
            - ./haproxy:/usr/local/etc/haproxy/:ro
            - /var/run/docker.sock:/var/run/docker.sock
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        depends_on:
            - wsgi
            - nodejs
            - nginx
        ports:
            - 9763:80
        networks:
            - myapp_network
        deploy:
            placement:
                constraints: [node.role == manager]

volumes:
    myapp_postgres_volume:
    myapp_redis_volume:
    myapp_nginx_volume:
    myapp_pgadmin_volume:

networks:
    myapp_network:
        driver: overlay

As you can see I have a nodejs app and a django (wsgi) app. I have written django middleware to log incoming IP to the database. However, it logs the IP different from the actual IP. After reading online, I came to know that this might be due to how the docker network is configured (overlay as can be seen in last line of above docker compose file). I read online that I need to configure docker network in host mode. So I tried adding network_mode: host to each service and removing networks section from above file.

version: "3.8"

services:
    postgres:
        image: postgres:11
        volumes:
            - myapp_postgres_volume:/var/lib/postgresql/data
            - type: tmpfs
              target: /dev/shm
              tmpfs:
                size: 536870912 # 512MB
        environment:
            POSTGRES_DB: elearning_academy
            POSTGRES_USER: myapp
            POSTGRES_PASSWORD: myapp123
        network_mode: host

    pgadmin:
        image: dpage/pgadmin4:5.4
        volumes:
            - myapp_pgadmin_volume:/var/lib/pgadmin
        environment:
            PGADMIN_DEFAULT_EMAIL: [email protected]
            PGADMIN_DEFAULT_PASSWORD: myapp123
        ports:
            - 8080:80
        network_mode: host

    redis:
        image: redis:6.2.4
        volumes:
            - myapp_redis_volume:/data
        network_mode: host

    wsgi:
        image: wsgi:myapp3
        volumes:
            - /myapp/frontend/static/
            - ./wsgi/myapp:/myapp
            - /myapp/frontend/clientApp/node_modules
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
            - /var/run/docker.sock:/var/run/docker.sock:ro
        depends_on:
            - postgres
            - redis
        ports:
            - 9090
            - 3000:3000
            - 8000:8000
        environment:
            C_FORCE_ROOT: 'true'
            SERVICE_PORTS: 9090
        network_mode: host
        deploy:
            replicas: 1
            update_config:
                parallelism: 1
                delay: 10s
            restart_policy:
                condition: on-failure
                max_attempts: 3
                window: 120s

    nodejs:
        image: nodejs:myapp3
        volumes:
            - ./nodejs/frontend:/frontend
            - /frontend/node_modules
        depends_on:
            - wsgi
        ports:
            - 9000:9000 # development
            - 9999:9999 # production
        environment:
            BACKEND_API_URL: http://0.0.0.0:3000
        network_mode: host

    nginx:
        image: mydockeraccount/nginx-brotli:1.21.0
        volumes:
            - ./nginx:/etc/nginx/conf.d:ro
            - ./wsgi/myapp:/myapp:ro
            - myapp_nginx_volume:/var/log/nginx/
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        network_mode: host

    haproxy:
        image: haproxy:2.3.9
        volumes:
            - ./haproxy:/usr/local/etc/haproxy/:ro
            - /var/run/docker.sock:/var/run/docker.sock
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
        depends_on:
            - wsgi
            - nodejs
            - nginx
        ports:
            - 9763:80
        network_mode: host
        deploy:
            placement:
                constraints: [node.role == manager]

volumes:
    myapp_postgres_volume:
    myapp_redis_volume:
myapp_nginx_volume:
myapp_pgadmin_volume:

When I run docker stack deploy -c docker-compose.yml my_stack, it outputs Ignoring unsupported options: network_mode. I tried to check how the network is created:

$ docker network ls
NETWORK ID     NAME                       DRIVER    SCOPE
tp6olv2atq06   myapp_default              overlay   swarm

So it ended up being configured in the overlay mode only and not in host mode.

Further reading online revealed that host network mode is not available for swarm started with docker stack deploy. (Possible related open issue: github link).

For development, I simply connect vscode to nodejs and wsgi containers and run the apps in debug mode. So nginx is not involved at during development. However, in the deployment we do use nginx, which is also deployed as a docker container.

Now I have following questions:

Q1. Is configuring nginx to set X-Real-IP header with actual IP only solution?
Q2. Cant I do some docker config or changes in docker compose file to obtain real IP address inside docker container (django in my case), without doing any changes to nginx? Say somehow configuring those containers to be on host network?
Q3. If somehow I configure nginx to set X-Real-IP header, will it work even while development, given that nginx is not involved while development?
Q4. The comments to this answer seem to suggest that if the nginx itself is running as a docker container, then configuring it to add X-Real-IP header will not work. Is it so?

2 Answers 2

1

Although you've set the network_mode to host, I believe you need to set the ports -> mode to host to get the real client IP.

For example:

ports: 
  - mode: host 

Hopefully this works for you.

1

If your trying to do what I think you are it's not done that way, atleast not currently.

Answer.

If you are trying to put a single service into "Host" mode you have to give it a network that has Host mode enabled. To do that you can do something like this:

networks:
  rajsNetwork:
    external: true

And then all you have to do is add the network to a service like so:

 wsgi:
        image: wsgi:myapp3
        volumes:
            - /myapp/frontend/static/
            - ./wsgi/myapp:/myapp
            - /myapp/frontend/clientApp/node_modules
            - /etc/timezone:/etc/timezone:ro
            - /etc/localtime:/etc/localtime:ro
            - /var/run/docker.sock:/var/run/docker.sock:ro
        depends_on:
            - postgres
            - redis
        ports:
            - 9090
            - 3000:3000
            - 8000:8000
        environment:
            C_FORCE_ROOT: 'true'
            SERVICE_PORTS: 9090
        networks:
          - rajsNetwork
        deploy:
            replicas: 1
            update_config:
                parallelism: 1
                delay: 10s
            restart_policy:
                condition: on-failure
                max_attempts: 3
                window: 120s

Then the container will be exposed to the host and use it's network.

If you haven't changed any of the default configurations for Docker I assume you have a network bridge and that is why you are getting weird IP addresses that start like 10.n.n.n or 172.n.n.n or 192.168.n.n.

These are IP address given by the Docker bridge and therefore not use the hosts IP address.

But! Word of warning.

You don't want to put all your containers in host mode.

You should explicitly define your containers networks in the docker-compose.yaml file so you know what they are explicitly connecting to.

Also, taking it off the Docker bridge network and giving it host mode will essentially mean your other devices will not be able to communicate with the container as they are on a different networks.

One solution.

What you could do is add 2 networks like so:

networks:
  rajsNetwork:
    name: rajsNetwork
    driver: bridge
  rajsNetworkHost:
    name: rajsNetworkHost
    external: true

This way you can connect your other containers to 'rajsNetwork' and then your Django Middleware container to 'rajsNetwork' & 'rajsNetworkHost' networks.

This way it can still communicate with the other containers and still be on "Host" mode.

Answers to your questions in your post Q1. No, there is multiple like using a reverse proxy like Traefik or Haproxy.

Q2. Yes, It's above.

Q3. Well, if your serving via Node for dev then no it will not.

Q4. I'm not too sure about this one, probably not as this is an essential feature to block troublesome IP addresses with something like Fail2Ban.

What I like to do is set NGinx config to use the Systemd/Journald logger and not a log file or any type of Syslog app. Instead what I do is add the logging driver to the container and then all the logs from your NGinx container will be inside the hosts Journald daemon, which to view is "journalctl -xe". You can then setup something like Fail2Ban to monitor the hosts journal and get the IP address from there.

A big benefit of doing it this way is you only need to install Fail2Ban on 1 container/machine/host/vm and as long as the containers who are exposed to the internet are logging IP's and have the journal logging driver enabled, means you only need Fail2Ban installed on the host machine to protect all the containers on your system.

Rather than installing in each container. TO add logging to a container, add this:

    logging:
      driver: journald
      options:
        tag: "{{.Name}}"

Make sure its the same indentation amount as the 'image', 'network' or 'volumes' keywords.

The tag: "{{.Name}}" like means that each entry in the Docker hosts Journal will have the .Name of the container with the log entry.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.