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.
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.
Contact us
Join the community
to join the community
Try Guance
Start online and pay only for what you use.
Get startedChoose a Guance plan