Sign inSign up

opop4444/uniportical

By opop4444

โ€ขUpdated 5 months ago

Create port forwarding rules on unifi routers

Image
Networking
0

286

opop4444/uniportical repository overview

โ Uniportical

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

โ Why not UPnP / portical?

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.

โ Auth: a local UniFi API key

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:

  1. Open your UniFi console in a browser and sign in (e.g. https://192.168.1.2).
  2. Go to Settings โ†’ Control Plane โ†’ Integrations.
  3. Click Create API Key and give it a name (e.g. uniportical); set an expiry.
  4. Under UniFi Applications, enable Network and select your site. Leave Site Manager unchecked.
  5. Copy the key now โ€” it's shown only once, then put it in .env as UNIFI_API_KEY.

โ Configuration โ€” only the un-detectable

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.

VarRequiredNotes
UNIFI_HOSTyesController IP/host, e.g. 192.168.1.2
UNIFI_API_KEYyesLocal UniFi API key (or UNIFI_API_KEY_FILE for a Docker secret)
UNIPORTICAL_POLL_INTERVALfor pollSeconds between reconciles. -d <seconds> overrides.
UNIPORTICAL_HOST_IPoptionalLAN 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.

โ Labels

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

โ Run

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

โ Example: forward an app

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.

โ Commands

CommandBehaviour
poll (default)Reconcile every UNIPORTICAL_POLL_INTERVAL seconds
listenReconcile, then react to container start/die/destroy events
updateReconcile once and exit
checkVerify key + print the auto-detected port / site / WAN

โ Lifecycle & ownership

  • Every rule is named uniportical: <container> <ext>/<proto>. Identity is (container, external port, protocol); the internal ip:port is updated in place when it changes.
  • A container with several ports gets one rule per port; changing/removing one port touches only that port's rule.
  • Pruning is on by default: any 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.

โ Conflict guard

  • Among Uniportical rules: a given 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.
  • Never shares a port with unknowns: if a non-Uniportical rule already uses an external port โ€” for any protocol โ€” Uniportical logs it and skips, and never modifies or deletes rules it doesn't own: CONFLICT: app wants external 8050, already used by a non-Uniportical rule [udp8050] โ€” skipping [uniportical: app 8050/tcp].

โ Notes

  • Needs read-only access to the Docker socket (no docker run).
  • Forwards follow running containers (docker ps); stopping a container drops its forward, starting re-adds it.
  • Migrating from portical? Point Uniportical at your old label with -l portical.upnp.forward.

โ License

MIT โ€” see LICENSEโ .

โ Disclaimer

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.

Tag summary

Content type

Image

Digest

sha256:32e1dd4d3โ€ฆ

Size

71.4 MB

Last updated

5 months ago

docker pull opop4444/uniportical