Automated hostnames for apps running on localhost

đź•‘05:32, 20 Sep 2024

Give hostnames to your Docker applications running on your local machine using Traefik Proxy. Traefik (pronounced like traffic) is an edge router that can serve many functions such as reverse proxying and load balancing.

Why would I want to give localhost applications hostnames?

Suppose you are working on multiple projects/apps at the same time, and each of those projects are fully Dockerized and have their own docker-compose.yaml configurations. Or suppose that you have some apps that you always want running in the background, like Portainer. If you are using a standard full-stack tech stack like mine, chances are you have some application exposed on port 8000 or 3000 (or both). You will run into problems launching containers when you already have another container listening on one of those common ports. You might think a quick solution would be to just change the port number of the second app, and you would be right. However, this tactic quickly becomes unwieldy as you have more apps running, and it can be easy to forget which ports correspond to which apps. The solution: forget port numbers and deal with containers the same way the larger internet has dealt with IP addresses: use hostnames.

Prerequisites

[!INFO]

RFC 1918 networks are private networks, meaning they are used only for local networks and are not publicly routable on the internet. The RFC 1918 address ranges are divided into blocks as follows:

  • the 16 block: 192.168.0.0/16 (192.168.0.0 -192.168.255.255)
  • the 12 block: 172.16.0.0/12 (172.16.0.0-172.31.255.255)
  • the 8 block: 10.0.0.0/8 (10.0.0.0 -10.255.255.255)

Most devices understand this, so if you enter any of these addresses in a browser, they will always be searched within the local network and such requests will never leave said network.

For ideal results, we’ll set up a single, global Traefik instance so that we can forget about it later on.

First, determine your Docker installation’s default subnet. You can do this by running. Assuming you haven’t tinkered around with it, the default is 172.16.0.0/12. If you have, then you already know enough to figure this out. Find an unused /16 CIDR block that falls within your Docker subnet. For our purposes, let’s take 172.20.0.0/16. Then use the following Traefik config traefik.yml:

api:
  dashboard: true
  debug: true

log:
  filePath: /var/log/traefik/traefik.log

accessLog:
  filePath: /var/log/traefik/access.log

entryPoints:
  http:
    address: ":80"

providers:
  docker:
    exposedByDefault: false

Let’s break this down:

api:
  dashboard: true
  debug: true

enables the Traefik dashboard and debug logging.

The top-level log and accessLog keys determine where the logs will be stored.

entryPoints:
  http:
    address: ":80"

entryPoints tell Traefik what ports to expose to the host system. We create an entrypoint named http and expose it on the host’s port 80, which is the default HTTP port (we will deal with HTTPS in another blog).

providers:
  docker:
    exposedByDefault: false

enables the Docker integration, but we want to control which apps should integrate with Traefik.

We then use the following docker-compose.yml:

name: global-traefik

networks:
  default:
    name: traefik
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1
    driver_opts:
      com.docker.network.bridge.name: traefik

services:
  traefik:
    image: traefik:latest
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - 80:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
    labels:
      traefik.enable: "true"
      traefik.docker.network: traefik
      traefik.http.routers.traefik.rule: Host(`traefik.localhost`)
      traefik.http.routers.traefik.entrypoints: http
      traefik.http.routers.traefik.service: api@internal
      traefik.http.services.traefik.loadbalancer.server.port: 8080

Let’s break this down as well:

ports:
  - 80:80

corresponds to the http entrypoint we set up in traefik.yml.

volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro

mounts the host’s Docker socket into the container and give it read-only access to other running containers.

labels:
  traefik.enable: "true"
  traefik.docker.network: traefik
  traefik.http.routers.traefik.rule: Host(`traefik.localhost`)
  traefik.http.routers.traefik.entrypoints: http
  traefik.http.routers.traefik.service: api@internal
  traefik.http.services.traefik.loadbalancer.server.port: 8080

Labels are the key to Traefik integration. We explicitly enable the integration of the Traefik container itself with the Traefik proxy. We add a rule so that the Traefik dashboard becomes accessible using the hostname traefik.localhost. Note that the hostname is surrounded by backticks (`) and not quotes.

Finally, spin up the Traefik instance:

docker compose up --detach

In a browser, navigate to traefik.localhost to view the Traefik dashboard.

To try out our first app with a custom hostname, check out the following NGINX test app docker-compose.yaml:

services:
  nginx:
    image: bitnami/nginx:latest
    restart: unless-stopped
    environment:
      NGINX_HTTP_PORT_NUMBER: 8000
    ports:
      - "8000:8000"

This is how we would normally write a Docker app, and it would be accessible on localhost:8000. To make it work with Traefik, we need to modify it a bit:

networks:
  traefik:
    external: true

services:
  nginx:
    image: bitnami/nginx:latest
    restart: unless-stopped
    environment:
      NGINX_HTTP_PORT_NUMBER: 8000
    networks:
      - traefik
    labels:
      traefik.enable: "true"
      traefik.http.routers.nginx.rule: Host(`nginx-test-app.localhost`)
      traefik.http.routers.nginx.entrypoints: http
      traefik.http.services.nginx.loadbalancer.server.port: 8000

Launch the test app instance:

docker compose up --detach

The app should now be accessible on nginx-test-app.localhost.

Keywords

tech
software