Traefik, Docker and dnsmasq to simplify container networking

Good tech adventures begin with some frustration, a necessity, or a requirement. That is the story of how I simplified the administration and entry of my native net purposes with the assistance of Traefik and dnsmasq. The reasoning applies simply as properly for a manufacturing server utilizing Docker.

My dev atmosphere consists of a rising variety of net purposes self-hosted on my laptop computer. Such purposes embody a number of web sites, instruments, editors, registries, … They use databases, REST APIs, or extra complicated backends. Take the instance of Supabase, the Docker Compose file consists of the Studio, the Kong API gateway, the authentication service, the REST service, the real-time service, the storage service, the meta service, and the PostgreSQL database.

The result’s a rising variety of containers began on my laptop computer, accessible at localhost on varied ports. A few of them use the default ports and can’t run in parallel to keep away from conflicts. For instance, the 3000 and 8000 ports are frequent to numerous containers current on my machine. To avoid the difficulty, some containers use customized ports which I usually occur to neglect.

The answer is to create native domains that are straightforward to recollect and use an online proxy to route the requests to the right container. Traefik helps within the routing and the invention of these companies and dnsmasq offers a customized top-level area (pseudo-TLD) to entry them.

One other utilization of Traefik is a manufacturing server utilizing a number of Docker Compose information for varied web sites and net purposes. The containers talk inside an inside community and are uncovered by means of a proxy service, in our case applied with Caddy.

Downside description

Out of many, let’s take 3 net purposes operating domestically. All of them are managed with Docker Compose:

  • Adaltas web site, 1 container, Gatsby-based static web site
  • Alliage web site, 10 containers, Subsequent.js frontend, Node.js backend, and Supabase
  • Penpot, 6 containers, Penpot frontend, backend companies plus Inbucket for e mail testing (private addition)

By default, these containers expose the next ports on localhost:

  • Adaltas
    • 8000 Gatsby server in dev mode
    • 9000 Gatsby service to serve a construct web site
  • Alliage
    • 3000 Subsequent.js web site each dev and construct mode
    • 3001 Node.js customized API
    • 3000 Supabase Studio
    • 5555 Supabase Meta
    • 8000 Kong HTTP
    • 8443 Kong HTTPS
    • 5432 PostgreSQL
    • 2500 Inbucket SMTP server
    • 9000 Inbucket Net interface
    • 1100 Inbucket POP3 server
  • Penpot
    • 2500 Inbucket SMTP server
    • 9000 Inbucket Net interface
    • 1100 Inbucket POP3 server
    • 9001 Penpot frontend

Word, relying in your atmosphere and wishes, some ports is likely to be restricted whereas different ports is likely to be accessible.

As you possibly can see, many ports collide with one another. It isn’t simply the two cases of Inbucket operating in parallel. For instance, port 8000 is used each by Gatsby and Kong. It’s a frequent default port for a number of purposes. The identical goes for ports 3000, 8080, 8443, …

One resolution is to assign distinctive ports for every service. Nevertheless, this strategy will not be scalable. Quickly sufficient, I neglect to which port every service is assigned.

Anticipated conduct

A greater resolution is the utilization of a reverse proxy with hostnames straightforward to recollect. Here’s what we count on:

  • Adaltas
    • www.adaltas.native Gatsby server in dev mode
    • construct.adaltas.native Gatsby service to serve a construct web site
  • Alliage
    • www.alliage.native Subsequent.js web site each dev and construct mode
    • api.alliage.native Node.js customized API
    • studio.alliage.native Supabase Studio
    • meta.alliage.native Supabase Meta
    • kong.alliage.native Kong HTTP
    • kong.alliage.native Kong HTTPS
    • sql.alliage.native PostgreSQL
    • smtp.alliage.native Inbucket SMTP server
    • mail.alliage.native Inbucket Net interface
    • pop3.alliage.native Inbucket POP3 server
  • Penpot
    • www.penpot.native Penpot frontend
    • smtp.penpot.native Inbucket SMTP server
    • mail.penpot.native Inbucket Net interface
    • pop3.penpot.native Inbucket POP3 server

In a standard setting, the reverse proxy is configured with one or a number of configuration information with all of the routing data. Nevertheless, a central configuration will not be so handy. It’s preferable to have every service declares which hostname they resolve.

Automated routing registration

All my net companies are managed with Docker Compose. Ideally, I count on data to be current contained in the Docker Compose file. Traefik is cloud-native within the sense that it configures itself utilizing cloud-native workflows. The applying offers some directions current in its docker-compose.yml file and the containers are robotically uncovered.

The way in which Traefik works with Docker, it plugs into the Docker socket, detects new companies, and creates the routes for you.

Beginning Traefik

To start out Traefik inside Docker is easy (by no means say straightforward). The docker-compose.yml file is:

model: '3'
    picture: traefik:v2.9
    command: --api.insecure=true --suppliers.docker
      - "80:80"
      - "8080:8080"
      - /var/run/docker.sock:/var/run/docker.sock

Registering new companies

Let’s take into account a further service. The Adaltas web site is a single container based mostly on Gatsby. In improvement mode, it begins an online server on port 8000. I count on it to be accessible with the hostname www.adaltas.native on port 80.

