System design

Localhost, 127.0.0.1, or 0.0.0.0?

Docker simultaneously is one of my favorite pieces of software because when it works it makes our lives much easier and least favorite because when it doesn’t work it can take many dev hours to debug. Recently, I ran into a version specific docker networking bug where my ports that I deliberately exposed weren’t actually anywhere to be found on localhost, 127.0.0.1 or 0.0.0.0. Adding to the confusion was my lack of understanding of these three addresses.

Since networking is only a blurb in our formal education in computer science just like pharmacology is a blurb in an a physician’s journey during their path in higher education, I’ve decided to write a quick guide on the differences between the three common loopback interfaces.

Localhost exists as a configurable alias. Some systems use IPv4 where localhost points to the address 127.0.0.1 and other systems use IPv6 where localhost points to the address ::1 . Servers can simultaneously listen on both, but generally it’s one or the other. In Windows you can change what localhost points to using PowerShell, in Unix-based systems you can change it in the /etc/hosts file.

Localhost is the least safe configuration when dealing foreign hosts and containers with unknown networking settings. Also noteworthy, as of 2024, Windows by default has localhost pointing to ::1 which may cause issues with some codebases such as integration tests querying localhost and your development server is listening on IPv4 the loopback address (127.0.0.1).

127.0.0.1 == Me, Myself and I

127.0.0.1 exists as the universal loopback address. In this context, loopback means the computer looping back to itself, like a digital narcissist always staring at its own reflection. Whatever computer 127.0.0.1 is queried on, it only refers to that computer. It’s also its own network interface, which is where things get interesting. This means that if you listen on 127.0.0.1, your server isn’t directly available to other network interfaces such as the one corresponding to your WiFi card or the one tied to your Ethernet port.

This is where Docker comes into play and things get a bit hairy. While listening on 127.0.0.1 is fine when developing something outside of a container (because your browser and the server are basically roommates on 127.0.0.1), it’s not fine when deploying your server using Docker. In the containerized world, your client likely exists outside your server’s container, and hence 127.0.0.1 would only access servers on the same machine as your client. It’s like your server’s gone into witness protection inside the container - technically there, but unreachable from the outside world. For those using containers, this means 127.0.0.1 is about as useful as a chocolate teapot when it comes to external accessibility. The IPv6 equivalent, ::1, is in the same boat, just with more colons.

0.0.0.0 == The Gregarious Host

In the context of a listening address for a TCP connection on a machine, 0.0.0.0 means any inbound IP or more succinctly all network interfaces. Your typical machine is like a Swiss Army knife of connectivity — it’s got multiple interfaces. Each physical network adapter (WiFi, Ethernet, etc.) has its own network interface, along with your computer’s self-referential network interface (127.0.0.1).

This means that if you’d like localhost , 127.0.0.1 , WiFi, and Ethernet to all point to your server, you need to have your server listen on 0.0.0.0 . When we talk about WiFi and Ethernet pointing to your server, in Docker this would translate to the virtual “network bridge” that Docker uses to connect to the other containers and the host machine. It’s like giving your server an all-access pass to the networking party.

The IPv6 version of this is just two colons — :: which due to IPv6’s consolidation scheme can be expanded to 128 bits of zeros separated by colons every 16 bits.

TLDR

The TLDR here is that in the context of Docker, you’ll almost always want to use 0.0.0.0 for your server’s listening address. This really extends to any server that you want listening to connections on any outside network. If you’re developing locally without any virtualization and you’re not deploying something for public consumption 127.0.0.1 is fine. If you’d like to avoid IPv4 VS IPv6 configuration issues, avoid the use of the alias localhost .