Docker networking explained in one sentence: every container gets its own small network world, and the network driver decides how that world is wired to other containers, to the host and to the internet. That sounds simple. In practice it is the spot where most self-hosting projects get stuck for the first time: the app can’t find the database, a port is reachable from outside when it shouldn’t be, or the container suddenly can’t reach the internet anymore.
We have been running dozens of containers on our own server for years, from small test shops to production services. For this article we still didn’t write anything from memory. Every claim was re-measured on 13 November 2026 on a real Linux server running Docker 29.9.3. We started throwaway containers prefixed gmnet-, had them ping each other and resolve names, and deleted everything afterwards. Wherever the measurement disagrees with what you usually read, the measurement is in the text.
The most important result first, because it affects almost every beginner:
On the default
bridgenetwork, container A could not find container B by name (ping: bad address 'gmnet-b'), but reached it instantly by IP address (0.096 ms). On a self-created network, the name worked right away.
If you take only one thing from this article: always create a dedicated network for containers that belong together. The measurements below show why.
If you’re one step earlier and want to know what containers, images and volumes are in the first place, start with What is Docker?. If you already use Compose, our Docker Compose example gives you a complete stack. This article goes one level deeper: what actually happens under the hood when Docker creates “a network”?

Docker networking explained: the basic principle
From the Linux kernel’s point of view, a container is an ordinary process running inside several so-called namespaces. One of them is the network namespace. It gives the process its own network interfaces, its own routing table and its own firewall rules. The process doesn’t see the host’s network cards, only what Docker puts into that namespace.
What Docker puts there is decided by the network driver. On our server, docker info reports these drivers:
Network: bridge host ipvlan macvlan null overlay
For 95% of use cases on a single server, only three matter: bridge, host and null (the driver behind the network called none). We introduce the other three briefly at the end so you know when you need them and when you don’t.
Right after installation, Docker creates three networks, which you can see with docker network ls:
NETWORK ID NAME DRIVER SCOPE
efea1ab2e702 bridge bridge local
9fb6f0a9d365 host host local
705caa8f1058 none null local
On a server already running Compose projects you’ll see more. Ours lists forkcart_default, shopware-test_default and getmind-demo_default, for example. The pattern is always the same: Compose creates one network per project named <folder>_default. More on that shortly, because this automatically created network is exactly why Compose services find each other by name and hand-started containers don’t.
The three commands you’ll always need
docker network ls # which networks exist?
docker network inspect <name> # subnet, gateway, attached containers
docker inspect -f '{{json .NetworkSettings.Networks}}' <container> # which networks is a container on?
These three commands narrow down almost every networking problem before you even look at logs.
The bridge driver: the default network and its quirks
If you start a container without any network option, it lands on the network called bridge. The name comes from the virtual switch Docker creates on the host: the docker0 interface. On our server it looks like this:
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
docker network inspect bridge reveals the default network’s settings:
Subnet: 172.17.0.0/16, Gateway: 172.17.0.1
com.docker.network.bridge.enable_icc: true
com.docker.network.bridge.enable_ip_masquerade: true
com.docker.network.bridge.host_binding_ipv4: 0.0.0.0
com.docker.network.driver.mtu: 1500
Three of these lines matter more than they look:
enable_icc: truemeans inter-container communication. All containers on the default network may talk to each other, including those that have nothing to do with each other.enable_ip_masquerade: trueis why containers can reach the internet. Outgoing packets get the host’s IP as their source (NAT).host_binding_ipv4: 0.0.0.0means a published port listens on all host interfaces by default, including the public one. This is the root cause of the firewall effect we measured in the Compose article.
Measurement 1: no name resolution on the default network
We started two containers without a network option and had one ping the other:
docker run -d --name gmnet-a alpine sleep 600
docker run -d --name gmnet-b alpine sleep 600
docker exec gmnet-a ping -c1 gmnet-b
Result:
ping: bad address 'gmnet-b'
By IP address, on the other hand:
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.092/0.096/0.099 ms
So the containers can talk to each other, they just don’t know each other’s names. The reason is in the container’s /etc/resolv.conf:
nameserver 185.12.64.2
nameserver 185.12.64.1
Those are simply our host’s DNS servers. On the default network Docker just copies them into the container. There’s no server that knows container names. Hard-coding the IP address isn’t a fix either, as measurement 6 below shows: these addresses change.
The old fix was --link. That option has been deprecated for years and you shouldn’t use it in new projects. The right fix is a custom network.
Custom networks: what changes compared to the default
You create a custom network with a single command:
docker network create gmnet-user
docker run -d --name gmnet-c --network gmnet-user alpine sleep 600
docker run -d --name gmnet-d --network gmnet-user alpine sleep 600
docker exec gmnet-c ping -c3 gmnet-d
Result:
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.081/0.104/0.128 ms
The name works immediately, and latency is practically identical to the default network. A custom network costs no performance. The difference is again in /etc/resolv.conf:
nameserver 127.0.0.11
options edns0 trust-ad ndots:0
What is 127.0.0.11?
127.0.0.11 is Docker’s embedded DNS server. It doesn’t run as a separate container; the Docker daemon hooks it into the network namespace of every container attached to a custom network. It answers two kinds of queries:
- Names of containers (and their aliases) on the same network it answers itself.
- Everything else, like
example.com, it forwards to the host’s DNS servers.
That’s why our test container on the custom network reached the internet without trouble (wget example.com returned 0), even though its resolv.conf only lists 127.0.0.11.
The ndots:0 option is a small detail with a big effect: a short name like db is queried directly as a full name instead of first being combined with search domains. That makes container name lookups fast and predictable.

