404 site with subdirectories

Recently I've tried to host a simple static page behind my Traefik instance and the following problem is driving me crazy the last few days.

My Traefik image version: traefik:v3.0

I have a simple Application in a Dockerfile that looks like the following:

FROM node:lts AS build
WORKDIR /app
COPY . .
RUN npm i
RUN npm run build

FROM httpd:2.4 AS runtime
COPY --from=build /app/dist /usr/local/apache2/htdocs/

I built the Image against a container registry. On top I've configured a docker-compose.yml with the Traefik labels. The docker-compose looks like the following:

version: "3.3"

services:
  my_page:
    image: ghcr.io/my_page:main
    restart: unless-stopped
    container_name: my_page
    networks:
      - web
      - default
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=traefik_web"
      - "traefik.http.routers.my_page.entrypoints=websecure"
      - "traefik.http.routers.my_page.rule=Host(`example.com`)"
      - "traefik.http.routers.my_page.tls=true"
      - "traefik.http.routers.my_page.tls.certresolver=default"

networks:
  default:
  web:
    external:
      name: traefik_web

If I go to example.com I can see my index.html just fine. However, if I want to visit example.com/features it will return a 404 for me. When I access example.com:80/features my subpage is displayed correctly. I think I am missing something really simple here but I can't figure out what it is. Do you have any ideas left on what I can try? Thanks in advance!

Hello,

Traefik v3 is in the beta phase, I recommend using the latest stable version of Traefik v2.
https://hub.docker.com/_/traefik

You didn't provide your traefik service definition (static configuration, etc.)

So I created a "light" and reproducible example based on your snippets.

Content of ./htdocs

.
├── features
│   └── index.html (content: "features")
└── index.html (content: "root")
Traefik v2
version: '3.7'

services:

  traefik:
    image: traefik:v2.10.1
    command:
      - --log.level=debug
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls=true
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
 
  foo:
    image: httpd:2.4
    container_name: my_page
    volumes:
      - ./htdocs/:/usr/local/apache2/htdocs/
    labels:
      traefik.enable: true
      traefik.http.routers.my_page.rule: Host(`example.localhost`)
      # traefik.docker.network: traefik_web
      # traefik.http.routers.my_page.entrypoints: websecure
      # traefik.http.routers.my_page.tls: true
      # traefik.http.routers.my_page.tls.certresolver: default
$ curl -k https://example.localhost/         
root%
$ curl -k https://example.localhost/features/ 
features%
Traefik v3
version: '3.7'

services:

  traefik:
    image: traefik:v3.0.0-beta3
    command:
      - --log.level=debug
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.web.http.redirections.entrypoint.scheme=https
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls=true
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
 
  foo:
    image: httpd:2.4
    container_name: my_page
    volumes:
      - ./htdocs/:/usr/local/apache2/htdocs/
    labels:
      traefik.enable: true
      traefik.http.routers.my_page.rule: Host(`example.localhost`)
      # traefik.docker.network: traefik_web
      # traefik.http.routers.my_page.entrypoints: websecure
      # traefik.http.routers.my_page.tls: true
      # traefik.http.routers.my_page.tls.certresolver: default
$ curl -k https://example.localhost/         
root%
$ curl -k https://example.localhost/features/ 
features%

So it works, maybe the missing information related to your context has an influence here.

Note: you can copy-paste without any changes, and run the docker-compose, to help you to debug.

Hello @ldez,

First of all, thank you for your initial thoughts! I forgot to add my Traefik configuration. Below you'll find my docker-compose.yml for my Traefik Service and my static configuration. Maybe I miss something here.

docker-compose.yml:

version: '3.8'

services:
  traefik:
    restart: unless-stopped
    image: traefik:v3.0
    ports:
      - "80:80"
      - "443:443"
      - "127.0.0.1:8080:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./cert/:/cert/"
      - "./config/:/etc/traefik/:ro"
      - "./config/dynamic/:/etc/traefik/dynamic/:ro"
      - "/opt/traefik/plugins:/plugins-local"
    labels:
      - "traefik.enable=true" # set to true to expose the Monitoring & API
      # middleware redirect
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      # global redirect to https
      - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.docker.network=traefik_web"
      # traefik dashboard
      - "traefik.http.routers.api.rule=Host(`dashboard.example.com`)"
      - "traefik.http.routers.api.service=api@internal"
      - "traefik.http.routers.api.middlewares=auth"
      #  basic auth variable provided by Ansible
      - "traefik.http.middlewares.auth.basicauth.users=admin:{{ traefik_dashboard_basic_auth }}"
      - "traefik.http.routers.api.entrypoints=websecure"
      - "traefik.http.routers.api.tls=true"
      - "traefik.http.routers.api.tls.certresolver=default"

    networks:
      - "web"

networks:
  web:
    driver: bridge

My Traefik static configuration (traefik.toml):

[global]
  checkNewVersion = false
  sendAnonymousUsage = false

[experimental]
  [experimental.localPlugins]
    [experimental.localPlugins.geoblock]
      moduleName = "github.com/nscuro/traefik-plugin-geoblock"

[serversTransport]
  insecureSkipVerify = true

[entryPoints]
  [entryPoints.web]
    address = ":80"
  [entryPoints.websecure]
    address = ":443"

[log]
  level = "INFO"

[accessLog]
  format = "common"
  filePath = "/dev/null"

[api]
  dashboard = true

[ping]

[providers.docker]
  network = "traefik_web"
  exposedByDefault = false
  defaultRule = "Host(`{{ normalize .Name }}.docker.localhost`)"

[providers.file]
  directory = "/etc/traefik/dynamic"
  watch= true

[certificatesResolvers.default.acme]
  email = "contact@example.com"
  storage = "/cert/acme.json"
  [certificatesResolvers.default.acme.httpChallenge]
    entryPoint = "web"

[[tls.certificates]]
  certFile = "cert/snakeoil.pem"
  keyFile = "cert/snakeoil.key"
[[tls.certificates]]
  certFile = "cert/bitmask.me.origin.pem"
  keyFile = "cert/bitmask.me.origin.key"
[[tls.certificates]]
  certFile = "cert/grun.host.origin.pem"
  keyFile = "cert/grun.host.origin.key"

I hope this will help.

Have you tried my example?
You just have to fill ./htdocs

I've tried your example with Traefik 3.0 and it's working for me. What is the best way to find the error in my current setup since I am running multiple services behind my Traefik instance?

Thanks for your help!

stop all your services and add them again one by one.

You don’t seem to mistakenly use Path() instead of PathPrefix() in rule, do you?

Make sure you use different router names inside labels of each Docker service.

Set target service port via label, see simple Traefik example.

Hello @bluepuma77.

I I don't currently use either, so I would rule that out. I have checked all the labels in my services. They are all unique at the moment.

tls.certificates is not a valid section for the static configuration.
This section is ignored and should be moved to your dynamic configuration files.