Port Allocation Registry
This document is the single source of truth for every host port consumed by Crow itself, by bundles in bundles/, and reserved by the MVP roadmap. Any new bundle PR that introduces a port binding must amend this file in the same PR. CI enforces that:
- Every host port in any
bundles/**/docker-compose.ymlis listed here. - No two bundles map the same host port.
Conventions
- All bundle ports bind to
127.0.0.1unless the bundle is explicitly a reverse proxy (Caddy) or usesnetwork_mode: host(browser, companion, coturn, plex, tailscale, crowdsec-firewall-bouncer). - "Existing" rows are bundles already shipped before this registry was introduced — they are recorded here so future bundles avoid them.
- "Reserved" rows are claimed by upcoming Phase 2 bundles; do not consume them for unrelated work.
- "MVP" rows are claimed by the bundles in the current MVP plan.
- Not every row is a Docker port: bundles whose payload is supervised directly by the gateway (e.g.
perch-hub, a vendored Node process) claim their loopback ports here too, so nothing else takes them. Theperch-hubblock deliberately sits clear of upstream Perch's own defaults (4200/4201, pool 4101-4139) so a crow-supervised hub and a standalonepi-hubcan coexist on one host — do not "tidy" them onto those numbers.
Known conflicts in current bundles/
These predate this registry and need follow-up resolution outside the MVP scope:
| Port | Conflict |
|---|---|
| 8080 | LocalAI and Nextcloud both bind 127.0.0.1:8080 — they cannot run simultaneously |
Allocation table
| Port | Binding | Bundle / Service | Status |
|---|---|---|---|
| 22 | host | host sshd | reserved (system) |
| 25 | — | mail SMTP | reserved (Phase 2 mail) |
| 53 | host | host DNS / systemd-resolved | reserved (system) |
| 80 | 0.0.0.0 | Caddy (reverse proxy + ACME HTTP-01) | MVP PR 0.5 |
| 143 | — | mail IMAP | reserved (Phase 2 mail) |
| 443 | 0.0.0.0 | Caddy | MVP PR 0.5 |
| 465 | — | mail SMTPS | reserved (Phase 2 mail) |
| 587 | — | mail submission | reserved (Phase 2 mail) |
| 993 | — | mail IMAPS | reserved (Phase 2 mail) |
| 2222 | — | (avoid: common host anti-scan sshd port) | avoid |
| 2019 | 127.0.0.1 | Caddy admin API (host-local) | MVP PR 0.5 |
| 2223 | 127.0.0.1 | gitea (SSH) | MVP PR 5 |
| 2224 | 127.0.0.1 | forgejo (SSH) | MVP PR 5 |
| 2283 | 127.0.0.1 | immich (existing — verify in compose) | existing |
| 3001 | 127.0.0.1 | Crow gateway (HTTPS) | core |
| 3002 | 127.0.0.1 | Crow gateway (alt) | core |
| 3004 | 127.0.0.1 | Crow gateway (alt) | core |
| 3007 | 127.0.0.1 | uptime-kuma | MVP PR 1 |
| 3008 | 127.0.0.1 | Crow gateway (alt) | core |
| 3020 | 127.0.0.1 | adguard-home (admin UI) | MVP PR 3 |
| 3030 | 127.0.0.1 | homepage | MVP PR 1 |
| 3040 | 127.0.0.1 | gitea (web) | MVP PR 5 |
| 3050 | 127.0.0.1 | forgejo (web) | MVP PR 5 |
| 3061 | 127.0.0.1 | rookery (OpenScience reviewer) | PR #157 |
| 3080 | 127.0.0.1 | romm (existing) | existing |
| 3456 | 127.0.0.1 | vikunja (existing) | existing |
| 4141-4179 | 127.0.0.1 | perch-hub — pi session pool (one port per live session, allocated by the hub; PI_HUB_POOL_START/PI_HUB_POOL_END) | perch-hub P1 |
| 4210 | 127.0.0.1 | perch-hub — hub web UI, reached only via the gateway proxy at /proxy/perch-hub (CROW_PERCH_PORT) | perch-hub P1 |
| 4211 | 127.0.0.1 | perch-hub — session registry (hub-internal, never proxied) (CROW_PERCH_REGISTRY_PORT) | perch-hub P1 |
| 4533 | 127.0.0.1 | navidrome (existing) | existing |
| 5000 | 127.0.0.1 | kavita (existing) | existing |
| 5006 | 127.0.0.1 | actual-budget (existing) | existing |
| 5010 | 127.0.0.1 | changedetection | MVP PR 1 |
| 5042 | — | rotki (web/API) | reserved (Phase 2 finance) |
| 5080 | — | plausible | reserved (Phase 2 analytics) |
| 5335 | 127.0.0.1 | adguard-home (DNS, TCP+UDP) | MVP PR 3 |
| 5336 | — | pi-hole (DNS) | reserved (Phase 2 DNS) |
| 5337 | — | technitium (DNS) | reserved (Phase 2 DNS) |
| 6080 | 127.0.0.1 | browser (noVNC, existing) — overridable via CROW_BROWSER_VNC_PORT; RFB 5900 via CROW_BROWSER_RFB_PORT, CDP 9222 via CROW_BROWSER_CDP_PORT. Secondary instances on one host pick +1 offsets (6081/5901/9223), and MUST also set a distinct X display number via CROW_BROWSER_DISPLAY (default 99, secondary 98) — under network_mode: host two instances on the same display collide | existing |
| 6875 | 127.0.0.1 | bookstack (existing) | existing |
| 8000 | 127.0.0.1 | paperless (existing) | existing |
| 8002 | tailscale IP | external, freed | |
| 8004 | 127.0.0.1 | faster-whisper-server (local STT) | existing |
| 8007 | 127.0.0.1 | llamacpp-cpu-qwen3-embed (CPU embeddings) | PR #111 |
| 8080 | 127.0.0.1 | localai (existing) — also nextcloud, conflict | existing |
| 8081 | 127.0.0.1 | calibre-server (existing) | existing |
| 8083 | 127.0.0.1 | calibre-web (existing) | existing |
| 8084 | 127.0.0.1 | wallabag (existing) | existing |
| 8085 | 127.0.0.1 | miniflux (existing) | existing |
| 8086 | 127.0.0.1 | shiori (existing) | existing |
| 8088 | 127.0.0.1 | trilium (existing) | existing |
| 8089 | 127.0.0.1 + tailscale IP | edjobs nominatim (external, ~/ed-jobs-scraper) — R4 GIS tools + grackle scraper stack (ufw-scoped) | external |
| 8090 | 127.0.0.1 | capstone-tracker | shipped |
| 8091 | 127.0.0.1 | crowdsec (LAPI) | MVP PR 4 |
| 8092 | 127.0.0.1 | stirling-pdf | MVP PR 1 |
| 8094 | 127.0.0.1 | gatus | MVP PR 2 |
| 8095 | 127.0.0.1 | dozzle | MVP PR 2 |
| 8096 | 127.0.0.1 | jellyfin (existing) | existing |
| 8097 | 127.0.0.1 | vaultwarden | MVP PR 5 |
| 8098 | 127.0.0.1 | searxng | MVP PR 5 |
| 8010 | 100.118.41.122 (tailscale) | llamacpp-vulkan-qwen36-27b-copilot — co-resident critic refute/probe model, 65536 ctx, text-only (crow-addons) | existing (2026-07-06) |
| 8530 | 127.0.0.1 | adguard-home (DNS-over-TLS) | MVP PR 3 |
| 8554 | 127.0.0.1 | frigate (RTSP restream) | existing |
| 8555 | 127.0.0.1 | frigate (WebRTC) | existing |
| 8765 | 127.0.0.1 | motioneye | existing |
| 8880 | 127.0.0.1 | kokoro-tts (local TTS) | existing |
| 8971 | 127.0.0.1 | frigate (authenticated UI) | existing |
| 9000 | 127.0.0.1 | minio (S3 API, existing) | existing |
| 9001 | 127.0.0.1 | minio (console, existing) | existing |
| 9090 | 127.0.0.1 | linkding (existing) | existing |
| 11434 | 127.0.0.1 | ollama (existing) | existing |
| 13378 | 127.0.0.1 | audiobookshelf (existing) | existing |
| 18100-18199 | gateway-internal loopback | native model servers (per-CROW_HOME dynamic range, not bundle ports — managed by servers/gateway/models/state.js) | informational |
| 18789 | 127.0.0.1 | openclaw-old-docker (pre-existing external process) | existing |
| 19999 | 127.0.0.1 | netdata | MVP PR 2 |
| 32400 | host | plex (host networking, existing) | existing |
Host-networked bundles (no 127.0.0.1 binding — uses host stack directly)
These bundles use network_mode: host. They consume whatever ports their upstream service expects directly on the host:
browser(Chrome DevTools — verify ports)companion(verify)coturn(3478 UDP, plus turnserver listen ports)plex(32400, plus discovery ports)tailscale(MagicDNS, peer connections)crowdsec-firewall-bouncer(deferred to PR 4.5 — upstream does not publish a Docker image; needs a custom Dockerfile and a tested unwind command verified on a throwaway host) — will need host network to manipulate iptables/nftables
Process for amending this file
- Pick an unallocated port in a sensible range (admin UIs in 3000-3099, backend APIs in 8000-8099, metrics in 19000-19999).
- Add a row to the table with bundle name and PR/status.
- CI port-collision check (the
static-checksjob in.github/workflows/test.yml) verifies your new port doesn't clash. - Reference this file in your bundle's PR description.