Measurement 2: networks are separated, right down to DNS
Next we wanted to know whether a container on the custom network can reach a container on the default network. Container gmnet-b from measurement 1 had the address 172.17.0.5:
docker exec gmnet-c ping -c1 -W2 172.17.0.5
1 packets transmitted, 0 packets received, 100% packet loss
And by name:
docker exec gmnet-c nslookup gmnet-a 127.0.0.11
** server can't find gmnet-a: SERVFAIL
Two separate layers, then. At the packet layer, Docker blocks traffic between different bridge networks with its own firewall chains (on our server, iptables rules in the DOCKER-ISOLATION-STAGE-1 and -2 chains). At the name layer, the DNS server only knows containers on its own network. For a database that means: a container on another network doesn’t even learn that it exists.
That’s the strongest argument for custom networks, even ahead of name resolution. On the default network every container can reach every other one. If you run a shop, a wiki and a monitoring tool on one server and all three sit on the default network, a compromised wiki can talk straight to the shop’s database. With separate networks it can’t.
Why Compose gets this right automatically
Docker Compose creates a dedicated network for each project (<project>_default) and attaches all services to it. That’s why DB_HOST=db works in a Compose stack while the same thing fails with two hand-started docker run containers. Many beginners think this is Compose magic. It’s just a custom bridge network with the DNS server on 127.0.0.11.
One consequence worth knowing: two different Compose projects on the same server are isolated from each other by default. The reverse proxy from project A can’t reach the app from project B unless you explicitly create a shared network. How to do that is covered below in the section on third-party networks.
One container on several networks
A container can be attached to several networks at once. That’s exactly how you build clean stacks: the reverse proxy sits on the public network, the database on the internal one, and only the app sits on both.
Measurement 3: connect and disconnect at runtime
We attached container gmnet-a, which until then only lived on the default network, to our custom network after the fact:
docker network connect gmnet-user gmnet-a
docker exec gmnet-a ping -c1 gmnet-d
round-trip min/avg/max = 0.080/0.080/0.080 ms
docker inspect then shows two addresses:
bridge=172.17.0.4 gmnet-user=172.23.0.4
An interesting side finding: after the connect, gmnet-a’s /etc/resolv.conf pointed at 127.0.0.11 instead of the host’s DNS servers. As soon as a container joins a custom network, it gets the embedded DNS server, even if it’s still on the default network too.
And back:
docker network disconnect gmnet-user gmnet-a
docker exec gmnet-a ping -c1 gmnet-d
ping: bad address 'gmnet-d'
All of this happens without restarting the container. Handy when you want to give a running service access to something later, or need to cut it off from a database quickly in an emergency.
In Compose, the same pattern looks like this:
services:
proxy:
image: nginx:1.27-alpine
ports:
- "127.0.0.1:8080:80"
networks: [frontend]
app:
image: my-app
networks: [frontend, backend]
db:
image: postgres:16-alpine
networks: [backend]
# deliberately no ports:
networks:
frontend:
backend:
internal: true
The database has no ports: mapping and sits only on backend. The proxy doesn’t know it exists. We covered the interplay with the reverse proxy in detail in nginx as a reverse proxy. What a reverse proxy does in general is explained in What is a reverse proxy?.

