Skip to content

2026-04-21: Only expose application ports in devcontainers

2026-04-21 Accepted

Devcontainer definitions can declare forwardPorts (or equivalent Docker Compose ports mappings) to make services reachable from the host machine. In the past, every service in the stack — including backing services such as Redis and PostgreSQL — had its port forwarded to the host.

This caused a port conflict whenever two devcontainers sharing the same port declarations were started simultaneously. The problem is recurrent when running applications that are meant to work together, for example Poseidon + DMEOS or any application paired with the Account Center: starting the second container would fail because its ports were already bound on the host.

Backing services running inside the same Docker network are already reachable from the application container by hostname (e.g. redis, postgres). Forwarding their ports to the host is only needed when a developer wants to inspect them with an external tool (e.g. TablePlus, RedisInsight). Both Orbstack and Docker Desktop provide a way to reach any container directly without explicit port forwarding: Orbstack assigns a domain name per container, Docker Desktop provides a container IP.

Only expose the application’s own port(s) in devcontainer definitions. Backing-service ports (databases, caches, queues, etc.) must not be forwarded to the host.

Forwarding all service ports to the host binds those ports on the host interface. Two devcontainers that declare the same backing-service ports (e.g. 5432 for Postgres) cannot run at the same time — the second one fails to bind the already-occupied port. This is a frequent pain point when running interdependent applications locally.

Concretely, the issue originates from the ports mapping in docker-compose.yml for backing services (e.g. 5432:5432 under the postgres service). Docker attempts to bind those host ports at container start time, which is what causes the failure when the port is already taken by another running devcontainer.

The application container is the only service that a developer’s browser, API client, or test runner needs to reach from outside the Docker network. Backing services are consumed by the application container itself, which reaches them by hostname inside the Docker Compose network — no host-side binding is required.

When a developer genuinely needs direct access to a backing service (running a manual query, flushing a cache, etc.), they can connect using:

  • the domain name assigned by Orbstack (e.g. postgres.my-app.orb.local)
  • the container IP provided by Docker Desktop
  • Developers must know how to find the Orbstack domain or Docker Desktop IP to connect external tools directly to backing services. This is slightly less discoverable than a fixed host port.
  • Teams that relied on a stable host port for backing services (e.g. a shared .env pointing to localhost:5432) need to update their local tooling configuration.
  • devcontainer.json (or the Docker Compose override) must only list the application port(s) in forwardPorts / ports.
  • Backing services (Redis, PostgreSQL, Elasticsearch, etc.) must not appear in forwardPorts.
  • The docker-compose.yml must not include a ports section for any backing service. Only the application service may expose ports.
  • Application containers must reference backing services by their Docker Compose service name (hostname), not localhost.
  • Documentation and onboarding guides should explain how to reach backing services via Orbstack domains or Docker Desktop container IPs when direct access is needed.
  • Multiple devcontainers can now run concurrently on the same machine without port conflicts, enabling local multi-app workflows (e.g. Poseidon + DMEOS, any app + Account Center).