Docker Ports
So far I have gone through how to build images, get containers running, and how to run your applications. None of that means anything if you can’t access your application outside the container! So how do we do that? We tell the host to listen on a port and forward any traffic it receives to a port on the container. The host port can not be used twice at the same time, but you can use the same port within the container.
Requirements
- Docker Introduction
- Dockerfile Build And Layers
- Docker CMD and ENTRYPOINT
- Docker Container Networking
- Dockerfile Expose
- Github example code for this post
Give the docs linked above in the requirements a read if you haven’t already and you’ll be better off.
If I run a command any of the files required to run the command should be in the Github Repo, and you should be able to run the commands as long as you are in that folder.
Steps I’m going to cover
Let’s roll
Forward Host Port To Docker Container
First thing we need to do is build an image that has a webserver. I am reusing my example from the CMD ENTRYPOINT post.
docker build -f Dockerfile.entrypoint
Sending build context to Docker daemon 2.095kB
Step 1/5 : FROM ubuntu:18.04
---> ea4c82dcd15a
Step 2/5 : RUN apt-get update && apt-get upgrade -y
---> Using cache
---> 6e04ff2dfe05
Step 3/5 : RUN apt-get install -y curl
---> Using cache
---> 176048a18465
Step 4/5 : RUN apt-get install -y nginx-light
---> Using cache
---> 56fbb8b68510
Step 5/5 : ENTRYPOINT ["nginx", "-g", "daemon off;"]
---> Using cache
---> fbdf31b4c72a
Successfully built fbdf31b4c72a
Instabuild, gotta love those layers and cache.
Now I could run this container like we have in the past, but we would not be able to access nginx that is running on it outside of the conatiner. So I now introduce the docker run
-p
command argurment. The way -p works is simple -p hostport:containerport/protocol
hostport: The port the host will listen on. You can talk to this port in order to communicate with the container.
containerport: The port inside the container that the host will forward the traffic to. Your application in the container needs to listen on this port.
/protocol: The protocol is optional and can be either ‘tcp’ or ‘udp’. tcp is the default if not specified.
You can repeat the -p argument as many times as needed for your application. You can use separate -p arguments to specify both tcp and udp ports.
Let’s start a container based on the built image that makes the host listen on port 80, and forwards to port 80 in the container.
docker run -td fbd -p 80:80
CRITICAL - This Command is wrong
46f028f8b24474e4ac8b2a8ce2ea581183cc7a9adf01b41f9c1af87d30416288
So that’s it, we started the container. Let’s see if there is anything different with docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
246835fd2ef9 hugo:latest "/usr/bin/hugo serve…" About an hour ago Up About an hour 0.0.0.0:1313->1313/tcp hugodev
This is not what I expected at all, my nginx container isn’t running and listening on port 80 like I expect….
So I ran the container just by running docker run fbd
and then exec’ed into it, made sure nginx was running like I expected. It was. So I went and looked back at the command and realized I typed it in very wrong.
WARNING - Docker cares about argument position and it expected the image to be the last argument.
Let’s try with the correct command docker run -td -p 80:80 fbd
86610c76cc75b02b57f078749e28d17e4c07b775ae7b09708d8fa65b04c0ca1f
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
86610c76cc75 fbd "nginx -g 'daemon of…" 3 seconds ago Up 2 seconds 0.0.0.0:80->80/tcp affectionate_lewin
246835fd2ef9 hugo:latest "/usr/bin/hugo serve…" 2 hours ago Up 2 hours 0.0.0.0:1313->1313/tcp hugodev
Despite the format not being great, that is what I expected. We have a second container and under ports 0.0.0.0:80->80/tcp
. This means that the host is listening on port 80 and forwarding to the containers port 80.
Info - Port 80 is the default port for http which nginx listens on after a fresh install.
Now if I curl from my local command line, not within the docker container, I get the nginx default page output.
curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
That’s it, easy. But what if I have something else running on 80 that’s not going to work, we can simply specify a different host port, but still send it to the same container port.
docker run -td -p 8080:80 fbd
53669acd89c627c3440b5d575fecabe72eefd2c529ff01ebb2e62e5ab0500058
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
53669acd89c6 fbd "nginx -g 'daemon of…" 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp pensive_golick
86610c76cc75 fbd "nginx -g 'daemon of…" 36 minutes ago Up 36 minutes 0.0.0.0:80->80/tcp affectionate_lewin
246835fd2ef9 hugo:latest "/usr/bin/hugo serve…" 2 hours ago Up 2 hours 0.0.0.0:1313->1313/tcp hugodev
From this output you can see that I have 3 containers running, I am only interested in the 2 running <“nginx -g ‘daemon of…”
. You can see that one is 0.0.0.0:8080->80/tcp
and the other is the original0.0.0.0:80->80/tcp
curl localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
Now I can curl on that new host port(8080) and get to the app running in the container which is listening on port 80.
As long as the host port is available, you can run as many containers as you have host ports. WARNING - I say that you can have as many containers as host ports, but memory, cpu, system or docker limitations may crash your system long before binding containers to all 65535 ports. Try if it you wan’t I’m not going to.
What you could now is modify the contents of one container without affecting the other to test changes. Although that is a pretty terrible workflow, and there are better ways of devloping using docker. I won’t go into development workflows using docker as it’s not my wheelhouse and I am confident there are plenty of examples out there. I kind of got into it on Switching From Pelican To Hugo - Pt1 and is how I am currently writing this post.
Expose Container Ports In The Image
On the Docker CMD and ENTRYPOINT post I explained how ENTRYPOINT can be used to ensure the correct command is run when you pass a container off to someone else. Just like we want to make sure the correct commands are run, we also want to make sure they know which ports are required for our application. The best way to do this is with detailed Documentation on your container and application. Along with the documentation we should also EXPOSE the ports for our container inside the image.
I am going to build and run my container that is EXPOSE’ing a tcp and a udp port just to show you what it looks like.
docker build -f Dockerfile.expose .
Sending build context to Docker daemon 2.56kB
Step 1/7 : FROM ubuntu:18.04
---> ea4c82dcd15a
Step 2/7 : RUN apt-get update && apt-get upgrade -y
---> Using cache
---> 6e04ff2dfe05
Step 3/7 : RUN apt-get install -y curl
---> Using cache
---> 176048a18465
Step 4/7 : RUN apt-get install -y nginx-light
---> Using cache
---> 56fbb8b68510
Step 5/7 : ENTRYPOINT ["nginx", "-g", "daemon off;"]
---> Using cache
---> fbdf31b4c72a
Step 6/7 : EXPOSE 80
---> Running in 50d29c61fd7a
Removing intermediate container 50d29c61fd7a
---> 91dbaf15316b
Step 7/7 : EXPOSE 7514/udp
---> Running in 11f2796de386
Removing intermediate container 11f2796de386
---> a212cb976e54
Successfully built a212cb976e54
Other than EXPOSE’ing the ports, it is the same as the Docker.entrypoint which gives us a container running nginx.
docker run -td a212
a93a89b4db8352c12f0d5e3bb6ecc798fae71092c4d693673198d832e2a7688e
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a93a89b4db83 a212 "nginx -g 'daemon of…" 4 seconds ago Up 3 seconds 80/tcp, 7514/udp distracted_albattani
8181d4ca447b hugo:latest "/usr/bin/hugo serve…" 18 hours ago Up 18 hours 0.0.0.0:1313->1313/tcp hugodev
We have a container it says 80/tcp
and 7514/udp
success! Let’s just curl localhost
to see the container running in all it’s glory.
curl: (7) Failed to connect to localhost port 80: Connection refused
Not so fast, did you notice that the output of docker ps
was slightly different this time? When curl localhost
worked:0.0.0.0:80->80/tcp
versus: 80/tcp
The 0.0.0.0:80->80
shows that the host is listening on 0.0.0.0:80 and sending ->
to container port 80. With docker ps, if there is no arrow, then traffic is not being forwarded. What that means is even though the ports are EXPOSEd we still need to tell docker to publish the ports with -p
. Let’s kill that container and re-start it with the ports published.
docker kill a93
a93
docker run -td -p 80:80 -p 7514:7514/udp a212
77be1ec4a0123637c18f9f3ebc15f135662378b15d9e3cb1d8f1437e11c397ac
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77be1ec4a012 a212 "nginx -g 'daemon of…" 2 seconds ago Up 1 second 0.0.0.0:80->80/tcp, 0.0.0.0:7514->7514/udp blissful_shtern
8181d4ca447b hugo:latest "/usr/bin/hugo serve…" 18 hours ago Up 18 hours 0.0.0.0:1313->1313/tcp hugodev
Notice our ->
as well we now also see that 7514 is udp
.
curl localhost
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
There we have it, we now know how to publish ports using -p
and how to EXPOSE and read EXPOSEd ports from containers that we did not make.