Ports To Expose
Compose ports: publishes a container port to a host port. On Fibe, all user-facing HTTP routing is done by Traefik based on fibe.gg/expose. Drop ports: and add the label.
Mapping
Compose ports: form | Container port | Fibe label |
|---|---|---|
- "3000:3000" | 3000 | fibe.gg/expose: external:3000 |
- "8080:80" | 80 | fibe.gg/expose: external:80 |
- "5173" | 5173 (auto host) | fibe.gg/expose: external:5173 |
- "127.0.0.1:8000:8000" (host-loopback only) | 8000 | fibe.gg/expose: internal:8000 (Basic Auth) |
- "9000:9000" for an admin console | 9000 | fibe.gg/expose: internal:9000 |
The PORT in the label is the container port (the second number in Compose's host:container form, or the only number when bare). Fibe owns host port allocation; you cannot pin a host port.
Step-by-step
- Read the container port from the rightmost colon of each
ports:entry. - Decide visibility:
- Public web →
external: - Admin/staff →
internal: - Not human-facing (DB/queue/cache) → no
fibe.gg/exposeat all; delete theports:entry.
- Public web →
- Add the label under
labels:on the service. - Delete the
ports:block from the service. - Add
fibe.gg/subdomainif you don't want the default (service name) routing. See recipe-add-subdomain. - Ensure the app binds
0.0.0.0inside the container — see decide-exposure-strategy.
Before / after
Public web
# BEFORE
services:
web:
image: ghcr.io/owner/app:latest
ports:
- "3000:3000"
# AFTER
services:
web:
image: ghcr.io/owner/app:latest
labels:
fibe.gg/expose: external:3000
Multiple ports — pick the one humans use, drop the rest
If a Compose service publishes multiple ports (e.g. main HTTP + metrics), only one can be public-facing per fibe.gg/expose label. For metrics/admin on a different port, split into two services if they really need separate routing, or omit the extra port entirely (it's reachable inside the Compose network without ports:).
# BEFORE
services:
app:
image: my-app
ports:
- "8080:8080" # HTTP
- "9090:9090" # metrics
# AFTER — keep only the HTTP one
services:
app:
image: my-app
labels:
fibe.gg/expose: external:8080
Metrics still reachable as app:9090 from another container inside the Compose network.
Internal admin
# BEFORE
services:
pgadmin:
image: dpage/pgadmin4:latest
ports:
- "127.0.0.1:5050:80"
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
# AFTER
services:
pgadmin:
image: dpage/pgadmin4:latest
labels:
fibe.gg/expose: internal:80
fibe.gg/subdomain: pgadmin
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
The Marquee-level Basic Auth credentials apply.
Database / cache — drop entirely
# BEFORE
services:
db:
image: postgres:17
ports:
- "5432:5432" # often only there for "I want to connect from my laptop"
# AFTER
services:
db:
image: postgres:17
# no labels/ports — service is reachable as "db:5432" inside the Compose network
Apps in the same Compose network reach Postgres as db:5432. If a Player needs psql access from their laptop, do it through fibe_playgrounds_debug and an exec into the container — never expose the database on the public internet.
Variable-driven port
If the port should be configurable at launch:
services:
web:
image: ghcr.io/owner/app:latest
labels:
fibe.gg/expose: external:$$var__PORT
x-fibe.gg:
variables:
PORT:
name: "Container port"
required: true
default: "3000"
validation: "/^[0-9]+$/"
Pitfalls
- Leaving
ports:while also settingfibe.gg/expose— works for non-zero-downtime services, butports:is a security/firewall surprise. Always remove it. - Leaving
ports:while turning onfibe.gg/zerodowntime: "true"— validator rejects (zerodowntime services cannot have 'ports'). - Setting
fibe.gg/exposeon a service that doesn't actually listen on that port — Traefik routes traffic; the container 404s or refuses. Verify withdocker exec <c> ss -ltnp(or equivalent). - Using
external:for a port the app binds to localhost only —0.0.0.0is required. Fix the app's bind config (see decide-exposure-strategy).
Related skills
decide-exposure-strategy, recipe-add-subdomain, recipe-add-path-rule, recipe-strip-incompatible-keys, recipe-inline-variables, reference-fibe-labels.