IceWhaleTech / CasaOS

CasaOS - A simple, easy-to-use, elegant open-source Personal Cloud system.
https://casaos.io
Apache License 2.0
25.82k stars 1.41k forks source link

Allow to set static IP on Docker network for each container #1307

Open alosarjos opened 1 year ago

alosarjos commented 1 year ago

Is your feature request related to a problem? Please describe.

I'm trying to have multiple services, all of them accessed through the Nginx Proxy Manager, and enable a Firewall blocking all ports except for 80 and 443. Right now the Nginx Proxy Manager can only point to other apps by using the machine IP or 172.17.0.1, but those are the host network, and require the ports to be open.

I can get this done by pointing to the internal IP of the container on the Docker network too, but that IP will change, unless we can set a static ip (Unless setting the IP with the --ip flag on docker)

Describe the solution you'd like Show a new field to setup an ID on the App creation form, that's used by Docker

Describe alternatives you've considered It's this, or opening unnecessarily all the ports for every single app.

ETWang1991 commented 1 year ago

Got it

vkartk commented 1 year ago

@alosarjos If you're currently utilizing pihole, you have the option to employ the following Python script for the automatic updating of your host name. This script will generate a hostname in the format of {containerName}.docker.

import docker
import json
import datetime
import subprocess

# Set custom.list file path
custom_list_path = '/DATA/AppData/pihole/etc/pihole/custom.list'

def get_docker_container_info():
    client = docker.from_env()
    container_info = {}
    for container in client.containers.list():
        if container.attrs['NetworkSettings']['IPAddress']:
            container_info[container.name] = container.attrs['NetworkSettings']['IPAddress']
    return container_info

def read_custom_list_file(path):
    custom_list = {}
    with open(path, 'r') as file:
        for line in file:
            ip, domain = line.strip().split()
            custom_list[domain] = ip
    return custom_list

def update_custom_list(container_info, custom_list, path):
    for container_name, container_ip in container_info.items():
        if container_name in custom_list and container_ip != custom_list[container_name]:
            custom_list[container_name] = container_ip
        elif container_ip not in custom_list.values():
            # Set domain name to name.docker for new containers
            custom_list[f"{container_name}.docker"] = container_ip

    with open(path, 'w') as file:
        for domain, ip in custom_list.items():
            file.write(f"{ip} {domain}\n")

def log_container_info(container_info):
    now = datetime.datetime.now().isoformat()
    container_info_log = {
        'last_run': now,
        'containers': container_info
    }
    with open('container_info.json', 'w') as json_file:
        json.dump(container_info_log, json_file, indent=4)

def restart_pihole_dns():
    subprocess.run(["sudo", "docker", "exec", "-it", "Pi-hole", "/bin/bash", "-c", "pihole restartdns reload"])

def main():
    container_info = get_docker_container_info()
    custom_list = read_custom_list_file(custom_list_path)
    update_custom_list(container_info, custom_list, custom_list_path)
    log_container_info(container_info)

    # Restart Pi-hole DNS
    restart_pihole_dns()

if __name__ == "__main__":
    main()
IJ08 commented 11 months ago

@alosarjos If you're currently utilizing pihole, you have the option to employ the following Python script for the automatic updating of your host name. This script will generate a hostname in the format of {containerName}.docker.

import docker
import json
import datetime
import subprocess

# Set custom.list file path
custom_list_path = '/DATA/AppData/pihole/etc/pihole/custom.list'

def get_docker_container_info():
    client = docker.from_env()
    container_info = {}
    for container in client.containers.list():
        if container.attrs['NetworkSettings']['IPAddress']:
            container_info[container.name] = container.attrs['NetworkSettings']['IPAddress']
    return container_info

def read_custom_list_file(path):
    custom_list = {}
    with open(path, 'r') as file:
        for line in file:
            ip, domain = line.strip().split()
            custom_list[domain] = ip
    return custom_list

def update_custom_list(container_info, custom_list, path):
    for container_name, container_ip in container_info.items():
        if container_name in custom_list and container_ip != custom_list[container_name]:
            custom_list[container_name] = container_ip
        elif container_ip not in custom_list.values():
            # Set domain name to name.docker for new containers
            custom_list[f"{container_name}.docker"] = container_ip

    with open(path, 'w') as file:
        for domain, ip in custom_list.items():
            file.write(f"{ip} {domain}\n")

