Cloud Native 18 min read

How to Build and Use Private Plugins with Traefik Proxy 2.5

This guide explains how to create Docker images that bundle custom or demo plugins for Traefik Proxy 2.5, covering local, public, and private Git sources, Docker‑Compose development setups, and optional local DNS configuration with dnsmasq for testing complex routing rules.

Hacker Afternoon Tea
Hacker Afternoon Tea
Hacker Afternoon Tea
How to Build and Use Private Plugins with Traefik Proxy 2.5

Overview

Traefik Proxy is a modular router that can load middleware plugins from a /plugins-local directory starting with version 2.5, without requiring Traefik Pilot.

Build a Traefik image with the demo plugin

Create a temporary directory and add a Dockerfile.demo that clones the demo plugin repository and copies it into the image.

# Dockerfile.demo
FROM alpine:3
ARG PLUGIN_MODULE=github.com/traefik/plugindemo
ARG PLUGIN_GIT_REPO=https://github.com/traefik/plugindemo.git
ARG PLUGIN_GIT_BRANCH=master
RUN apk add --update git && \
    git clone ${PLUGIN_GIT_REPO} /plugins-local/src/${PLUGIN_MODULE} \
      --depth 1 --single-branch --branch ${PLUGIN_GIT_BRANCH}
FROM traefik:v2.5
COPY --from=0 /plugins-local /plugins-local

Build and tag the image:

docker build -f Dockerfile.demo --tag traefik-with-demo-plugin .

Run the container to verify plugin loading:

docker run --rm -it traefik-with-demo-plugin \
  --log.level=DEBUG \
  --experimental.localPlugins.demo.moduleName=github.com/traefik/plugindemo

The logs show the plugin being loaded. Changing moduleName to a non‑existent module causes an immediate error.

Build an image with a custom plugin

Fork the demo repository, edit the source, and modify the

.traefik.yml
import

line to match the new repository name. No compilation is required; Traefik interprets the Go source at runtime using Yaegi.

Build from a public Git repository

Export build‑time variables:

export DOCKER_IMAGE=traefik-with-my-plugin
export PLUGIN_MODULE=github.com/YOUR_NAME/YOUR_REPOSITORY
export PLUGIN_GIT_REPO=https://github.com/YOUR_NAME/YOUR_REPOSITORY.git
export PLUGIN_GIT_BRANCH=master

Create Dockerfile.public using the same multi‑stage pattern:

# Dockerfile.public
FROM alpine:3
ARG PLUGIN_MODULE
ARG PLUGIN_GIT_REPO
ARG PLUGIN_GIT_BRANCH
RUN apk update && \
    apk add git && \
    git clone ${PLUGIN_GIT_REPO} /plugins-local/src/${PLUGIN_MODULE} \
      --depth 1 --single-branch --branch ${PLUGIN_GIT_BRANCH}
FROM traefik:v2.5
COPY --from=0 /plugins-local /plugins-local

Build the image:

docker build -f Dockerfile.public \
  --tag ${DOCKER_IMAGE} \
  --build-arg PLUGIN_MODULE \
  --build-arg PLUGIN_GIT_REPO \
  --build-arg PLUGIN_GIT_BRANCH .

Build from a private Git repository

Docker BuildKit (Docker ≥ 18.09) and SSH forwarding are required.

export DOCKER_BUILDKIT=1
export DOCKER_IMAGE=traefik-with-my-plugin
export PLUGIN_MODULE=github.com/YOUR_NAME/YOUR_REPOSITORY
export [email protected]:YOUR_NAME/YOUR_REPOSITORY.git
export PLUGIN_GIT_BRANCH=master

Create Dockerfile.private with experimental syntax and an --mount=type=ssh step:

# syntax=docker/dockerfile:1.0.0-experimental
FROM alpine:3
ARG PLUGIN_MODULE
ARG PLUGIN_GIT_REPO
ARG PLUGIN_GIT_BRANCH
RUN apk add --update git openssh && \
    mkdir -m 700 /root/.ssh && \
    touch -m 600 /root/.ssh/known_hosts && \
    ssh-keyscan github.com > /root/.ssh/known_hosts
