danielbodart / portical

Portical manages UPnP port forwarding rules for Docker containers by just adding single label
https://github.com/danielbodart/portical
10 stars 3 forks source link
docker traefik upnp

Portical Logo

Portical

Overview

Portical is a docker container designed to manage UPnP port forwarding rules for Docker containers. It allows users to set up port forwarding just by setting a single label on their container. It was inspired by Traefik Proxy autoconfiguration of HTTP port forwarding rules.

Requirements

Usage

There are 2 parts to Portical:

  1. Add the portical.upnp.forward label and rules (published, 8080:80/tcp, 8080:80 or 8080, 8080/up etc) to your Docker containers to expose them to the internet.
  2. Run Portical container to set up port forwarding rules and monitor as many containers as you want.

Part 1: Adding the portical.upnp.forward label

The label portical.upnp.forward is used to specify the port forwarding rules in the following format ${external_port}:${internal_port}/${optional-protocol}.

Examples

Lets see what that looks like in practice:

Docker:

# Forward port 9999 on your router to port 8888 on the docker host then to port 80 on the container (for illustration purposes)
docker run nginx:latest \
 --label portical.upnp.forward=9999:8888 
 -p 8888:80

Docker Compose:

version: '3.8'

services:
  nginx: 
    image: 'nginx:latest'
    ports: 
      - '8888:80'
    labels:
      - 'portical.upnp.forward=9999:8888'

Part 2: Running Portical

Next we need to run Portical to set up the port forwarding rules and keep them up to date.

Overview

Docker

To get started it is recommended to run Portical with the update command to check it can either autodiscover your internet gateway or you can specify the root URL. Auto discovery is the default behaviour but can be very slow, so it may be more practical to specify the root URL.

Run Once with Autodiscovery (for testing)

docker run --rm -v '/var/run/docker.sock:/var/run/docker.sock' \
  danielbodart/portical:latest /opt/portical/run -v update

This will use autodiscovery to find your internet gateway.

Run Once with explicit root URL

If autodiscovery does not work, you can specify the UPnP root URL using the -r or --root option:

docker run --rm -v '/var/run/docker.sock:/var/run/docker.sock' \
  danielbodart/portical:latest /opt/portical/run \
  -r "http://internal-gateway-ip:5000/somePath.xml" update

Poll and Demonise

docker run --rm -d -v '/var/run/docker.sock:/var/run/docker.sock' \
  danielbodart/portical:latest /opt/portical/run poll

This will use autodiscovery to find your internet gateway.

Poll and Demonise with explicit root URL

If autodiscovery does not work, you can specify the UPnP root URL using the -r or --root option:

docker run --rm -d -v '/var/run/docker.sock:/var/run/docker.sock' \
  danielbodart/portical:latest /opt/portical/run \
  -r "http://internal-gateway-ip:5000/somePath.xml" poll

Docker Compose Setup (Recommended)

The ideal solution is to use Docker Compose to run Portical and your other containers in all one place:

version: '3.8'

services:

  portical:
    image: 'danielbodart/portical:latest'
    environment:
      - PORTICAL_UPNP_ROOT_URL=http://internal-gateway-ip:5000/somePath.xml # Optional
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock' # Required
    restart: unless-stopped
    network_mode: host
    command: "/opt/portical/run poll" # Change default "listen" command to "poll". It checks every "${PORTICAL_POLL_INTERVAL}" seconds
                                      # for running containers with "portical.upnp.forward" label and "renew" the forward

  # This is a service we are going to expose to the internet (for illustration purposes only)
  minecraft_java: 
    image: 'gameservermanagers/gameserver:mc'
    restart: unless-stopped
    ports: 
      - '25565:25565' # This is the port that will be exposed on the host (when in bridge network mode)
    labels:
    - 'portical.upnp.forward=published' # This will forward 25565 to 25565 on the container (see ports section)
    depends_on:
      - portical # Wait for "portical" container to be up and running

  # This is another service we are going to expose to the internet (for illustration purposes only)
  nginx: 
    image: 'nginx:latest'
    restart: unless-stopped
    network_mode: custom_network # This is a custom network (could be macvlan or ipvlan), notice no ports are needed
    labels:
      - 'portical.upnp.forward=8000:80/tcp' # This will forward port 8000 on the internet gateway to port 80 on the container on its custom network
    depends_on:
      - portical # Wait for "portical" container to be up and running

How it Works

The Portical container does the following steps:

  1. Uses Docker's API (via /var/run/docker.sock) to find containers with the specified label portical.upnp.forward.
  2. Determine the network driver / type used (supports bridge, host, macvlan and ipvlan).
  3. Connects to the internet gateway from that network (so the gateway allows it)
  4. Sends UPnp port forwarding rules specified.
  5. Repeat after the specified interval (default 15 seconds).

It's worth understanding that depending on the network driver, how port forwarding works is different:

TODO

Contributing

Contributions to Portical are welcome. Please submit your contributions as pull requests on GitHub.

License

Apache License 2.0