Traefik 3.0, Docker, Django->404, whoami->200

Problem statement:
On the same local server and same docker instance have two "projects".
I have a domain used for testing going through cloudflare using origin certificates instead of trying lets encrypt or certresolvers.
Cloudflare is setup as follows:

  1. Traefik docker container - exposes dashboard

  2. App docker container - with django postgres and whoami

Further question: Just before I posted this, I made one small change to ensure that both apps were identical in setup:

  • Cloudflare - added a new CNAME for the django app i.e. django.my-home-build.com and changed the rule in the label to add the django prefix as in - "traefik.http.routers.mydjango.rule=Host(django.my-home-build.com)"
  • And the BL@%#Y thing worked.

So why does this work yet an A record does not?
How to get it to work with - "traefik.http.routers.mydjango.rule=Host(my-home-build.com)"

Files:
JSON for whoami

{
    "ClientAddr": "172.68.64.173:12886",
    "ClientHost": "172.68.64.173",
    "ClientPort": "12886",
    "ClientUsername": "-",
    "DownstreamContentSize": 1086,
    "DownstreamStatus": 200,
    "Duration": 643974,
    "OriginContentSize": 1086,
    "OriginDuration": 583268,
    "OriginStatus": 200,
    "Overhead": 60706,
    "RequestAddr": "whoami.my-home-build.com",
    "RequestContentSize": 0,
    "RequestCount": 100,
    "RequestHost": "whoami.my-home-build.com",
    "RequestMethod": "GET",
    "RequestPath": "/",
    "RequestPort": "-",
    "RequestProtocol": "HTTP/2.0",
    "RequestScheme": "https",
    "RetryAttempts": 0,
    "RouterName": "whoami@docker",
    "ServiceAddr": "192.168.112.5:8082",
    "ServiceName": "whoami@docker",
    "ServiceURL": "http://192.168.112.5:8082",
    "StartLocal": "2024-05-21T11:41:16.689043293+12:00",
    "TLSCipher": "TLS_CHACHA20_POLY1305_SHA256",
    "TLSVersion": "1.3",
    "entryPointName": "websecure",
    "level": "info",
    "msg": "",
    "time": "2024-05-21T11:41:16+12:00"
}

json for www

{
    "ClientAddr": "172.68.64.150:15222",
    "ClientHost": "172.68.64.150",
    "ClientPort": "15222",
    "ClientUsername": "-",
    "DownstreamContentSize": 19,
    "DownstreamStatus": 404,
    "Duration": 24157,
    "GzipRatio": 0,
    "OriginContentSize": 0,
    "OriginDuration": 0,
    "OriginStatus": 0,
    "Overhead": 24157,
    "RequestAddr": "www.my-home-build.com",
    "RequestContentSize": 0,
    "RequestCount": 103,
    "RequestHost": "www.my-home-build.com",
    "RequestMethod": "GET",
    "RequestPath": "/",
    "RequestPort": "-",
    "RequestProtocol": "HTTP/2.0",
    "RequestScheme": "https",
    "RetryAttempts": 0,
    "StartLocal": "2024-05-21T11:41:18.552074758+12:00",
    "TLSCipher": "TLS_CHACHA20_POLY1305_SHA256",
    "TLSVersion": "1.3",
    "entryPointName": "websecure",
    "level": "info",
    "msg": "",
    "time": "2024-05-21T11:41:18+12:00"
}

You'll note, no RouterName and service entries....?
Yet have used exactly the same labels for both apps (whoami, django)

Relevant files below:

Traefik docker-compose.yml

version: "3.3"