RUN --mount=type=ssh git clone \
    --depth 1 --single-branch --branch ${PLUGIN_GIT_BRANCH} \
    ${PLUGIN_GIT_REPO} /plugins-local/src/${PLUGIN_MODULE}
FROM traefik:v2.5
COPY --from=0 /plugins-local /plugins-local

Build the image with SSH forwarding:

docker build -f Dockerfile.private \
  --ssh default \
  --tag ${DOCKER_IMAGE} \
  --build-arg PLUGIN_MODULE \
  --build-arg PLUGIN_GIT_REPO \
  --build-arg PLUGIN_GIT_BRANCH .

Note: the --ssh flag is not supported in docker‑compose, so the image must be built with docker build before using docker‑compose up.

Docker‑compose as a plugin development environment

Create a simple Dockerfile that adds the plugin source to /plugins-local/src:

# Dockerfile
FROM traefik:v2.5
ARG PLUGIN_MODULE=github.com/traefik/plugindemo
ADD . /plugins-local/src/${PLUGIN_MODULE}

Create a .env file:

# .env
PLUGIN_NAME=demo
PLUGIN_MODULE=github.com/traefik/plugindemo
WHOAMI_TRAEFIK_HOST=whoami.example.com

Define docker-compose.yaml to run Traefik with the local plugin and a whoami test service:

# docker-compose.yaml
version: "3.3"
services:
  traefik-proxy:
    build:
      context: .
      args:
        PLUGIN_MODULE: ${PLUGIN_MODULE}
    restart: unless-stopped
    networks: [traefik-proxy]
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--api.dashboard=true"
      - "--api.insecure=true"
      - "--experimental.localPlugins.${PLUGIN_NAME}.moduleName=${PLUGIN_MODULE}"
    ports:
      - "80:80"
      - "443:443"
      - "127.0.0.1:9000:9000"
    volumes:
      - "traefik-proxy:/data"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`${WHOAMI_TRAEFIK_HOST}`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.middlewares=whoami-demo"
      - "traefik.http.middlewares.whoami-demo.plugin.${PLUGIN_NAME}.headers.DoesPluginWork=YES"
      - "traefik.http.routers.whoami.tls.certresolver=default"
  whoami:
    image: traefik/whoami
    networks: [traefik-proxy]
    restart: unless-stopped
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`${WHOAMI_TRAEFIK_HOST}`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.middlewares=whoami-demo"
      - "traefik.http.middlewares.whoami-demo.plugin.${PLUGIN_NAME}.headers.DoesPluginWork=YES"
      - "traefik.http.routers.whoami.tls.certresolver=default"
volumes:
  traefik-proxy:

Add a .dockerignore to exclude the Git history: .git Start the stack: docker-compose up Test with: curl -k https://whoami.example.com The response should contain the header DoesPluginWork: YES, confirming that the plugin is active.

Configure a local DNS service for development

Use dnsmasq to provide wildcard A records for many sub‑domains, avoiding repeated edits to /etc/hosts.

# /etc/dnsmasq.conf
interface=lo
listen-address=::1,127.0.0.1
bind-interfaces
cache-size=1000
server=1.1.1.1
server=1.0.0.1
address=/example.com/127.0.0.1

Enable the service (systemd example): sudo systemctl enable --now dnsmasq.service Point the resolver to the local server by setting nameserver 127.0.0.1 in /etc/resolv.conf. Make the file immutable to prevent overwrites: chattr +i /etc/resolv.conf Verify with dig, drill or nslookup; the answer should resolve the wildcard domain to the Docker host IP.

References

https://doc.traefik.io/traefik/middlewares/overview/

https://doc.traefik.io/traefik-pilot/

https://github.com/traefik/plugindemo

https://github.com/traefik/traefik/pull/8224

https://docs.docker.com/develop/develop-images/build_enhancements

https://github.com/docker/compose/issues/7025

https://traefik.io/blog/using-private-plugins-in-traefik-proxy-2-5/

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerPlugin DevelopmentBuildKitdnsmasqTraefikLocal DNS
Hacker Afternoon Tea
Written by

Hacker Afternoon Tea

You might find something interesting here ^_^

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.