internal: true, a network with no way out
The internal: true line in the example above deserves its own section because it’s often misunderstood. Many people think it blocks access from outside. It does that too, but its real effect concerns the other direction.
Measurement 4: an internal network can’t reach the internet
docker network create --internal gmnet-int
docker run --rm --network gmnet-int alpine sh -c 'wget -q -T3 -O- http://1.1.1.1; ip route'
wget: can't connect to remote host (1.1.1.1): Network unreachable
172.24.0.0/16 dev eth0 scope link src 172.24.0.2
The routing table explains everything: there’s only a route into the local subnet and no default route. The container can talk to its neighbours on the same network, but it simply has no idea where to send packets for the rest of the world.
For a database that’s ideal. It never needs the internet, and if someone compromises it, they can neither ship data out nor download malware from there. For the app, however, internal: true would be wrong if it needs to send email or call an third-party API. That’s why the app in the example is also attached to a normal network.
One trap: if you put a database on an internal network and still give it a ports: mapping, you’ll wonder why the port isn’t reachable from the host. Published ports don’t work on internal networks because Docker sets up no forwarding for them. That’s by design.
The host driver: no network of its own
With the host driver the container gets no network namespace of its own. It uses the host’s directly.
Measurement 5: the container sees exactly what the host sees
docker run --rm --network host alpine sh -c 'hostname; ip -o -4 addr | wc -l'
Nyx
9
On the host itself:
Nyx
9
Same hostname, same nine IPv4 addresses. Network-wise, the container lives on the host. A service listening on port 3000 inside the container is listening directly on port 3000 of the server, on all interfaces.
That’s also why -p is pointless, and Docker now says so plainly:
docker run --rm --network host -p 9999:80 alpine true
WARNING: Published ports are discarded when using host network mode
When host makes sense
- Many or dynamic ports, for example VoIP software or some game servers where mapping hundreds of ports is impractical.
- Network tools that need to see the host’s real interfaces, such as monitoring agents or packet capture tools.
- Maximum performance with very many small connections. The bridge’s NAT step disappears. In our ping test, though, the bridge was already so fast at around 0.1 ms that you won’t measure the difference on a normal web application.
When host is dangerous
With host you lose network isolation entirely. The container can talk to services that listen only on 127.0.0.1 on the host, such as a local database or an admin panel you deliberately kept off the network. So host is not a good default for normal web applications. Use it only if you have one of the reasons above.
A note for Mac and Windows users: Docker Desktop runs inside a virtual machine there. “Host” means that VM, not your computer. Our measurements apply to Linux servers where Docker runs natively.
The none driver: no network at all
With --network none the container gets its own network namespace containing nothing but the loopback interface:
docker run --rm --network none alpine sh -c 'ip -o addr; ping -c1 1.1.1.1'
1: lo inet 127.0.0.1/8 scope host lo
1: lo inet6 ::1/128 scope host
ping: sendto: Network unreachable
No network card, no route, nothing. That sounds useless, but it isn’t. none is the right choice for batch jobs that only process local files: converting images, encrypting backups, analysing unknown files. If such a job ever executes foreign code, say because a crafted file exploits a bug in the converter, that code can’t phone home. We use this ourselves for tasks where a program handles files from an unknown source.
If you’d rather walk through the drivers visually: Anton Putra covers bridge, none, host, ipvlan, macvlan and overlay in one video.
Measurement 6: container IPs are not stable
This is the finding that causes the most trouble, because it only shows up days or weeks later. We wanted to know: does a container keep its IP address when you restart it?
Starting point: gmnet-d had the address 172.23.0.3. We stopped it, started a new container gmnet-g on the same network in the meantime, and then brought gmnet-d back up:
docker stop gmnet-d
docker run -d --name gmnet-g --network gmnet-user alpine sleep 300
docker start gmnet-d
Result:
gmnet-d: 172.23.0.7 (previously 172.23.0.3)
gmnet-g: 172.23.0.3
The new container grabbed the freed address and gmnet-d got a different one. Anyone who had entered 172.23.0.3 as the database host is now talking to the wrong container. In the harmless case you get a connection error. In the nasty case, another service happens to listen on the same port at the new address.
The name, by contrast, pointed to the new address immediately:
PING gmnet-d (172.23.0.7): 56 data bytes
The rule is simple: always use container names or aliases in configuration, never IP addresses. The embedded DNS server keeps the mapping current; you don’t have to do anything.
What if I really need a fixed IP?
Sometimes you do, for a firewall rule or a program that can’t handle names. That works, but only on custom networks with a defined subnet:
docker network create --subnet 10.77.0.0/24 gmnet-sub
docker run --rm --network gmnet-sub --ip 10.77.0.50 alpine ip -o -4 addr show eth0
10.77.0.50/24
On the default network, Docker refuses explicitly:
invalid config for network bridge: invalid endpoint settings:
user-specified IP address is supported on user-defined networks only
One more argument for custom networks. In Compose you write it like this:
services:
service:
networks:
fixednet:
ipv4_address: 10.77.0.50
networks:
fixednet:
ipam:
config:
- subnet: 10.77.0.0/24
Defining your own subnet has a second benefit: it prevents overlaps. Docker automatically hands out networks from 172.17.0.0/16 upwards (our test network got 172.23.0.0/16). If your company VPN or a home network happens to use the same range, parts of it suddenly become unreachable from the server, because the server routes those packets into the Docker network. Anyone who has hunted this down once only ever sets subnets deliberately afterwards.
Aliases and DNS round robin
On a network, a container can have additional names besides its own, so-called network aliases. That’s useful when an application expects a fixed hostname such as database, but your container is named differently.
Measurement 7: two containers, one alias
docker run -d --name gmnet-e --network gmnet-user --network-alias database alpine sleep 300
docker run -d --name gmnet-f --network gmnet-user --network-alias database alpine sleep 300
docker exec gmnet-c nslookup database
Address: 172.23.0.5
Address: 172.23.0.6
The DNS server returns both addresses. In Compose, exactly this happens when you scale a service with docker compose up --scale app=3: the service name resolves to all three containers.
One detail that many guides describe differently: across our three consecutive queries, the addresses came back in the same order every time. Whether a client actually spreads the load therefore depends heavily on the client. Many programs simply take the first address. DNS round robin is no replacement for a real load balancer. If you really want to distribute load, put a proxy in front that addresses the backends itself. Why that is explained in What is a load balancer?.

