Docker expose service

devops docker -

Docker is a great tool for DevOps, and here we’re going to make it even better! We’re going to use a simple but brilliant solution to automatically expose selected services (containers) whenever they are started. No complicated machine allocation is needed – just docker/docker-compose and a simple one time system change for your Linux.

Docker expose service

We all know we can expose docker ports, so why not services? I’ve been using docker for a while now, and have become used to pulling container IPs to connect  to the services in them. Yes – you can export ports and connect to your local host at a specific port, but I find this somewhat awkward, and it doesn’t always work for what I need. Looking around for a solution, I didn’t find anything quite satisfying.  The only thing which came close was this post, and I’m basing my solution on it.

The requirements

Expose by name which represents an IP

This goes almost without saying, since this is the main issue to solve here. It is expected to work much like a dynamic DNS service, in which the IP changes, but the name remains the same.

Selection mechanism

I want to choose which container to expose, and not in its build, since the same image could be exposed several times as services with different names.

Mapping mechanism

The container ‘hostname’ or ‘service’ names are not necessarily the names I want to use externally.

Domain and subdomain support

The docker-compose.yml can set up a small network domain with subdomain hosts, which need external access. Actually sometimes multiple subdomains can connect us to the same host, something which is common in website development.

Docker encapsulation

The selective configuration should be part of the docker-compose YAML file.


  • For each container started or stopped we want automatic detection if it needs to be exposed externally.
  • This service should be a registered system service and started at boot time.

The implementation

The implementation is based on dnsmasq, docker events, the docker LABEL feature and a small bash script. The script which will be presented shortly identifies these labels:

  • “name”
    • Here you are exposing the IP with the name.
    • If you have a domainname, then it will be appended to the hostname.
  • com.theimpossiblecode.expose.domain “domain.suffix”
    • If you want to reach the service with a domain name.
    • This will override your own external domainname.
    • You can reach the exposed host from above (if provided) with this domain suffix as well.
  • com.theimpossiblecode.expose.subdomainHosts “name [name…]”
    • These names will be appended the domain of your external host or the exposed domain name from above (overrides your own external domain).

Some or all of the above will cause the script to create a ‘hosts‘ entry for the dnsmasq, in a file which it is configured to use. After each such change the dnsmasq will be signaled to reload its config.

To install dnsmasq

sudo apt-get install dnsmasq

Configure dnsmasq for docker

Put this in /etc/dnsmasq.d/docker-dns:


Restart dnsmasq:

sudo systemctl restart dnsmasq.service

The bash script

Note that this script is a compromise between fast-simple coding/hacking and desired functionality. It could be made better over time .

This script is not on github and I’m enhancing it as I need.
Here is a list of changes from the original post:
1. Bug fixes.
2. Support swarm and compose service scaling by allowing names with com.theimpossiblecode.expose.useDockerName.
3. Support containers in internal docker networks, with IPs not showing in their docker inspect.
4. Support containers in multiple networks with several IPs.
5. Consider to some extent a docker with many containers and fast restarting ones. Delay the dnsmasq update a little to not disturb the dockerd too much.

Put this in /usr/local/bin/



# Path to the addn-hosts file

if [ `$ECHO  "$DOMAIN_NAME" | grep -v '\.'` ]; then

generate_docker_hosts ()
    $ECHO "# Auto-generated by $0" > $CONTAINER_HOSTS
    for cid in `docker ps -q`; do
        use_docker_host=`docker inspect --format='{{ index .Config.Labels "com.theimpossiblecode.expose.useDockerName"}}' $cid`
        if [ "$use_docker_host" = "true" ]; then
            host=`docker inspect --format='{{.Name}}' $cid | sed -e 's@^/@@'`
            host=`docker inspect --format='{{ index .Config.Labels ""}}' $cid`
            subdomain_hosts=`docker inspect --format='{{ index .Config.Labels "com.theimpossiblecode.expose.subdomainHosts"}}' $cid`
        container_domain=`docker inspect --format='{{ index .Config.Labels "com.theimpossiblecode.expose.domain"}}' $cid`
        if [ "$host" != "" -o "$container_domain" != "" -o "$subdomain_hosts" != "" ]; then
            ips=$(docker exec $cid ip address | perl -ne 'if (/ether/ .. /global/) {print "$1\n" if /(\d+\.\d+\.\d+\.\d+)/}')
            for ip in $ips; do
                # Take the first reachable IP
                $ECHO "Checking IP $ip"
                if ping -c 1 -W 1 $ip ; then
                    $ECHO "$ip is reachable"
                $ECHO "$ip is not reachable"
            if [ "$ip" = "" ]; then
                # No reachable IP
            $ECHO -n "$ip  " >> $CONTAINER_HOSTS
            if [ "$container_domain" = "" ]; then
            elif [ "$container_domain" != "$DOMAIN_NAME" ]; then
                $ECHO -n "$container_domain " >> $CONTAINER_HOSTS
            subdomain_hosts="$host $subdomain_hosts"  
            for host in $subdomain_hosts; do
                $ECHO -n " $host" >> $CONTAINER_HOSTS
                if [ "$container_domain" != "" ]; then
                    $ECHO -n " $host.$container_domain" >> $CONTAINER_HOSTS
            $ECHO  >> $CONTAINER_HOSTS

    # Ask dnsmasq to reload
    pkill -x -HUP dnsmasq

