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.
There are 2 parts to Portical:
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.portical.upnp.forward
labelThe label portical.upnp.forward
is used to specify the port forwarding rules in the following format
${external_port}:${internal_port}/${optional-protocol}
.
published
will forward port all ports that have been published on the host. This is useful for containers that use the default bridge
network driver and reduces duplication.9999:8000/tcp
will forward port 9999
on internet gateway to the docker network's port 8000
using only the TCP protocol.25565:25565
will forward port 25565
on internet gateway to the docker network's port 25565
using both TCP and UDP protocol.19132:19132/udp
will forward port 19132
on internet gateway to the docker network's port 19132
using only the UDP protocol.19132/udp,8080/tcp
will forward port two ports UDP port 19132
and TCP port 8080
.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'
Next we need to run Portical to set up the port forwarding rules and keep them up to date.
Commands:
poll
: Continuously updates port forwarding rules at specified intervals.update
: Finds containers with the specified label and sets up port forwarding once only. (mainly for testing)
Options (all optional):
-r
, --root [URL]
: Set the UPnP root URL. (use if autodiscovery does not work)-d
, --duration [SECONDS]
: Set the polling interval in seconds (default: 15
seconds).-l
, --label [LABEL]
: Specify the Docker label to filter containers (default: portical.upnp.forward
.-v
, --verbose
: Enable verbose output.-f
, --force
: Remove existing rules even if they match (will disconnect any active connections).Environment Variables:
PORTICAL_UPNP_ROOT_URL
: The root URL for the UPnP device.PORTICAL_POLL_INTERVAL
: Interval in seconds for polling and updating rules (default: 15 seconds).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.
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.
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
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.
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
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
The Portical container does the following steps:
/var/run/docker.sock
) to find containers with the specified label portical.upnp.forward
. bridge
, host
, macvlan
and ipvlan
).15
seconds).It's worth understanding that depending on the network driver, how port forwarding works is different:
bridge
network driver (the default), traffic will be making a double hop, once from the internet gateway to the docker interface (controlled by the portical.upnp.forward
label rule), then from the docker interface to
the target container (controlled by the normal docker ports -p
flag or ports
yaml option).host
, macvlan
or ipvlan
network driver, traffic will only make a single hope from the
internet gateway to the target container (and you will not be required to specify the -p
flag or ports
yaml option).Contributions to Portical are welcome. Please submit your contributions as pull requests on GitHub.
Apache License 2.0