Traefik, Docker and dnsmasq to simplify container networking

Faheem

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 internet purposes with the assistance of Traefik and dnsmasq. The reasoning applies simply as nicely for a manufacturing server utilizing Docker.

My dev surroundings consists of a rising variety of internet purposes self-hosted on my laptop computer. Such purposes embrace a number of web sites, instruments, editors, registries, … They use databases, REST APIs, or extra advanced 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 widespread to numerous containers current on my machine. To bypass 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 internet proxy to route the requests to the proper container. Traefik helps within the routing and the invention of these companies and dnsmasq supplies 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 recordsdata for varied web sites and internet purposes. The containers talk inside an inner community and are uncovered via a proxy service, in our case carried out with Caddy.

Downside description

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

  • Adaltas website, 1 container, Gatsby-based static web site
  • Alliage website, 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

Notice, relying in your surroundings and wishes, some ports may be restricted whereas different ports may be accessible.

As you’ll be able to see, many ports collide with one another. It’s not simply the two situations of Inbucket operating in parallel. For instance, port 8000 is used each by Gatsby and Kong. It’s a widespread 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 method shouldn’t be scalable. Quickly sufficient, I neglect to which port every service is assigned.

Anticipated habits

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

  • 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 recordsdata with all of the routing info. Nevertheless, a central configuration shouldn’t be so handy. It’s preferable to have every service declares which hostname they resolve.

Automated routing registration

All my internet companies are managed with Docker Compose. Ideally, I anticipate info 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 supplies some directions current in its docker-compose.yml file and the containers are routinely uncovered.

The best way 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 simple (by no means say straightforward). The docker-compose.yml file is:

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

Registering new companies

Let’s think about an extra service. The Adaltas web site is a single container based mostly on Gatsby. In growth mode, it begins an internet server on port 8000. I anticipate it to be accessible with the hostname www.adaltas.native on port 80.

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

model: '3'
companies:
  www:
    container_name: adaltas-www
    ...
    labels:
    - "traefik.http.routers.adaltas-www.rule=Host(`www.adaltas.localhost`)"
    ports:
    - "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 internet UI
  • http://localhost:8080/api/rawdata: Entry the Traefik’s API rawdata
  • http://www.adaltas.localhost: Entry the Adaltas web site in growth mode
  • http://localhost:8080: Identical as http://www.adaltas.localhost

There are 3 limitations we have to take care of:

  • Inner networking
    It solely works as a result of all of the companies are declared inside the identical Docker Compose file. With separated Docker Compose recordsdata, an inner community have to 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 an alternative of www.adaltas.localhost. The .native TLD doesn’t but resolve regionally, an area DNS server have to 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 have to be disabled and Traefik have to be instructed in regards to the port with one other mechanism than the ports discipline.

Inner networking

When outlined throughout separated recordsdata, 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 typical community to speak. When the Traefik container is began, a traefik_default community is created, see docker community record. As a substitute 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 discipline:

model: '3'
companies:
  www:
    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 advantageous. 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 shouldn’t be really helpful. 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 prior to now. An easier and extra light-weight choice is the utilization of dnsmasq. The directions beneath 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 so on/
echo 'deal with=/.native/127.0.0.1' >> $(brew --prefix)/and so on/dnsmasq.conf

sudo brew companies begin dnsmasq

sudo mkdir -v /and so on/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /and so on/resolver/take a look at'

scutil --dns

Linux directions with NetworkManager (eg Ubuntu Desktop):


systemctl disable systemd-resolved
systemctl cease systemd-resolved
unlink /and so on/resolv.conf

cat <<CONF | sudo tee /and so on/NetworkManager/conf.d/00-use-dnsmasq.conf
[main]
dns=dnsmasq
CONF

cat <<CONF | sudo tee /and so on/NetworkManager/dnsmasq.d/00-dns-public.conf
server=8.8.8.8
CONF
cat <<CONF | sudo tee /and so on/NetworkManager/dnsmasq.d/00-address-local.conf
deal with=/.native/127.0.0.1
CONF
systemctl restart NetworkManager

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

Port label

With the introduction of a reverse proxy like Traefik, exposing the container port on the host machine is not needed, 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'
companies:
  www:
    container_name: adaltas-www
    picture: node:18
    volumes:
      - .:/app
    consumer: node
    working_dir: /app
    command: bash -c "yarn set up && yarn run develop"
    labels:
    - "traefik.http.routers.adaltas-www.rule=Host(`www.adaltas.native`)"
    - "traefik.http.companies.adaltas-www.loadbalancer.server.port=8000"
networks:
 default:
   identify: traefik_default

Conclusion

With Traefik, I like the concept of my container companies registering routinely 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.

Leave a Comment