Skip to content

Networking (Tailscale) ​

Once the app is installed and configured, the next step is making it reachable — from the wall itself, from phones on your LAN, and (safely) from phones when you're away. The guiding rule for the whole page:

Never expose port 8080 to the public internet

The wall runs login-free by design (it's a trusted physical device). The companion PWA (/m) has a PIN, but that's defense-in-depth, not a firewall. Reach everything over your own LAN or a mesh VPN — Tailscale is what this project uses and what the rest of this page assumes. A forwarded port on your router is the one setup to avoid.

Where OpenFamHub needs to be reachable ​

FromNeedsHow
The wall's own browserhttp://localhost:8080Always works — same box
Phones/tablets at homeThe host on your LANLAN reachability (below)
Phones when awayThe host over your tailnetTailscale (below)

LAN reachability ​

By default docker-compose.yml publishes port 8080 on every interface, so any device on your LAN can reach http://<host-ip>:8080. For a home network you trust, that's fine — you're done.

If you'd rather lock it down further — a guest/IoT VLAN shares the router, or you want only your tailnet to ever reach the app — bind it to localhost only. In .env:

bash
BIND_ADDR=127.0.0.1

then apply it:

bash
docker compose up -d

Now the app answers only on the host itself. The kiosk's own browser (http://localhost:8080) keeps working unchanged, and you reach it from anywhere else over Tailscale (next).

Use up -d, not restart, after editing .env

docker compose restart reuses the old environment and won't pick up BIND_ADDR (or any other new value). Always re-apply with docker compose up -d.

Set up Tailscale on the host ​

Tailscale puts your devices on a private mesh network (a "tailnet") with no ports opened to the internet. Install it on the machine running the Docker stack:

bash
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Follow the login URL it prints to add the host to your tailnet. Do the same on each phone (the Tailscale app from the App Store / Play Store). Every device on the tailnet can now reach every other by name, encrypted, from anywhere — no port forwarding, no dynamic DNS.

Enable MagicDNS in the Tailscale admin console so devices are reachable by a stable name like wall.<your-tailnet>.ts.net instead of a raw tailnet IP.

Serve the app over the tailnet ​

Rather than binding the app to the LAN, let Tailscale's own proxy forward tailnet traffic to it. Run once on the host:

bash
tailscale serve --bg --tcp 8080 tcp://127.0.0.1:8080

Tailscale forwards tailnet traffic in, while the app stays bound to 127.0.0.1 and never touches the LAN-facing interface. Phones on the tailnet reach it at http://<host>.<tailnet>.ts.net:8080.

HTTPS (required for push notifications) ​

The TCP mode above forwards plain HTTP — fine for browsing. But browsers only allow service-worker push notifications from a secure context, so if you want the chore-reminder / dinner-digest notifications, serve over HTTPS instead:

bash
tailscale serve --bg --https=443 http://127.0.0.1:8080

Tailscale terminates TLS with a free certificate for your machine's tailnet name, and the PWA becomes https://<host>.<tailnet>.ts.net/m. Nothing in the app changes — the same backend serves both. Push-notification key setup (VAPID) is covered in Self-hosting → Push notifications.

Keep exactly one URL

If you set up the TCP mode first, turn it off once HTTPS works (tailscale serve --tcp=8080 off). Two working URLs for the same app is how a family ends up with half its phones on the wrong one — one HTTPS address, one bookmark, no ambiguity. Install/bookmark https://<host>.<tailnet>.ts.net/m on each phone.

The PIN is defense-in-depth ​

Each family member can have a 4-digit PIN for the companion PWA (set in .env — see First-time setup). That protects /m if a phone is unlocked and handed around — it is not a substitute for keeping the app off the public internet. Network isolation (LAN/tailnet) is the real boundary; the PIN sits on top of it.

Troubleshooting: flaky DNS ​

If the logs show occasional getaddrinfo ENOTFOUND and calendar feeds drop, or a tapped chore reverts and only sticks on a later try, the container's DNS is flaky — not the app. Docker's embedded resolver forwards to the host's resolver, and a home router's DNS forwarder (or a VPN resolver such as Tailscale MagicDNS falling back to one) can intermittently SERVFAIL under load. Every outbound lookup — calendar feeds, the Todoist close request behind a chore tap — rides that same resolver, so a blip drops a feed and reverts a tap.

Point the container straight at reliable public resolvers. In docker-compose.yml, add to the app service (and the monarch-fetcher sidecar if you run Budget):

yaml
    dns:
      - 1.1.1.1
      - 8.8.8.8

Then recreate so it takes effect (a plain restart won't re-read dns:):

bash
docker compose up -d

This is safe as long as every host the app talks to is publicly resolvable — the adapters only reach public APIs (calendar, Todoist, weather) plus your Mealie instance, which you can reference by IP if it isn't in public DNS.

Released under the MIT License.