def log_container_info(container_info):
    now = datetime.datetime.now().isoformat()
    container_info_log = {
        'last_run': now,
        'containers': container_info
    }
    with open('container_info.json', 'w') as json_file:
        json.dump(container_info_log, json_file, indent=4)

def restart_pihole_dns():
    subprocess.run(["sudo", "docker", "exec", "-it", "Pi-hole", "/bin/bash", "-c", "pihole restartdns reload"])

def main():
    container_info = get_docker_container_info()
    custom_list = read_custom_list_file(custom_list_path)
    update_custom_list(container_info, custom_list, custom_list_path)
    log_container_info(container_info)

    # Restart Pi-hole DNS
    restart_pihole_dns()

if __name__ == "__main__":
    main()

Thanks for this. Is this a custom solution to run upon reboot of the server to execute this script and assign the new IPs generated by casaOS to container names in pi-hole? I have the pi-hole as a docker service thru casaOS along with nginx, would this take care of it?

vkartk commented 11 months ago

@IJ08 Yes, this script is designed to run when the server reboots and allocate the new IPs generated by CasaOS (Docker) to the Pi-hole Local DNS Records. It assigns IPs using the {containerName}.docker as the hostname, you can change '.docker' or any other desired suffix you want. It should work seamlessly for Nginx too, As I am using it alongside Nginx Proxy Manager. Just be sure to run the script after all containers have started up and are running smoothly.

IJ08 commented 11 months ago

@IJ08 Yes, this script is designed to run when the server reboots and allocate the new IPs generated by CasaOS (Docker) to the Pi-hole Local DNS Records. It assigns IPs using the {containerName}.docker as the hostname, you can change '.docker' or any other desired suffix you want. It should work seamlessly for Nginx too, As I am using it alongside Nginx Proxy Manager. Just be sure to run the script after all containers have started up and are running smoothly.

Thanks for the confirmation! Having a delayed startup on sleep in front should help with ensuring containers are up before this runs.

xBlueCellx commented 6 months ago

It does not work for me using NGINX.

NGINX is running in the bridge network like the containers i want to proxy. It tries to resolve the hostname using the docker 8.8.8.8 default nameserver. So its not utilizing the local pihole dns.

I tried adding "resolver 192.168.178.100;" (the ip is the host ip) in the advanced tab. But it does not work, i get a 502 bad gateway error.

Anyone has a fix for this? Or any other solution for not needing to open all needed ports of any container using NGINX?

vkartk commented 6 months ago

It does not work for me using NGINX.

NGINX is running in the bridge network like the containers i want to proxy. It tries to resolve the hostname using the docker 8.8.8.8 default nameserver. So its not utilizing the local pihole dns.

I tried adding "resolver 192.168.178.100;" (the ip is the host ip) in the advanced tab. But it does not work, i get a 502 bad gateway error.

Anyone has a fix for this? Or any other solution for not needing to open all needed ports of any container using NGINX?

Set Pi-Hole as the DNS server for the host, either through your router's DHCP settings or by following the manual setup instructions available here.

After configuring, execute the code on the host. You should then be able to view all the host names either in Pi-Hole's Local DNS Records or in the file AppData/pihole/etc/pihole/custom.list

import docker
import json
import datetime
import subprocess

# Set custom.list file path
custom_list_path = '/DATA/AppData/pihole/etc/pihole/custom.list'

def get_docker_container_info():
    client = docker.from_env()
    container_info = {}
    for container in client.containers.list():
        if container.attrs['NetworkSettings']['IPAddress']:
            container_info[container.name] = container.attrs['NetworkSettings']['IPAddress']
    return container_info

def read_custom_list_file(path):
    custom_list = {}
    with open(path, 'r') as file:
        for line in file:
            ip, domain = line.strip().split()
            custom_list[domain] = ip
    return custom_list

