This is for those times when we start a container and figure that no port or the wrong port was exposed. Docker uses it’s own bridge network, that we can define or change but there is no official documentation about how it manipulates the OS routing rules. And there is no official solution to expose a port on a docker container but there are a few workarounds. Let’s see what can we do.
For your information I am running the following versions:
➜ ~ docker --version
Docker version 18.03.1-ce, build 9ee9f40
➜ ~ uname -a
Linux razor-xps 4.13.0-41-generic #46~16.04.1-Ubuntu
First we need a docker image
Let’s work with a simple web application that runs on a port inside a container. We can build this quickly with nodejs
and http-server
.
Here is the Dockerfile
FROM node:argon
WORKDIR /home
RUN npm install -g http-server
EXPOSE 8080
CMD [ "http-server /home" ]
Then to build the image: docker build --tag http-server .
By running docker images
the new http-server
image should be present.
Let’s just run it to see it in action: docker run --name http-server -p 8000:8080 http-server:latest
. Port 8080
from inside the container is exposed to port 8000
to our local OS. By requesting http://localhost:8000
we can see the content of the /home
folder from inside the container.
Great, now let’s remove the container and start it again without exposing the port.
➜ ~ docker rm -f http-server
http-server
➜ ~ docker run --name http-server http-server:latest
Starting up http-server, serving ./
Available on:
http://127.0.0.1:8080
http://172.17.0.2:8080
Requesting http://localhost:8000
should fail now.
Here are some ways we can still access the 8080
port that our inner server runs on.
1. Access the network IP directly
Each running container that is connected to the network has it’s own IP. We can find out the IP of our http-server
by running:
docker inspect http-server
In the returned JSON, look for "IPAddress": "172.17.0.2"
, this is the IP of the container and it can be accessed from the main OS.
So we can request http://172.17.0.2:8080/
and it should return again the contents of /home
folder from inside the running container. If by any chance this is not working for you, check the OS firewall for this IP / port.
2. Commit state and start new container
The way Docker images work is by committing different states in the build process that can be reused. So we can commit the state of our container. This is very useful if we made changes inside the container after starting it. But remember that the heap will not be saved while stopping the container.
Let’s start with making a state change to our container by adding a new file inside the container:
➜ echo "some_text" > test.txt
➜ docker cp test.txt http-server:/home
Requesting http://172.17.0.2:8080/
would show the newly created file.
Let’s commit container state.
docker commit http-server http-server-commit-1
It is important to understand what this does. All commands running on docker are saved and can be inspected by running docker history http-server
. Compare it to docker history http-server http-server-commit-1
and notice that is one extra line created recently.
Now let’s stop the running container with docker stop http-server
. Requesting http://172.17.0.2:8080/
should not work anymore.
And start the new committed one with correctly exposing inner port 8080:
docker run --name http-server-commit-1 -p 8000:8080 http-server-commit-1:latest
Request http://172.17.0.2:8000/
will show the new files from new container.
To reset it back to the running container with no exposed port:
In case you did the last step let’s reset it to the container with no ports exposed.
➜ docker rm -f http-server-commit-1
http-server-commit-1
➜ docker start http-server
http-server
➜ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
62f166319cb5 http-server:latest "http-server" About an hour ago Up 1 second 8080/tcp http-server
3. By running another container as a reverse proxy in the same docker network
One docker container can access another container if both are in the same network. We will have access to our http-server
container by starting a new container where we expose the port we want and forward all the requests to the http-server
container.
Let’s quickly build an nginx
reverse proxy.
Create a file default.conf
and add the minimum nginx reverse proxy configuration:
server {
listen 8080;
location / {
proxy_pass http://http-server:8080;
}
}
Then run a new nginx
container while overwriting the default configuration.
docker run --name nginx -p 8000:8080 -v $(pwd):/etc/nginx/conf.d --link=http-server nginx
-v
will will create a volume by sharing the current folder with the nginx config folder.--link=http-server
will bridge the two containers and add access tohttp-server
using it’s name as DNS.
Request localhost:8000/
will show the files from http-server
container while going trough the reverse proxy.
Cleanup this state
docker rm -f nginx
4. By doing port forwarding trough SSH
First we need to install openssh-server
inside the container.
➜ docker exec -ti http-server bash
root@62f166319cb5:/home# apt-get update
...
root@62f166319cb5:/home# apt-get install -y openssh-server
...
root@62f166319cb5:/home# /etc/init.d/ssh start
[ ok ] Starting OpenBSD Secure Shell server: sshd.
Now we could set a password for root
inside container and ssh
using root
user. But we can use the sshd
user also. So let’s do that.
From inside container:
root@62f166319cb5:/home# passwd sshd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
Then from outside container we can connect to our ssh server inside container and forward a port. Remember that these connections usually time out by default.
ssh -NfL 8000:localhost:8080 sshd@172.17.0.2
Request localhost:8000/
will show the files from http-server
container while going trough the ssh server.
Are there other ways to do this more cleanly? Please let me know.
Every time a port is exposed on a container, there is a new process running as root
.
root 29904 0.0 0.0 182744 3484 ? Sl 22:57 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8000 -container-ip 172.17.0.3 -container-port 8080
I tried fiddling with this process and also changing iptables
configurations but it didn’t work for me.
➜ ~ sudo /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 8009 -container-ip 172.17.0.3 -container-port 8080
2018/05/14 23:08:01 Stopping proxy on tcp/[::]:8009 for tcp/172.17.0.3:8080 (accept tcp [::]:8009: accept4: bad file descriptor)
I will end it here for now.
Happy coding.