function set_timer
   ( sleep $1
     kill -ALRM $$
   ) &

function timeout_handler
    $ECHO "regenerating the $CONTAINER_HOSTS file after $events_raised events"
trap timeout_handler SIGALRM

# Handle race conditions when dnsmasq is started before docker0 interface was up
$ECHO "waiting for docker to be up"
if ! docker network ls > /dev/null 2>&1 ; then
    # Wait for docker networks to start
    while ! docker network ls > /dev/null 2>&1 ; do
        sleep 1
    $ECHO "restarting dnsmasq"
    systemctl restart dnsmasq

$ECHO "register for docker start/stop events"
# Listen on docker events to maintain the CONTAINER_HOSTS file
coproc docker events --filter 'event=start' --filter 'event=stop'

# Initialize the CONTAINER_HOSTS now
$ECHO "generating an initial $CONTAINER_HOSTS file"

while read -u ${COPROC[0]} line; do
    if [ $events_raised != 0 ]; then
        # Just count until the timer expires
        let events_raised++
        # Start counting events and handle them all at once
        #  after WAIT_EVENS_TO seconds
        $ECHO "will generate $CONTAINER_HOSTS in $WAIT_EVENS_TO seconds"
        set_timer $WAIT_EVENS_TO

Make sure it is executable:

sudo chmod +x /usr/local/bin/

Set up the script as a service

Put this in /etc/systemd/system/docker-dnsmasq.service:

Description=Update dnsmasq with selected docker containers
After=docker.service dnsmasq.service



Enable and start the service

sudo systemctl enable docker-dnsmasq.service
sudo systemctl start docker-dnsmasq.service

Test it

Use this docker-compose.yml based on

version: '3'

     image: mysql:5.7
       - db_data:/var/lib/mysql
     restart: always
       MYSQL_ROOT_PASSWORD: somewordpress
       MYSQL_DATABASE: wordpress
       MYSQL_USER: wordpress
       MYSQL_PASSWORD: wordpress

     # We'll want to use a name to access this service, regardless of its IP
     labels: "dockerwp"
       - db
     image: wordpress:latest
     # No need to map the ports anymore
     # ports:
     #  - "8000:80"
     restart: always
       WORDPRESS_DB_HOST: db:3306
       WORDPRESS_DB_USER: wordpress
       WORDPRESS_DB_PASSWORD: wordpress

start it with:

 docker-compose up -d

Check the DNS lookup with dig:

dig dockerwp
; <<>> DiG 9.10.3-P4-Ubuntu <<>> dockerwp
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42941
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 1280
;dockerwp.			IN	A

dockerwp.		0	IN	A

;; Query time: 0 msec
;; WHEN: Sun Nov 19 15:59:37 UTC 2017
;; MSG SIZE  rcvd: 53

You can also open your browser now at http://dockerwp

Now bring down this setup with:

docker-compose down

And try again the DNS lookup to make sure nothing is found:

dig dockerwp
; <<>> DiG 9.10.3-P4-Ubuntu <<>> dockerwp
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 26781
;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1

; EDNS: version: 0, flags:; udp: 1280
;dockerwp.			IN	A

;; Query time: 0 msec
;; WHEN: Sun Nov 19 16:04:58 UTC 2017
;; MSG SIZE  rcvd: 37


  • This is a simple way to both selectively and automatically add, by name, small docker services to your system for whatever needs you have. This adds a very powerful tool to your DevOps  toolbox.
  • I’m interested to know if you found an easier way to cope with these requirements or if you find a problem with this setup. Please share.

See you next time,

Sagi Zeevi

A software developer brain surgeon ... if software only had a brain. An electronics hobbyist heart surgeon ... if electronics only had a heart.

You may also like...

2 Responses

  1. Hi Sagi,

    I have studied your Post, this is a good idea to improve docker container handling. But a question inside the bash script i have:

    You “handle race conditions with dnsmasq service, if its starts before docker service”. The function ‘generate_docker_hosts’, subsequently called after these lines, restarts the dnsmasq service with every call. Thus, in this case, you restart the dnsmask service twice. Is this intended?

    Thank you for your replay –
    D. Schreier, Germany

    • Sagi Zeevi says:


      You’re right this is a puzzling flow, but SIGHUP will only ask dnsmasq to reload its configuration files.
      The problem is that if dnsmasq is started before the docker0 network is ready, then it will not read its configuration – not at startup and not at SIGHUP.
      So we wait for docker networks to come up, and then restart dnsmasq.

      Hope this is more clear now,

Leave a Reply

Scroll Up