Real applications are rarely one container. A web app talks to a database, a cache, a queue. For that to work, containers need a way to reach each other. Docker networks provide it.
Docker sets up a few networks out of the box. List them with:
docker network ls
You will see at least three: bridge, host, and none. The one that
matters most is bridge, the default network every container joins
unless you say otherwise.
When you run a container with no network options, it joins the default
bridge network and gets a private IP address on it. Containers on the
bridge can reach each other by IP address, and Docker connects the
bridge to the outside so containers can reach the internet and you can
publish ports to the host.
There is a catch that trips people up. On the default bridge, containers can reach each other by IP address but not by name. Those IP addresses are assigned by Docker and change when containers restart, so hardcoding them is useless. That makes the default bridge awkward for connecting an app to its database - you have no stable address to point at.
The solution is to create your own network. On a user-defined network, Docker adds automatic name resolution: containers reach each other by container name, which is stable. That single feature is why nearly every multi-container setup uses a user-defined network, and it is the subject of the next node.
docker network ls - list networks