From container to host and back
Two questions come up in every forum: how do I reach a service on the host from a container? And how do I reach a container from the host?
From host to container
On Linux this is easy, because the host itself has an address (the gateway) on every bridge network. A ping from the host to container IP 172.23.0.4 worked straight away:
rtt min/avg/max/mdev = 0.110/0.110/0.110/0.000 ms
Convenient for tests. For permanent use, measurement 6 applies: the IP can change. Long-term access from the host is better done through a published port bound to 127.0.0.1.
From container to host: host.docker.internal
Docker Desktop provides the name host.docker.internal automatically. On a Linux server it doesn’t, as we checked:
docker run --rm alpine ping -c1 host.docker.internal
ping: bad address 'host.docker.internal'
You can add it with the special value host-gateway, though:
docker run --rm --add-host host.docker.internal:host-gateway alpine grep host.docker /etc/hosts
172.17.0.1 host.docker.internal
Docker writes the bridge’s gateway address into the container’s /etc/hosts. In Compose:
services:
app:
extra_hosts:
- "host.docker.internal:host-gateway"
The trap: the service on the host also has to listen on that address. A database bound only to 127.0.0.1 is not reachable via 172.17.0.1, because inside the container 127.0.0.1 is the container itself, not the host. So you have to bind the host service to the bridge address as well, and add a firewall rule so it doesn’t accidentally end up on the public interface too. How to build such a rule with ufw is shown in UFW firewall setup.
Publishing ports: what -p really does
In bridge mode, containers aren’t reachable from outside at first. Only a published port (-p or ports:) opens the way. Technically, Docker creates a NAT rule for it. On our server, the DOCKER chain in the NAT table looks like this, for example:
-A DOCKER -d 127.0.0.1/32 ! -i docker0 -p tcp -m tcp --dport 3997 -j DNAT --to-destination 172.17.0.3:80
Translated: packets for 127.0.0.1:3997 are redirected to port 80 of container 172.17.0.3. Because we bind our ports to 127.0.0.1, the local address appears in the rule. With a bare -p 3997:80 that restriction is missing and the rule applies to every address of the server.
And this is the reason for the most important finding in our Compose article: these NAT rules kick in before ufw’s normal input rules are even checked. In our measurement, a bare -p 8412:80 was reachable from a foreign server even though ufw was active and had never allowed the port. The full measurement is in the Docker Compose example. The short version for this topic:
-p 127.0.0.1:8080:80→ reachable only from the server itself. Put a reverse proxy in front.-p 8080:80→ reachable from everywhere, whatever ufw says.- no
-pat all → reachable only from the same Docker network. The right choice for databases.
By the way, expose in Dockerfiles and Compose files publishes nothing. It’s purely documentation of which port the container uses. Containers on the same network reach every port anyway, with or without expose.
External networks: connecting two Compose projects
Because every Compose project gets its own network, you need a way to connect, say, a central reverse proxy to several projects. The clean way is a shared network created outside the projects:
docker network create proxy
In the proxy’s project:
services:
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
networks: [proxy]
networks:
proxy:
third-party: true
In the app project:
services:
app:
image: my-app
networks: [proxy, default]
db:
image: postgres:16-alpine
# only on the project's default network
networks:
proxy:
third-party: true
third-party: true tells Compose: this network isn’t mine, I won’t create it, and I won’t delete it on docker compose down either. The database stays on the project’s own default network, where the proxy can’t see it. Only the app lives in both worlds. Which proxy you use is a matter of taste; our comparison is at Caddy vs nginx.
Measurement 8: a network in use can’t be deleted
While cleaning up after the tests we ran into behaviour that protects your setup but also trips up scripts:
docker network rm gmnet-user
Error response from daemon: error while removing network: network gmnet-user has active endpoints
(name:"gmnet-d" ..., name:"gmnet-e" ..., name:"gmnet-c" ..., name:"gmnet-f" ...)
Docker won’t delete a network while containers are still attached, not even stopped ones. That’s why docker compose down occasionally reports that a network couldn’t be removed: usually a container from another project is still attached. The error message names the containers, which keeps the search pleasantly short. docker network prune removes all networks with nothing attached. It never touches the three default networks bridge, host and none.
macvlan, ipvlan and overlay: when you need them
The remaining three drivers solve problems that rarely come up on a single web server. You should still know them so you don’t reach for the wrong solution.
macvlan gives each container its own MAC address and thus its own address directly on the physical network, as if it were a separate device. The typical use is a home server: a Pi-hole or a smart home service should appear on the home network under its own IP. On rented servers macvlan usually doesn’t work, because hosts only let specific MAC addresses through per server. Also, the host itself can’t reach its macvlan containers directly without an extra interface, a well-known quirk of the driver.
ipvlan is macvlan’s relative: its own IP on the physical network, but all containers share the host’s MAC address. That gets around networks that allow only one MAC per port, such as some Wi-Fi setups or switches with port security.
overlay connects containers across multiple servers into one network. You need it for Docker Swarm. If you want to connect containers on several servers without setting up a swarm, a VPN between the servers is often simpler. Our comparison of the two common tools is Tailscale vs WireGuard.
NetworkChuck rebuilds the drivers live, including macvlan and ipvlan. Good if you want to try the home server case.
Which network driver for which case?
| Situation | Driver | Why |
|---|---|---|
| Normal web app with database | custom bridge network | name resolution, isolation, fixed IPs possible |
| Database, cache, queue | custom network with internal: true | no route to the internet, no published ports |
| Quick test with one container | default bridge | fine as long as nobody needs the name |
| Many or dynamic ports, network tools | host | no NAT, sees real interfaces |
| Processing files from unknown sources | none | no network, no back channel |
| Container with its own IP on the home network | macvlan (or ipvlan) | shows up as its own device |
| Containers across several servers | overlay (Swarm) or VPN | network across machines |

