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.
- Some Docker containers you want to expose to the internet
- UPnP-enabled internet gateway (tested on Google Nest Wifi)
There are 2 parts to Portical:
- Add the
portical.upnp.forward
label and rules (published
,8080:80/tcp
,8080:80
or8080
,8080/up
etc) to your Docker containers to expose them to the internet. - Run Portical container to set up port forwarding rules and monitor as many containers as you want.
The 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 defaultbridge
network driver and reduces duplication.9999:8000/tcp
will forward port9999
on internet gateway to the docker network's port8000
using only the TCP protocol.25565:25565
will forward port25565
on internet gateway to the docker network's port25565
using both TCP and UDP protocol.19132:19132/udp
will forward port19132
on internet gateway to the docker network's port19132
using only the UDP protocol.19132/udp,8080/tcp
will forward port two ports UDP port19132
and TCP port8080
.
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)- Warning UPnP rules expire.
-
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:
- Uses Docker's API (via
/var/run/docker.sock
) to find containers with the specified labelportical.upnp.forward
. - Determine the network driver / type used (supports
bridge
,host
,macvlan
andipvlan
). - Connects to the internet gateway from that network (so the gateway allows it)
- Sends UPnp port forwarding rules specified.
- Repeat after the specified interval (default
15
seconds).
It's worth understanding that depending on the network driver, how port forwarding works is different:
- For
bridge
network driver (the default), traffic will be making a double hop, once from the internet gateway to the docker interface (controlled by theportical.upnp.forward
label rule), then from the docker interface to the target container (controlled by the normal docker ports-p
flag orports
yaml option). - For
host
,macvlan
oripvlan
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 orports
yaml option).
- Support sigterm/sigkill
- Test more corner cases
Contributions to Portical are welcome. Please submit your contributions as pull requests on GitHub.
Apache License 2.0