80

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
David Maze
  • 130,717
  • 29
  • 175
  • 215
Assaf Shomer
  • 1,429
  • 1
  • 15
  • 21
  • You do not "run with `--expose`". You build an image with the `EXPOSE` directive, *then* you run with `--publish` (or `-p`). See my answer below. – VonC Feb 15 '16 at 16:44

2 Answers2

126

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.

alessiosavi
  • 2,753
  • 2
  • 19
  • 38
Chris McKinnel
  • 14,694
  • 6
  • 64
  • 67
  • 3
    Listening on 0.0.0.0 looks very strange, not immediately obvious what it means. It appears to be the default though, so you can just omit that argument from the `listen()` function, which I think is easier to grasp https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback – Davos Nov 11 '17 at 11:24
  • 5
    Listening on 0.0.0.0 means "listen on all interfaces". It's often overkill, sometimes a security concern, but generally harmless. It makes sense inside a Docker container. – Jim Stewart Mar 22 '18 at 18:20
  • 1
    Thanks a lot, this solution addresses the problem with any kind of server running within docker like java or node.js express server. The interface MUST be in any case: `0.0.0.0`, where the port is the one being exported with the `-P $PORT:$PORT/tcp` option, so normally a server would check for `export HOST=0.0.0.0` at runtime. – loretoparisi Apr 04 '18 at 10:08
  • Thank you so much. I had the same problem but with netty, that I could not access it from outside of the container. Is there a better way to understand of which network interface container bounds, so not to use 0.0.0.0? – matio Nov 21 '20 at 21:54
  • This saved my day! I just realized there is obviously no way to publish a port of container's localhost interface. The rule is --publish [ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort] – dom free Jul 22 '21 at 15:59
36

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.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
  • Thank you for the comment, but as I mentioned above I already tried adding the EXPOSE 1337 and it doesn't work either – Assaf Shomer Feb 15 '16 at 16:41
  • @AssafShomer you need both, that is the all point of my answer – VonC Feb 15 '16 at 16:41
  • @AssafShomer I have edited the answer in order to make that point clearer. – VonC Feb 15 '16 at 16:43
  • 2
    Thanks, of course, I tried it with both. Namely: Build with EXPOSE 1337 and run with -p 1337:1337, still nothing. I changed the question to reflect that. – Assaf Shomer Feb 15 '16 at 16:46
  • @AssafShomer Are you directly on Linux, or are you using docker through a boot2docker VM on Windows or Mac? – VonC Feb 15 '16 at 16:48
  • Directly on Linux, no boot2docker, no Windows and no VM. Bare metal :) (almost) – Assaf Shomer Feb 15 '16 at 16:49
  • @AssafShomer Did you try with the IP of the container for testing? (I have edited the answer) – VonC Feb 15 '16 at 16:55
  • What does the `docker run --expose` switch do then? The docs say ```This exposes port ... of the container without publishing the port to the host system’s interfaces``` which implies it is available on the container's interfaces, if you know that address https://docs.docker.com/engine/reference/commandline/run/#publish-or-expose-port--p-expose Seems like a useful thing to do if you don't want your host to open up those ports into your containers. – Davos Nov 10 '17 at 15:54
  • @Davos "What does the docker run --expose switch do then? ": it exposes at runtime (for other containers to see) a port that was not initially EXPOSE'd in the Dockerfile image. – VonC Nov 10 '17 at 16:10
  • 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. There's no runtime impact that I've ever seen. – BMitch Nov 11 '17 at 17:21
  • 5
    `EXPOSE` in `Dockerfile` is totally unnecessary. It serves purely as documentation. It's a good idea, to indicate intent, but it's also misleading, because it doesn't do anything at all. It may as well be a comment. – Jim Stewart Mar 22 '18 at 18:22
  • @jim I agree. Don't hesitate to edit this answer, and make that point clearer. – VonC Mar 22 '18 at 18:23