def update_custom_list(container_info, custom_list, path):
    for container_name, container_ip in container_info.items():
        if container_name in custom_list and container_ip != custom_list[container_name]:
            custom_list[container_name] = container_ip
        elif container_ip not in custom_list.values():
            # Set domain name to name.docker for new containers
            custom_list[f"{container_name}.docker"] = container_ip

    with open(path, 'w') as file:
        for domain, ip in custom_list.items():
            file.write(f"{ip} {domain}\n")

def log_container_info(container_info):
    now = datetime.datetime.now().isoformat()
    container_info_log = {
        'last_run': now,
        'containers': container_info
    }
    with open('container_info.json', 'w') as json_file:
        json.dump(container_info_log, json_file, indent=4)

def restart_pihole_dns():
    subprocess.run(["sudo", "docker", "exec", "-it", "Pi-hole", "/bin/bash", "-c", "pihole restartdns reload"])

def main():
    container_info = get_docker_container_info()
    custom_list = read_custom_list_file(custom_list_path)
    update_custom_list(container_info, custom_list, custom_list_path)
    log_container_info(container_info)

    # Restart Pi-hole DNS
    restart_pihole_dns()

if __name__ == "__main__":
    main()
xBlueCellx commented 6 months ago

It does not work for me using NGINX. NGINX is running in the bridge network like the containers i want to proxy. It tries to resolve the hostname using the docker 8.8.8.8 default nameserver. So its not utilizing the local pihole dns. I tried adding "resolver 192.168.178.100;" (the ip is the host ip) in the advanced tab. But it does not work, i get a 502 bad gateway error. Anyone has a fix for this? Or any other solution for not needing to open all needed ports of any container using NGINX?

Set Pi-Hole as the DNS server for the host, either through your router's DHCP settings or by following the manual setup instructions available here.

After configuring, execute the code on the host. You should then be able to view all the host names either in Pi-Hole's Local DNS Records or in the file AppData/pihole/etc/pihole/custom.list

import docker
import json
import datetime
import subprocess

# Set custom.list file path
custom_list_path = '/DATA/AppData/pihole/etc/pihole/custom.list'

def get_docker_container_info():
    client = docker.from_env()
    container_info = {}
    for container in client.containers.list():
        if container.attrs['NetworkSettings']['IPAddress']:
            container_info[container.name] = container.attrs['NetworkSettings']['IPAddress']
    return container_info

def read_custom_list_file(path):
    custom_list = {}
    with open(path, 'r') as file:
        for line in file:
            ip, domain = line.strip().split()
            custom_list[domain] = ip
    return custom_list

def update_custom_list(container_info, custom_list, path):
    for container_name, container_ip in container_info.items():
        if container_name in custom_list and container_ip != custom_list[container_name]:
            custom_list[container_name] = container_ip
        elif container_ip not in custom_list.values():
            # Set domain name to name.docker for new containers
            custom_list[f"{container_name}.docker"] = container_ip

    with open(path, 'w') as file:
        for domain, ip in custom_list.items():
            file.write(f"{ip} {domain}\n")

def log_container_info(container_info):
    now = datetime.datetime.now().isoformat()
    container_info_log = {
        'last_run': now,
        'containers': container_info
    }
    with open('container_info.json', 'w') as json_file:
        json.dump(container_info_log, json_file, indent=4)

def restart_pihole_dns():
    subprocess.run(["sudo", "docker", "exec", "-it", "Pi-hole", "/bin/bash", "-c", "pihole restartdns reload"])

def main():
    container_info = get_docker_container_info()
    custom_list = read_custom_list_file(custom_list_path)
    update_custom_list(container_info, custom_list, custom_list_path)
    log_container_info(container_info)

    # Restart Pi-hole DNS
    restart_pihole_dns()

if __name__ == "__main__":
    main()

The host and routers dns was already set to pihole. This setting apparently does not apply to docker containers on the bridge network. They use default 8.8.8.8 nameservers. I found no way to change this in CasaOS.

I had to set the resolver to my bridge gateway, it forwards the dns request to pihole. Using "resolver 172.17.0.1 ipv6=off;" in the advanced tap for the NGINX Proxy.

I also had to edit the update_custom_list functions if clause, since the skript did not update the custom.list when the ip adresses were already on it from the previous boot but "shuffeled" their ip adresses.