Docker ships with several built-in network drivers, each suited to a different scenario:
| Driver | What It Does | Best For |
|---|---|---|
bridge | Private internal network on the host | Default for standalone containers |
host | Shares the host's network directly | Maximum performance, no isolation |
none | No networking at all | Air-gapped containers |
overlay | Multi-host networking | Docker Swarm / Kubernetes |
When you run a container without specifying a network, it automatically joins Docker's built-in bridge network:
docker run -d nginx
docker inspect bridge # See all containers on the default bridgeContainers on the default bridge can reach the internet and each other, but only by IP address. There's no automatic DNS, which means you'd need to hardcode IP addresses to connect containers together. That's fragile and error-prone. That's why user-defined networks exist.
Creating your own network unlocks automatic DNS between containers, and they can find each other by name instead of IP:
# Create a network
docker network create my-app-network
# Run containers on it
docker run -d --name web --network my-app-network nginx
docker run -d --name db --network my-app-network postgres:16
docker run -d --name cache --network my-app-network redis:7Now from inside the web container, you can reach the others by their container name:
docker exec -it web curl http://cache:6379 # Redis by name
docker exec -it web psql -h db -U postgres # Postgres by nameDocker's embedded DNS server handles name resolution automatically. No IP addresses needed, no manual config. This is why user-defined networks are always recommended over the default bridge.
Containers are isolated from your host machine by default. To make a service accessible from your browser, another machine, or a CI pipeline, you need to publish a port. The format is -p HOST_PORT:CONTAINER_PORT.
# Map host port 8080 to container port 80
docker run -d -p 8080:80 nginx
# Bind to localhost only (more secure as it is not accessible from other machines)
docker run -d -p 127.0.0.1:5432:5432 postgres:16
# Publish multiple ports
docker run -d -p 80:80 -p 443:443 nginx
# Let Docker pick a random available host port
docker run -d -p 80 nginx
docker port <container-id> # Check what port was assignedA key rule: Only publish ports that need to be reached from outside. Container-to-container traffic on the same network works without any port publishing at all.
# db is internal only so no -p needed
docker run -d --name db --network app-net postgres:16
# web is public
docker run -d --name web --network app-net -p 80:3000 myapp# List all networks
docker network ls
# Inspect a network (see connected containers, subnet, gateway)
docker network inspect my-app-network
# See which network(s) a container is on
docker inspect my-container --format='{{json .NetworkSettings.Networks}}' | python3 -m json.tool
# Connect a running container to a network (no restart needed)
docker network connect my-app-network my-container
# Disconnect a container from a network
docker network disconnect my-app-network my-container
# Connect with an alias (extra DNS name)
docker network connect --alias api my-app-network backend-v2A container can belong to multiple networks at the same time, it is useful when you need one container to bridge two separate networks.
EXPOSE vs -pThese two things sound similar but do very different things:
EXPOSE in Dockerfile | -p in docker run | |
|---|---|---|
| What it does | Documents which port the app uses | Actually opens the port to the host |
| Reachable from host? | No | Yes |
| Required for container-to-container? | No | No |
EXPOSE is just metadata, a note to developers (and tools like Docker Compose) saying "this container listens on this port." It doesn't open anything. Only -p actually publishes a port.
With --network host, the container skips network isolation entirely and shares the host's network stack directly. The upside is maximum throughput with no NAT overhead. The downside is zero isolation, the container can bind to any host port, which means port conflicts become your problem. Use it sparingly, typically only for performance-critical tools or network monitoring utilities.
docker run -d --network host nginx
# nginx now listens on host port 80, so no -p neededWithin a user-defined network, DNS works automatically. But you can also customize it:
# Use a specific DNS server
docker run --dns 8.8.8.8 --dns 8.8.4.4 my-app
# Add a custom /etc/hosts entry
docker run --add-host db.internal:192.168.1.50 my-app
# Set a DNS search domain
docker run --dns-search company.internal my-app| Command | What It Does |
|---|---|
docker network create <name> | Create a new bridge network |
docker network ls | List all networks |
docker network inspect <name> | See containers and config |
docker network connect <net> <c> | Add a container to a network |
docker network disconnect <net> <c> | Remove a container from a network |
docker run -p 8080:80 | Publish port 80 to host port 8080 |
docker run --network host | Use host networking (no isolation) |
-pEXPOSE in a Dockerfile is documentation only: it doesn't open any ports; -p in docker run is what actually publishes--network host removes all isolation: useful for performance-critical tools, but avoid it for regular application containers-p 8080:80 maps host port 8080 to container port 80, and you choose the host port. -p 80 tells Docker to map container port 80 to a random available port on the host. Use docker port <container> to see what port was assigned.
No. Containers on the same user-defined network can reach each other freely without any port publishing. Port publishing is only needed to make a service accessible from outside Docker which means from your browser, another machine, or the host itself.
It's documentation. It tells anyone reading the Dockerfile what port the application listens on. Some tools (like Docker Compose and Docker Desktop) use it to suggest port mappings. But it has zero effect on actual network accessibility, only -p does that.
Yes. You can connect a running container to additional networks with docker network connect. This is useful when you have a gateway or proxy container that needs to reach services on two separate isolated networks.
Bridge networking gives each container its own isolated network namespace. It has its own IP, and you control what's exposed via port publishing. Host networking removes that isolation completely; the container shares the host's network interfaces and ports directly. Bridge is safer and more flexible; host is faster but requires careful port management.
Keep data after a container is removed. Compare named volumes, bind mounts and tmpfs, then share, back up and restore volumes with working commands.
Pass docker environment variables with the -e flag and --env-file, check them in a running container, and learn why secrets need a safer approach.
Free disk space with docker system prune, docker rm and docker image prune. Learn what each command deletes, what is safe, and how to automate cleanup.