I would like to get mapped port from inside node.js application.
ex.
docker-compose
:
my-app:
build:
context: ./my-app
ports:
- "80"
volumes:
- /var/run/docker.sock:/tmp/docker.sock
networks:
- private
docker ps -a
:
1234 my-app "/usr/local/bin/dock…" About a minute ago Up About a minute 0.0.0.0:33962->80/tcp
And I would like to get 33962
port from node.js app.
Any ideas?
So the first thing is that currently, docker doesn't give the metadata information inside the container in any direct manner. There are workarounds to the same though
Now passing a docker socket inside your container is something that you can live with when it concerns a development environment but not prod environment
So what you want to do is build your own metadata service and then let it give the information back to your container without exposing the docker socket itself.
Now to get this metadata you can use two approaches
In this case, you create a simple endpoint like /metadata/container_id
and then return the data you want.
This service can run on the main host itself or on the container which has the docker socket mapped.
Now in your NodeJS service, you will get the current container ID and then use the same to call this service. This can be done in different ways as listed in below thread
Docker, how to get container information from within the container?
The data returned you can restrict, in case I only want ports to be exposed, I will only return the detail of the ports only.
The problem with this approach is that you are still letting the service figure out its own container id and also at the same time trusting that it is sending the correct container id
To get metadata information without passing information, we need to identify which pad made a call to the metadata server
This can be identified by using the source IP. Then we need to identify which container belongs to this IP. Once we have the container ID, we can return the metadata.
Below is a simple bash script that does the same
#!/bin/bash
CONTAINER_IP=$SOCAT_PEERADDR
CONTAINER_ID=$(docker ps -q | xargs docker inspect -f '{{.Id}}|{{range .NetworkSettings.Networks}}{{.IPAddress}}|{{end}}' | grep "|$CONTAINER_IP|" | cut -d '|' -f 1)
echo -e "HTTP/1.1 200 OK\r\nContent-Type: application/json;\r\nServer: $SOCAT_SOCKADDR:$SOCAT_SOCKPORT\r\nClient: $SOCAT_PEERADDR:$SOCAT_PEERPORT\r\nConnection: close\r\n";
METADATA=$(docker inspect -f '{{ json .NetworkSettings.Ports }}' $CONTAINER_ID)
echo $METADATA
Now to convert this to a web server we can use socat
.
sudo socat -T 1 TCP-L:10081,pktinfo,reuseaddr,fork EXEC:./return_metadata.sh
Now inside the container when we call this metadata server
root@a9cf6dabdfb4:/# curl 192.168.33.100:10081
{"80/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}]}
and the docker ps for the same
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a9cf6dabdfb4 nginx "nginx -g 'daemon of…" 3 hours ago Up 3 hours 0.0.0.0:8080->80/tcp nginx
Now how you create such a metadata server is upto you. You can use any approach
You can also add the IP of the service using extra_hosts
in your docker-compose.yml
and make the call like curl metaserver:10081
This should be a decently secure approach, without compromising on what you need
You can use docker port
for this.
docker port my-app 80
That will include the listener IP. If you need to strip that off, you can do that with the shell:
docker port my-app 80 | cut -f2 -d:
Unfortunately, this will only work with access to the docker socket. I wouldn't recommend mounting that socket for just this level of access inside your container.
Typically, most people solve this by passing a variable to their app and controlling what port is published in their docker-compose file. E.g.:
my-app:
build:
context: ./my-app
ports:
- "8080:80"
environment:
- PUBLISHED_PORT=8080
volumes:
- /var/run/docker.sock:/tmp/docker.sock
networks:
- private
Then the app would look at the environment variable to reconfigure itself.
I used a dockerode library for this.
Solution:
const Docker = require( 'dockerode' );
const os = require( 'os' );
const docker = new Docker( { socketPath: '/tmp/docker.sock' } );
const container = docker.getContainer( os.hostname() );
container.inspect( function( error, data ) {
if ( error ) {
return null;
}
const mappedPort = data.NetworkSettings.Ports[ '80/tcp' ][ 0 ].HostPort
} );
What about using shelljs
?
const shelljs = require('shelljs');
const output = shelljs.exec('docker ps --format {{.Ports}}', {silent: true}).stdout.trim();
console.log(output); // 80/tcp
// 19999/tcp
// 9000/tcp
// 8080/tcp, 50000/tcp
The output is a string, now let's map the response.
const shelljs = require('shelljs');
const output = shelljs.exec('docker ps --format {{.Ports}}', {silent: true}).stdout.trim();
const ports = output.split('\n').map(portList => portList.split(',').map(port => Number(port.split('/')[0].trim())));
This way ports
return an array of arrays containing the ports:
[ [ 80 ], [ 19999 ], [ 9000 ], [ 8080, 50000 ] ]
In your case, you want the number between :
and ->
. So you can do this:
const shelljs = require('shelljs');
const output = shelljs
.exec('docker ps --format {{.Ports}}', { silent: true })
.stdout.trim();
const aoa = output.split('\n').map(portList => portList.split(','));
console.log(aoa); // [ [ '9000/tcp', ' 0.0.0.0:9000->123/tcp ' ], [ ' 80/tcp' ] ]
let ports = [];
aoa.forEach(arr => {
arr.forEach(p => {
// match strings which contains :PORT-> pattern;
const match = p.match(/:(\d+)->/);
if (match) {
ports.push(Number(match[1]));
}
});
});
console.log(ports); // [ 9000 ]
Finally, you need to install docker inside your container and connect docker socks as explained here.
Update 29/06/2019:
As said by @Tarun Lalwani, if sharing docker socks is a problem, you may create an app which shares the network with your main app and has a GET method that returns ports
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With