Distinguish exposed and published Docker ports

Use EXPOSE as image metadata and publish ports deliberately, without confusing either setting with an application listener or firewall.

Best practices
Illustration of container operations and troubleshooting

EXPOSE records intended container ports in image metadata. docker run -p publishes a container port through a host binding. Neither starts the application's listener, and EXPOSE is not an access-control rule.

Declare an intended port

FROM nginx:stable
EXPOSE 80/tcp

The official Nginx image already declares its HTTP port; this demonstrates the syntax. An exposed-port declaration can inform users and tools, including Docker's automatic -P publishing. It does not itself create a host-port mapping or block traffic to undeclared ports.

The application must listen on the relevant container interface and port. A server bound only to the container's loopback address is normally unreachable through bridge-network port publishing.

Publish a specific binding

docker run -d --name web \
  -p 127.0.0.1:8080:80/tcp nginx:stable

For this ordinary bridge-network example, requests to host loopback port 8080 are forwarded to container port 80. Omitting the host IP generally publishes on all host interfaces, which can expose the service more widely than intended. Use a maintained Docker version and validate the actual network policy, including any version-specific localhost-publishing limitations.

An explicit -p can publish a port even when the image does not declare it with EXPOSE. Protocols matter: TCP and UDP are separate mappings. Docker's implementation may involve firewall/NAT rules or a proxy; do not assume every published port appears as an ordinary userspace listener in ss.

Understand the combinations

Settings Result
EXPOSE, no publishing Metadata only; no host binding is created by that declaration
Explicit -p, with or without EXPOSE The requested host mapping is configured
-P Exposed ports are published on automatically selected host ports

“No host binding” is not equivalent to “unreachable from anywhere.” Containers on a suitable shared network can normally reach each other's listening ports directly. Native-host routing, configured direct routing, and host-network mode have different access paths. EXPOSE cannot serve as a firewall for any of them.

Compose and service-to-service access

Compose expose describes container ports without publishing host bindings. Its ports section configures publication, for example:

services:
  web:
    image: nginx:stable
    ports:
      - "127.0.0.1:8080:80"

A peer service on the shared network uses web:80, not the host's published port. It does not need an expose entry as permission to contact another listening port. Shared-network membership, application binding, and applicable network controls determine reachability.

Verify the intended access path

docker port web
docker inspect --format '{{json .NetworkSettings.Ports}}' web

docker ps can show both exposed-port metadata and actual mappings, so inspect the bindings when the table looks ambiguous. Then make a request from the actual client network and confirm the response belongs to the intended service. A mapping alone does not verify HTTP routing, TLS, authentication, or readiness.

For ongoing checks, configure Guance HTTP tests against a URL reachable from the chosen test location. A remote test cannot reach a host-only loopback binding without an appropriate local agent or access path.

See Docker port publishing and Dockerfile EXPOSE.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting