Create port forwarding rules on unifi routers
286
UniFi + portical โ declarative, label-driven port forwarding for Docker, backed by the UniFi controller API. (A portico is a gateway/entrance, which is exactly what a port forward is.)
Label your containers with the ports they need; Uniportical reconciles matching static UniFi port-forward rules.
๐ฆ Image: opop4444/uniporticalโ on Docker Hub โ docker pull opop4444/uniportical
On UniFi a UPnP request creates the NAT mapping but the zone-based firewall
never grants the matching WAN-in allow โ so the port is mapped yet stays closed
(verified: the IGD shows the mapping enabled=1, traffic is still dropped). A
static port forward created via the UniFi API gets the firewall allow
automatically, so it actually works. Uniportical keeps portical's model and
swaps the broken backend for one that works.
Uniportical authenticates with X-API-KEY and nothing else โ so you need a
local UniFi API key. A Site-Manager (cloud) key will not work. To create one:
https://192.168.1.2).uniportical); set an expiry..env as
UNIFI_API_KEY.Almost everything is discovered from the controller, so the environment carries only what genuinely can't be. No values have baked-in defaults; the entrypoint validates and fails fast with a clear message if a required one is missing.
| Var | Required | Notes |
|---|---|---|
UNIFI_HOST | yes | Controller IP/host, e.g. 192.168.1.2 |
UNIFI_API_KEY | yes | Local UniFi API key (or UNIFI_API_KEY_FILE for a Docker secret) |
UNIPORTICAL_POLL_INTERVAL | for poll | Seconds between reconciles. -d <seconds> overrides. |
UNIPORTICAL_HOST_IP | optional | LAN IP for host/bridge targets. Auto-detected from the route to the controller when running on the host network; set it explicitly otherwise. |
Auto-detected at runtime: controller port (probes 443 / 11443 / 8443), site, and WAN interface.
Flags (not env): -l <label> ยท -d <seconds> ยท -w <wan> ยท -v verbose ยท
-f force-update ยท -n dry-run ยท --no-prune.
Default label uniportical.forward; value is a comma-separated list of
external[:internal][/proto], or the literal published:
uniportical.forward = "8050/udp" # udp 8050 -> 8050
uniportical.forward = "8050:80" # external 8050 -> internal 80
uniportical.forward = "80,443,8050/udp" # several
uniportical.forward = "published" # every -p published port
Targets resolve to the container's own IP on macvlan/ipvlan, or the Docker
host's LAN IP on host/bridge.
docker run -d --name myapp -l uniportical.forward="8050/udp" your/image
Pulls the published image from Docker Hub โ no build step.
docker compose (uses the bundled docker-compose.yml, which reads .env):
cp .env.example .env # fill in UNIFI_HOST + UNIFI_API_KEY
docker compose run --rm uniportical check # verify key + show discovered port/site/wan
docker compose run --rm uniportical update -v -n # dry-run: preview, makes no changes
docker compose up -d
docker compose logs -f uniportical # watch reconciles
docker run (equivalent, without compose):
docker run -d --name uniportical --restart unless-stopped \
--network host \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
-e UNIFI_HOST=192.168.1.2 \
-e UNIFI_API_KEY=your-unifi-api-key \
-e UNIPORTICAL_POLL_INTERVAL=30 \
opop4444/uniportical
Run uniportical alongside your own containers โ label them, and the matching WAN port forward appears automatically:
services:
uniportical:
image: opop4444/uniportical:latest
restart: unless-stopped
network_mode: host
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
env_file:
- .env
command: ["poll"]
webapp:
image: nginx
restart: unless-stopped
ports:
- "8080:80"
labels:
- 'uniportical.forward=8080/tcp' # WAN tcp/8080 -> this host:8080 -> nginx:80
# or: uniportical.forward=published (derive from the published ports above)
Bring it up and uniportical creates uniportical: webapp 8080/tcp -> <host-ip>:8080
in UniFi. Stop or unlabel webapp and the rule is pruned on the next reconcile.
| Command | Behaviour |
|---|---|
poll (default) | Reconcile every UNIPORTICAL_POLL_INTERVAL seconds |
listen | Reconcile, then react to container start/die/destroy events |
update | Reconcile once and exit |
check | Verify key + print the auto-detected port / site / WAN |
uniportical: <container> <ext>/<proto>. Identity is
(container, external port, protocol); the internal ip:port is updated in
place when it changes.uniportical: rule no longer wanted
(container stopped/removed, label or a port removed) is deleted. --no-prune
to disable. Don't hand-name rules with that prefix.external/proto can be claimed once;
later claimants are logged and skipped (containers are processed in sorted
order, so the winner is deterministic). tcp_udp overlaps both tcp and
udp; tcp and udp on the same port from the same owner coexist.CONFLICT: app wants external 8050, already used by a non-Uniportical rule [udp8050] โ skipping [uniportical: app 8050/tcp].docker run).docker ps); stopping a container
drops its forward, starting re-adds it.-l portical.upnp.forward.MIT โ see LICENSEโ .
Not affiliated with, authorized, maintained, sponsored, or endorsed by Ubiquiti Inc. "UniFi" and "Ubiquiti" are trademarks of Ubiquiti Inc. This project uses the UniFi API โ including some undocumented endpoints โ which may change without notice. Provided "as is", without warranty of any kind; use at your own risk.
Content type
Image
Digest
sha256:32e1dd4d3โฆ
Size
71.4 MB
Last updated
5 months ago
docker pull opop4444/uniportical