Docker expose service
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.
Automation
- 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:
- com.theimpossiblecode.expose.host “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).
- com.theimpossiblecode.expose.domainIsHost “true”
- If set to true a host entry will be added with the above domain.suffix.
- com.theimpossiblecode.expose.useDockerName “true”
- If set to true the hostname will be the docker host name.
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
This is now on github – see how to install it here.
Test it
Use this docker-compose.yml based on https://docs.docker.com/compose/wordpress/:
version: '3' services: db: image: mysql:5.7 volumes: - db_data:/var/lib/mysql restart: always environment: MYSQL_ROOT_PASSWORD: somewordpress MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress wordpress: # We'll want to use a name to access this service, regardless of its IP labels: com.theimpossiblecode.expose.host: "dockerwp" depends_on: - db image: wordpress:latest # No need to map the ports anymore # ports: # - "8000:80" restart: always environment: WORDPRESS_DB_HOST: db:3306 WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress volumes: db_data:
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 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1280 ;; QUESTION SECTION: ;dockerwp. IN A ;; ANSWER SECTION: dockerwp. 0 IN A 172.18.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; 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 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1280 ;; QUESTION SECTION: ;dockerwp. IN A ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) ;; WHEN: Sun Nov 19 16:04:58 UTC 2017 ;; MSG SIZE rcvd: 37
Summary
- 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.
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
Hi,
The installation instructions were changed so the race condition is no longer relevant.
Regards,
Sagi.