Containerized Node server inaccessible with server.listen(port, '127.0.0.1')

I set up a simple Node server in Docker.

Dockerfile

FROM node:latest
RUN apt-get -y update
ADD example.js .
EXPOSE 1337   
CMD node example.js

example.js

var http = require('http');
http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n'+new Date);
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');

Now build the image

$ docker build -t node_server .

Now run in container

$ docker run -p 1337:1337 -d node_server  
$ 5909e87302ab7520884060437e19ef543ffafc568419c04630abffe6ff731f70

Verify the container is running and ports are mapped:

$ docker ps  

CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
5909e87302ab        node_server         "/bin/sh -c 'node exa"   7 seconds ago       Up 6 seconds        0.0.0.0:1337->1337/tcp   grave_goldberg

Now let's attach to the container and verify the server is running inside:

$ docker exec -it 5909e87302ab7520884060437e19ef543ffafc568419c04630abffe6ff731f70 /bin/bash 

And in the container command line type:

root@5909e87302ab:/# curl http://localhost:1337
Hello World
Mon Feb 15 2016 16:28:38 GMT+0000 (UTC)

Looks good right?

The problem

When I execute the same curl command on the host (or navigate with my browser to http://localhost:1337) I see nothing.

Any idea why the port mapping between container and host doesn't work?

Things I already tried:

  • Running with the --expose 1337 flag

Your ports are being exposed correctly but your server is listening to connections on 127.0.0.1 inside your container:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n'+new Date);
}).listen(1337, '127.0.0.1');

You need to run your server like this:

http.createServer(function (req, res) {
    res.writeHead(200, {'Content-Type': 'text/plain'});
    res.end('Hello World\n'+new Date);
}).listen(1337, '0.0.0.0');

Note the 0.0.0.0 instead of 127.0.0.1.


Adding EXPOSE 1337 to the docker file

EXPOSE is mandatory if you want to "expose" that port to other containers.

As BMitch comments:

Expose isn't needed to publish a port or to connect container to container over a shared docker network.
It's metadata for publishing all ports with -P and inspecting the image/container.

So:

Running with the --expose 1337 flag

Not exactly: you need to docker run it with -p 1337:1337

You need either:

  • build an image with the EXPOSE directive in it (used by -P)
  • or run it with the port published on the host -p 1337:1337

The test curl http://localhost:1337 was done from within the container (no EXPOSE or publish needed).
If you want it to work from the Linux host, you need EXPOSE+-P or you need -p 1337:1337.
Either.

Declaring it expose alone is good for documenting the intent, but does not do anything alone.

For instance:

https://i.stack.imgur.com/wmKgd.png

In that figure, 8080 is EXPOSE'd, published to the Linux host 8888.
And if that Linux host is not the actual host, that same port needs to be fastfowarded to the actual host. See "How to access tomcat running in docker container from browser?".

If localhost does not work from the Linux host, try its IP address:

CID=$(docker run -p 1337:1337 -d node_server)
CIP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${CID})
curl http://${CIP}:1337

Or, as mentioned above, make your server listen from connections coming from any IP: 0.0.0.0 which is the broadcast address or zero network.