services:
  traefik:
    image: "traefik:v3.0"
    container_name: traefik-dev
    environment:
      - TZ=Pacific/Auckland
    ports:
      - 80:80
      - 443:443
      - 8080:8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config/traefik.yml:/etc/traefik/traefik.yml:ro    # For the static configuration 
      - ./config/config.yml:/etc/traefik/config.yml:ro      # For any dynamic configuration you add
      - ./certs:/certs:ro                               # location for the certs
      - ./logs:/var/log/traefik
    labels:
      - "traefik.enable=true"           # This option overrides the provider value of exposedByDefault.
      - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$evGOymrW$$HNQIAfLV0TKlt3c.Lf1RH1"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.traefik.entrypoints=web"
      - "traefik.http.routers.traefik.rule=Host(`monitor.my-home-build.com`)"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-secure.entrypoints=websecure"
      - "traefik.http.routers.traefik-secure.rule=Host(`monitor.my-home-build.com`)"
      - "traefik.http.routers.traefik-secure.service=api@internal"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.routers.traefik-secure.tls=true"
    networks:
      - mynet

networks:
  mynet:
    external: true

Traefik traefik.yml file

global:
  checkNewVersion: false
  sendAnonymousUsage: false
log:
 filePath: /var/log/traefik/traefik.log
 format: json
 level: INFO
accesslog:
  format: json
  filePath: /var/log/traefik/access.log
  addInternals: true
  fields:
    names:
      StartUTC: drop        # allows localTime to be used when use TZ environment var
api:
  dashboard: true
  insecure: true
entryPoints:
  web:
    address: :80
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https 
  websecure:
    address: :443
serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false  
    network: mynet
  file:
    filename: /etc/traefik/config.yml
    watch: true

Traefik config.yml file

tls:
  certificates:
    - certFile: /certs/my-home-build.com.crt
      keyFile: /certs/my-home-build.com.key
http:
  middlewares:
    https-redirectscheme:
      redirectScheme:
        scheme: https
        permanent: true
    default-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https 
    # From cookiecutter-djano
    csrf:
      headers:
        hostsProxyHeaders: ['X-CSRFToken']

Then for the app files:

docker-compose.yml

volumes:
  production_postgres_data: {}
  production_postgres_data_backups: {}
  # production_traefik: {}
  production_django_media: {}

networks: 
  mynet:
    external: true

services:
  django:
    build:
      context: .
      dockerfile: ./compose/production/django/Dockerfile
    image: project_production_django
    volumes:
      - production_django_media:/app/project/media
    depends_on:
      - postgres
    env_file:
      - ./.envs/.production/.django
      - ./.envs/.production/.postgres
    labels:
      - "traefik.enable=true"           # This option overrides the provider value of exposedByDefault.
      - "traefik.http.routers.mydjango.rule=Host(`my-home-build.com`)"
      - "traefik.http.routers.mydjango.service=mydjango"
      - "traefik.http.routers.mydjango.middlewares=default-headers@file"
      - "traefik.http.services.mydjango.loadbalancer.server.port=5000"
      - "traefik.http.routers.mydjango.tls=true"  
    networks:
      - mynet
    expose:
      - 5000
    command: /start

  postgres:
    build:
      context: .
      dockerfile: ./compose/production/postgres/Dockerfile
    image: project_production_postgres
    volumes:
      - production_postgres_data:/var/lib/postgresql/data
      - production_postgres_data_backups:/backups
    env_file:
      - ./.envs/.production/.postgres
    networks:
      - mynet

  whoami-app-1:
    image: traefik/whoami:v1.7.1
    command:
      # It tells whoami to start listening on 8082 instead of 80
      - --port=8082
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.my-home-build.com`)"
      - "traefik.http.routers.whoami.service=whoami"
      - "traefik.http.routers.whoami.middlewares=default-headers@file"
      - "traefik.http.services.whoami.loadbalancer.server.port=8082"
      - "traefik.http.routers.whoami.tls=true"
    networks:
      - mynet

Note: the django project does nothing, bare bones, just to show default page.

My confusion is why does one app work yet the other does not?

Of course you don't get a response for www as that is not included in the rule. How should Traefik know to match www? Add www to the router rule:

      - "traefik.http.routers.mydjango.rule=Host(`my-home-build.com`) || Host(`www.my-home-build.com`)"