Following the Traefik’s getting began with Docker, the mixing is made with the property traefik.http.routers.router_name.rule current within the labels area of the docker service. It defines the hostname underneath which our web site is accessible on port 80. It’s set to www.adaltas.localhost as a result of the .localhost TLD resolves domestically by default. Since I favor to make use of the .native area, we set the area to www.adaltas.native later utilizing dnsmasq. The site visitors is then routed to the container IP on port 8000. The container port is obtained by Traefik from the Docker Compose’s ports area.

model: '3'
    container_name: adaltas-www
    - "traefik.http.routers.adaltas-www.rule=Host(`www.adaltas.localhost`)"
    - "8000:8000"

This works when each the Traefik and the Adaltas companies are outlined in the identical Docker compose file. Firing docker-compose up and you’ll:

  • http://localhost:8080: Entry the Traefik net UI
  • http://localhost:8080/api/rawdata: Entry the Traefik’s API rawdata
  • http://www.adaltas.localhost: Entry the Adaltas web site in improvement mode
  • http://localhost:8080: Similar as http://www.adaltas.localhost

There are 3 limitations we have to take care of:

  • Inside networking
    It solely works as a result of all of the companies are declared inside the identical Docker Compose file. With separated Docker Compose information, an inside community should be used to speak between the Traefic container and the targetted containers.
  • Area identify
    I want to use a pseudo top-level area (TLD), for instance, www.adaltas.native as a substitute of www.adaltas.localhost. The .native TLD doesn’t but resolve domestically, an area DNS server should be configured.
  • Port label
    The port of Adaltas is outlined contained in the Docker Compose file. Thus, it’s uncovered on the host machine and it collides with different companies. Port forwarding should be disabled and Traefik should be instructed concerning the port with one other mechanism than the ports area.

Inside networking

When outlined throughout separated information, the container can not talk. Every Docker Compose file generates a devoted community. The focused service is seen contained in the Traefik UI. Nevertheless, the request fails to be routed.

The containers should share a standard community to speak. When the Traefik container is began, a traefik_default community is created, see docker community checklist. As an alternative of making a brand new community, let’s reuse it. Enrich the Docker Compose file of the targetted container, the Adaltas web site in our case, with the community area:

model: '3'
    container_name: adaltas-www
networks:  default:    identify: traefik_default

After beginning the two Docker Compose setups with docker-compose up, the Traefik and the Web site containers begin speaking.

Area identify

It’s time to sort out the FQDN of our companies. The present TLD in use, .localhost, is completely high quality. It really works by default and it’s formally reserved for this utilization. Nevertheless, I want to use my very own top-level domains (pseudo-TLD identify), we’ll use .native on this instance.

Disclaimer, utilizing a pseudo-TLD identify will not be really useful. The .native TLD is utilized by multicast DNS / zero-configuration networking. In follow, I haven’t encountered any points. To mitigate the chance of conflicts, RFC 2606 reserves the next TLD names: .take a look at, .instance, .invalid, .localhost.

A neighborhood DNS server is used to resolve the *.native addresses. I had some expertise with Bind previously. A less complicated and extra light-weight possibility is the utilization of dnsmasq. The directions under cowl the set up on MacOS and Ubuntu Desktop. In each instances, dnsmaq is put in and configured to not intervene with the present DNS settings.

MacOS directions:

brew set up dnsmasq

mkdir -pv $(brew --prefix)/and many others/
echo 'handle=/.native/' >> $(brew --prefix)/and many others/dnsmasq.conf

sudo brew companies begin dnsmasq

sudo mkdir -v /and many others/resolver
sudo bash -c 'echo "nameserver" > /and many others/resolver/take a look at'

scutil --dns

Linux directions with NetworkManager (eg Ubuntu Desktop):

systemctl disable systemd-resolved
systemctl cease systemd-resolved
unlink /and many others/resolv.conf

cat <<CONF | sudo tee /and many others/NetworkManager/conf.d/00-use-dnsmasq.conf

cat <<CONF | sudo tee /and many others/NetworkManager/dnsmasq.d/00-dns-public.conf
cat <<CONF | sudo tee /and many others/NetworkManager/dnsmasq.d/00-address-local.conf
systemctl restart NetworkManager

Use dig to validate that any FQDN utilizing our pseudo-TLD resolves to the native

Port label

With the introduction of a reverse proxy like Traefik, exposing the container port on the host machine is now not essential, thus, eliminating the chance of collision between the uncovered port and those of different companies.

One label is already current to outline the hostname of the web site service. Traefik comes with numerous complementary labels. The traefik.http.companies.service_name.loadbalancer.server.port property tells Traefik to make use of a particular port to connect with a container.

The ultimate Docker Compose file seems to be like this:

model: '3'
    container_name: adaltas-www
    picture: node:18
      - .:/app
    consumer: node
    working_dir: /app
    command: bash -c "yarn set up && yarn run develop"
    - "traefik.http.routers.adaltas-www.rule=Host(`www.adaltas.native`)"
    - "traefik.http.companies.adaltas-www.loadbalancer.server.port=8000"
   identify: traefik_default


With Traefik, I like the concept of my container companies registering robotically in a cloud-native philosophy. It offered me with confort and ease. Additionally, dnsmasq has proved to be well-documented and fast to regulate to my varied necessities.