Skip to content
COMA

Ports

Published container ports are mirrored to localhost at the same number, and published only on 127.0.0.1 on the machine.

Every TCP port a container publishes on the machine is also available on your localhost at the same number. On the machine, COMA publishes on 127.0.0.1 only.

localhost on your computer

While you are connected (coma connect or coma workspace up), COMA watches the engine for containers that start and stop. For each published TCP port it listens on 127.0.0.1 and ::1 on your computer at the same port number, and forwards each connection over SSH to the port on the machine.

docker run -d -p 8080:80 nginx
curl localhost:8080
  • The local number always equals the machine's host port. Tools that ask the engine which port a container got, such as Testcontainers, docker port or -P with random ports, find the right number with no configuration.
  • docker run and docker start return only after the mirrors are listening (COMA waits at most 2 seconds), so a client can connect immediately.
  • Mirrors close when the container stops.
  • Only TCP is mirrored. A UDP publication shows as port_protocol_unsupported.

127.0.0.1 on the machine

With no host address, Docker publishes ports on every interface of the machine, and on a rented VM that puts your development database on the internet. Firewalls such as ufw do not stop it, because Docker's rules run first.

For containers created through COMA's endpoint, COMA rewrites the publication before the engine sees it:

  • no address, 0.0.0.0 or :: becomes 127.0.0.1;
  • -P (publish all) becomes explicit 127.0.0.1 bindings with random host ports;
  • an explicit address you chose, such as -p 203.0.113.10:8080:80, is kept and reported.

docker inspect therefore shows 127.0.0.1 as the host IP. The port is reachable from your computer through COMA, and not from the network. COMA never creates public ingress.

Containers started without COMA's endpoint are not rewritten: over a separate SSH session, or with coma docker and coma podman, which run the engine's CLI on the machine. COMA still mirrors their ports while you are connected; coma port list marks those published on every interface as all-interfaces and warns. To make Docker itself default to 127.0.0.1 for every container, bootstrap with --harden-publish:

coma machine bootstrap dev --engine docker --harden-publish

That step sets "ip": "127.0.0.1" in Docker's daemon.json, keeps a backup of the file and restarts Docker. The plan shows it before anything changes.

coma port list

coma port list

coma port list shows each mirrored or declared port with these columns:

ColumnMeaning
LOCALThe address on your computer
SOURCEmirror, or <workspace>/<name> for a port declared in coma.yaml
CONTAINERThe Compose service or container name
REMOTEThe machine and its host port, with the container port
REMOTE EXPOSUREloopback, explicit or all-interfaces
STATEready, pending, disabled, or why it is not ready

With nothing mirrored, it says so:

No ports mirrored. Publish one, e.g. `docker run -d -p 8080:80 nginx`, while connected (`coma connect`).

Another local port: spec.ports

Mirroring keeps the machine's number. To use a different local number, declare the port in the workspace's coma.yaml. A declared port replaces the mirror of the same remote port while the workspace is up.

spec:
  ports:
    - name: db
      remote: 5432
      local: 15432
    - service: web
      remote: 80
      local: auto
    - remote: 6379
      local: disabled
  • remote is the machine's host port. With service, it is the container port of that Compose service, wherever the engine publishes it.
  • local is a port number, auto or disabled, and defaults to the remote number.
  • auto picks a free port and keeps it across workspace up runs while it stays free.
  • disabled forwards nothing and also turns off the mirror for that port.
  • A declared service port whose container is not running is pending: COMA holds the local port and refuses connections until the service starts. If the service is recreated on another host port, COMA follows it and keeps the local port.
  • Only TCP is supported, and visibility: public is rejected.

Declared ports are for you and for tools that read COMA's output (coma port list --json, coma workspace status --json). Tools that ask the engine for a port number still expect the mirrored one. See the coma.yaml reference for every field.

When the local port is taken

COMA never moves a mirror to another local number on its own, because tools that ask the engine for the port could not find it. If something on your computer already uses the port, the mirror's state is local_port_in_use (local port in use in the table), and coma port list names the program that holds it when lsof can tell:

port 5432: already used on this computer by postgres (pid 4242)

Common causes are a local database, or another machine's container publishing the same port while you are connected to both. To resolve it, do one of these:

  1. Stop the local program, then restart the container (docker restart <container>). COMA creates mirrors when a container starts.
  2. Publish the container on another host port, for example "15432:5432" in the Compose file.
  3. In a workspace, declare the port in coma.yaml with another local number or local: auto, then run coma workspace up.

If two workspaces declare the same explicit local port, the second is refused with local_port_in_use, naming the other workspace. Choose another port or auto, or run coma workspace down in the other workspace.

On Linux, binding a local port below 1024 needs privileges. Such a mirror shows local_port_permission_denied; declare another local port for it.

Next

On this page