Common mistakes and how to find them
“Could not resolve host: db”
Almost always the two containers aren’t on the same custom network. Check both sides with docker inspect -f '{{json .NetworkSettings.Networks}}' <container>. If one only shows bridge, that’s the cause (see measurement 1). Second most common cause: a typo in the service name. In DNS, Compose services are named after the key under services:, not after container_name. Both names work, but only if you write the right one.
“Connection refused” although the name resolves
Then the network is fine, but the service in the target container listens only on 127.0.0.1 instead of 0.0.0.0. Inside a container, 127.0.0.1 means “only me”. Many development servers (Vite, some Node and Python servers) bind to localhost by default. The fix is an option like --host 0.0.0.0 when starting the service, not a change to the Docker network.
The container can’t reach the internet
First check whether it’s on an internal network (measurement 4). Then run ip route inside the container: if the default route is missing, it’s the network. If it’s there but DNS fails, look at /etc/resolv.conf. A common cause on servers is custom firewall rules that block forwarding (FORWARD) and override the rules Docker created.
Parts of the company network become unreachable from the server
A Docker network overlaps with a real network. docker network inspect shows the subnets. Fix: choose subnets deliberately with docker network create --subnet, or set default-address-pools in /etc/docker/daemon.json to control which range Docker hands out automatically.
A port is open from outside although the firewall blocks it
That’s the NAT effect from the -p section. Bind the port to 127.0.0.1 and put a reverse proxy in front. Always verify from another machine, for example with curl from your laptop. A test on the server itself proves nothing, because the server can always reach itself.
Limits of these measurements
We measured on a single Linux server with Docker 29.9.3 and the iptables backend. On Docker Desktop (Mac, Windows) everything runs in a VM and some effects look different, especially the host driver and host.docker.internal. With Podman instead of Docker, name resolution behaves similarly, but firewall integration doesn’t; the differences are in Docker vs Podman. The latency figures (around 0.1 ms) come from a handful of pings and are an order of magnitude, not a benchmark. We deleted all test containers and test networks after measuring. Afterwards, docker ps -a --filter name=gmnet- returned nothing.
Conclusion
Docker networks look complicated because there are six drivers and many options. For a normal server the topic boils down to a handful of rules, all of which we measured ourselves:
- Never use the default network for containers that belong together. It has no name resolution and every container sees every other one.
- One custom network per application. It provides DNS via
127.0.0.11and separates applications from each other right down to name resolution. - Names, not IPs. Container IPs change on restart, for us after a single stop/start.
- Databases without ports and on
internalnetworks. Then they’re neither reachable from outside nor able to phone out. - Bind ports only to
127.0.0.1, with a reverse proxy in front. A bare-pcan be open right past your firewall. - Use
hostandnonedeliberately:hostfor special cases with many ports,nonefor anything that touches foreign files.
Follow these six points and your server will have a Docker network you’ll still understand a year from now. The natural next step is the rest of the hardening: our guide to setting up a Linux server and the article on Fail2ban are good places to continue.
Frequently asked questions
What is the default network in Docker?
The network called bridge with the host interface docker0, on our server with subnet 172.17.0.0/16 and gateway 172.17.0.1. Every container started without --network lands there. Containers can reach each other by IP on it, but not by name.
What is the difference between bridge and host?
With bridge, the container gets its own isolated network with its own IP and is reachable from outside only through published ports. With host, it shares the server’s network directly: same hostname, same addresses, every port in the container is a port of the server. -p is ignored, and Docker prints a warning.
How do Docker containers communicate with each other?
Best through a custom network (docker network create) or the automatic network of a Compose project. There, every container reaches the others by name, because the embedded DNS server at 127.0.0.11 resolves the names. No ports need to be published for that.
Why can’t my container find the other one by name?
Because both are on the default bridge network. There, Docker only copies the host’s DNS servers into the container, and those don’t know container names. Create a custom network and attach both containers to it.
What is 127.0.0.11 in Docker?
The Docker daemon’s embedded DNS server. It appears in the /etc/resolv.conf of every container attached to a custom network. It answers container names on the same network itself and forwards all other queries to the host’s DNS servers.
Can a container be on several networks at the same time?
Yes. With docker network connect you can even attach it at runtime and detach it again with disconnect, without a restart. It then gets its own IP per network. That’s how you build stacks where only the app sees both the proxy and the database.
How do I give a container a fixed IP address?
Only on a custom network with a defined subnet: docker network create --subnet 10.77.0.0/24 mynet, then docker run --network mynet --ip 10.77.0.50 .... On the default network Docker refuses with “user-specified IP address is supported on user-defined networks only”. In most cases a name is the better solution anyway.
How do I reach a service on the host from inside a container?
On Linux with --add-host host.docker.internal:host-gateway (in Compose: extra_hosts). Docker then writes the host’s bridge address, 172.17.0.1 for us, into /etc/hosts. The service on the host must also listen on that address, not only on 127.0.0.1.
Does Docker bypass my firewall?
Published ports can, yes. Docker creates NAT rules that take effect before ufw’s normal input rules. So bind ports to 127.0.0.1 (-p 127.0.0.1:8080:80) and check from another machine whether they’re really closed.
What does internal: true do?
An internal network has no default route. Containers on it can reach each other, but they can’t reach the internet and their ports can’t be published. Ideal for databases and caches, unsuitable for services that need to reach third-party APIs or mail servers.
What is the difference between expose and ports?
ports (or -p) publishes a port on the host. expose publishes nothing; it only documents which port the container uses. Other containers on the same network reach every port regardless of whether it’s marked with expose.
When do I need macvlan?
When a container should appear as its own device with its own IP on the physical network, typically on a home server for services like Pi-hole. On rented servers macvlan usually doesn’t work because the host blocks foreign MAC addresses.