When working with Docker Compose, one of the most common points of confusion is the difference between ports and expose. Both deal with container networking, but they serve very different purposes.
In this blog, we’ll clarify how each works, when to use them, and the best practices for designing containerized applications.
Quick Definitions
- ports: Maps a container’s internal port to the host machine, making the service accessible from outside Docker.
- expose: Makes a container’s port available only to other services within the same Docker network, but not exposed to the host machine.
How ports Works
The ports directive creates a port mapping between the container and the host.
Example:
version: "3.8"
services:
web:
image: nginx
ports:
- "8080:80"
- Port 80 inside the container is mapped to port 8080 on the host.
- You can now access the Nginx server at:
http://localhost:8080
This is how you expose your service to the outside world (developers, users, APIs, etc.).
How expose Works
The expose directive only makes ports available internally to other services defined in the same Docker Compose network. It does not publish them to the host machine.
Example:
version: "3.8"
services:
app:
image: my-app
expose:
- "5000"
worker:
image: my-worker
depends_on:
- app
- The app service exposes port 5000.
- The worker service can connect to app:5000 over the internal network.
- But you cannot access http://localhost:5000 from the host.
This is useful for microservices or backend services that should only communicate internally.
Key Differences Between ports and expose
| Feature | ports | expose |
|---|---|---|
| Accessibility | Host machine + other containers | Only other containers (internal) |
| Syntax Example | “8080:80” | “5000” |
| Use Case | Public-facing services (e.g., web) | Internal-only communication |
| Security | Less restrictive | More restrictive |
When to Use Which
- Use ports when:
- The service must be accessed from your local machine or external clients.
- Examples: Nginx web server, API gateway, PostgreSQL database (when you want to connect with local client).
- Use expose when:
- The service should only be accessed by other containers in the same network.
- Examples: Internal APIs, background workers, private databases.
Best Practices
- Minimize ports usage in production for security; only expose what’s needed.
- Use expose for internal-only communication between microservices.
- Remember that by default all services in the same Compose project share a network, so they can already talk to each other on their container ports — expose is more like documentation than enforcement.
Conclusion
In Docker Compose:
- ports publishes ports from the container to the host machine.
- expose makes ports available to other containers only, without exposing them externally.
Choosing between them depends on whether your service should be public-facing or private to your container ecosystem. Use ports for external access and expose for internal communication — and you’ll have a cleaner, more secure architecture.