The real power of Compose shows when an app has more than one service.
You list each service under services, and Compose runs them together
on a shared network where they find each other by name.
Here is an app with two services, a web server and a Redis cache:
services:
web:
image: nginx:1.27-alpine
ports:
- "8080:80"
cache:
image: redis:7-alpine
Two services, web and cache. Compose starts both, creates one
network for the app, and attaches both to it. web can reach cache
at the hostname cache on Redis's port 6379 - no --network, no
links, nothing extra. The service name is the address, the same rule
you learned for user-defined networks, applied automatically.
Notice web publishes a port but cache does not. That is
deliberate. The web server needs to be reachable from the host, so it
maps 8080. The cache is only ever talked to by web, over the internal
network, so it needs no published port at all. Publishing it would only
expose it needlessly.
This is a good habit: publish ports only for the services that must be reached from outside - usually just the front door - and let the rest talk to each other privately on the Compose network.
Nothing new is happening under the hood. Each service is a container like the ones you ran by hand, with the same images, environment, and volumes. Compose is coordinating them - starting them together, wiring the network, giving them names. Everything you know about containers still applies to each service.