"middleware ... does not exist" with a local plugin?

I'm trying to add middleware that adds a header and comes before ForwardAuth middleware. Using a simplified example in Docker Compose, here is what I have so far:

services:
  traefik:
    image: traefik:latest
    ports:
      - "80:80"
      - "8080:8080"
    command:
      - --api.insecure=true
      - --providers.docker
      - --entrypoints.web.address=:80
      - --experimental.localplugins.my-plugin.modulename=github.com/khpeek/my-plugin
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./my-plugin:/plugins-local/src/github.com/khpeek/my-plugin

  whoami:
    image: traefik/whoami:latest
    labels:
      - traefik.enable=true
      - traefik.http.routers.whoami.rule=Host(`whoami.localhost`)
      - traefik.http.routers.whoami.entrypoints=web
      - traefik.http.middlewares.whoami-auth.forwardauth.address=http://host.docker.internal:4181
      - traefik.http.routers.whoami.middlewares=my-plugin,whoami-auth

where my directory structure is

.
├── docker-compose.yml
└── my-plugin
    ├── .traefik.yml
    ├── go.mod
    └── myplugin.go

and myplugin.go reads

package myplugin

import (
	"context"
	"log"
	"net/http"
)

type Config struct{}

func CreateConfig() *Config {
	return &Config{}
}

type MyPlugin struct {
	next http.Handler
	name string
}

func New(_ context.Context, next http.Handler, _ *Config, name string) (http.Handler, error) {
	return &MyPlugin{
		next: next,
		name: name,
	}, nil
}

func (m *MyPlugin) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println("MyPlugin serving an HTTP request")
	r.Header.Add("foo", "bar")
	m.next.ServeHTTP(w, r)
}

with a go.mod file,

module github.com/khpeek/my-plugin

go 1.23.0

and .traefik.yml,

# The name of your plugin as displayed in the Plugins Catalog web UI.
displayName: My Plugin

type: provider

# The import path of your plugin.
import: github.com/khpeek/my-plugin

# A brief description of what your plugin is doing.
summary: Adds a header

# Medias associated to the plugin (optional)
iconPath: foo/icon.png
bannerPath: foo/banner.png

# Configuration data for your plugin.
# This is mandatory,
# and Plugins Catalog will try to execute the plugin with the data you provide as part of its startup validity tests.
testData:
  Headers:
    Foo: Bar

The problem is that when I run it using docker compose up, I get

[+] Running 2/0
 ✔ Container traefik-docker-compose-example-traefik-1  Created                                                                                                 0.0s 
 ✔ Container traefik-docker-compose-example-whoami-1   Created                                                                                                 0.0s 
Attaching to traefik-1, whoami-1
whoami-1   | 2024/09/05 15:31:53 Starting up on port 80
traefik-1  | 2024-09-05T15:31:53Z ERR error="middleware \"my-plugin@docker\" does not exist" entryPointName=web routerName=whoami@docker

It seems from similar topics that I have to add to the labels of whoami something like

- traefik.http.middlewares.my-plugin. ...

but since my-plugin doesn't have any configuration options (unlike ForwardAuth), I'm not sure what to add there. Any idea how to get this to work?

Maybe try

- traefik.http.middlewares.my-plugin=true

That leads to a different error,

invalid middleware \"my-plugin@docker\" configuration: invalid middleware type or middleware does not exist

It seems from traefik/pkg/config/dynamic/plugins.go at c2cb4fac106fc74efe30288348b6b1679d5dd7d0 · traefik/traefik · GitHub that a map[string]any is expected, but as I mentioned my plugin has no configuration so not sure what to put there.

Aren’t you able to supply a fake key=value?

It’s probably just passed into the plugin, but you don’t need to use it.

How can your plugin not require params :laughing: