Issues setting up crowdsec on docker with traefik

Hey everyone,

I’m new to crowdsec but been trying to implement it with traefik in docker in my lab environment, so far without success.

My issue is that I don’t really understand how to connect traefik to crowsec with the bounce plugin, I’ve looked though the official documentation and a lot of different github repos from different people, but whatever I try I can’t get it to work.

I’ve setup a simple test were I try and access my debug container from a browser, but when I access it I just get trough, even though I’ve set a manual ban rule for my PCs IP with:

docker exec crowdsec cscli decisions add --ip 192.168.1.10

Any help getting this to work would be greatly appreciated! :slight_smile:

Compose file for Traefik:

networks:
  docker-proxy:
    name: docker-proxy
    external: true
    
  crowdsec-network:
    name: crowdsec-network

volumes:
  traefik-logs:
    name: traefik-logs
  traefik-certs:
    name: traefik-certs
  traefik-config:
    name: traefik-config

services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    networks:
      - docker-proxy
      - crowdsec-network
    command:
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --api.dashboard=true
      - --providers.docker.network=docker-proxy # define default network to monitor for docker provider
       # Set up LetsEncrypt
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.letsencrypt.acme.email=${LETSENCRYPT_EMAIL}
      - --certificatesresolvers.letsencrypt.acme.caserver=https://acme-v02.api.letsencrypt.org/directory # prod (default)
      - --entrypoints.websecure.address=:443
      # Set up the TLS configuration for our websecure listener

      - --entrypoints.websecure.http.tls.certResolver=letsencrypt
      - --entrypoints.websecure.http.tls=true
      - --entrypoints.websecure.forwardedHeaders.trustedIPs=${TRUSTED_PROXIES}
      # Log configuration
      - --log.level=DEBUG
      - --log.format=json
      - --log.filePath=/var/log/traefik/traefik.log
      - --accesslog.format=json
      - --accesslog.filePath=/var/log/traefik/access.log
      # Crowdsec Plugin
      - --experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
      - --experimental.plugins.bouncer.version=v1.4.4

    labels:
      - traefik.http.middlewares.crowdsec-bar.plugin.bouncer.enabled=true
      - traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecAppsecEnabled=true
      - traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecAppsecHost=crowdsec:7422
      - traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecLapiKey=${CrowdSecAPIKey}
      - traefik.http.middlewares.crowdsec-bar.plugin.bouncer.crowdsecAppsecUnreachableBlock=true
    ports:
      - 443:443
    environment:
      - CF_DNS_API_TOKEN=${CF_API_TOKEN}
      - TZ=Europe/Stockholm # Change this to your timezone
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /etc/localtime:/etc/localtime:ro
      - traefik-config:/traefik
      - traefik-certs:/letsencrypt
      - traefik-logs:/var/log/traefik

Compose file for Crowdsec:

networks:
  docker-proxy:
    name: docker-proxy
    external: true
    
  crowdsec-network:
    name: crowdsec-network
    external: true

volumes:
  crowdsec-db:
    name: crowdsec-db
  crowdsec-config:
    name: crowdsec-config
  traefik-logs: # this will be the name of the volume from trarfic logs
    external: true # remove if traefik is running on same stack

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    environment:
      - TZ=Europe/Stockholm # Change this to your timezone
      - CUSTOM_HOSTNAME=crowdsec
      - COLLECTIONS=crowdsecurity/linux crowdsecurity/traefik crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules crowdsecurity/http-cve
      - BOUNCER_KEY_PLUGIN=${BOUNCER_KEY_PLUGIN}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - crowdsec-db:/var/lib/crowdsec/data/
      - crowdsec-config:/etc/crowdsec/
      - traefik-logs:/var/log/traefik:ro
    networks:
      - crowdsec-network

Compose file for debug container:

services:
  whoami:
    image: brndnmtthws/nginx-echo-headers
    container_name: debug
    networks:
      - docker-proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.external.rule=Host(`debug.domain.com`)"
      - "traefik.http.routers.external.entrypoints=websecure"
      - "traefik.http.routers.debug.tls.certresolver=letsencrypt"
      - "traefik.http.services.external.loadbalancer.server.port=8080"

networks:
  docker-proxy:
    name: docker-proxy
    external: true

acquis.yaml

filenames:
  - /var/log/traefik/*
labels:
  type: traefik

---
appsec_config: crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 0.0.0.0:7422
source: appsec

Logs from the crowdsec container:

time="2025-07-31T23:16:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:16:58 CEST] \"GET /v1/heartbeat HTTP/1.1 200 43.73953ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:16:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:16:58 CEST] \"GET /v1/allowlists?with_content=true HTTP/1.1 200 53.846708ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:17:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:17:58 CEST] \"GET /v1/heartbeat HTTP/1.1 200 44.340742ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:17:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:17:58 CEST] \"GET /v1/allowlists?with_content=true HTTP/1.1 200 53.676975ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:18:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:18:58 CEST] \"GET /v1/heartbeat HTTP/1.1 200 42.056434ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:18:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:18:58 CEST] \"GET /v1/allowlists?with_content=true HTTP/1.1 200 53.776109ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:19:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:19:58 CEST] \"GET /v1/heartbeat HTTP/1.1 200 35.838689ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:19:58+02:00" level=info msg="127.0.0.1 - [Thu, 31 Jul 2025 23:19:58 CEST] \"GET /v1/allowlists?with_content=true HTTP/1.1 200 33.630978ms \"crowdsec/v1.6.11-d64ee2ae-docker\" \""

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not string (1:1)\n | Split(evt.Unmarshaled.traefik.ClientAddr, ':')[0]\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : reflect: call of reflect.Value.Type on zero Value (1:1)\n | int(evt.Unmarshaled.traefik.Duration)\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not string (1:1)\n | Split(evt.Unmarshaled.traefik.RequestProtocol, '/')[1]\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : reflect: call of reflect.Value.Type on zero Value (1:1)\n | int(evt.Unmarshaled.traefik.DownstreamStatus)\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not string (1:1)\n | Split(evt.Unmarshaled.traefik.ClientAddr, ':')[0]\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : reflect: call of reflect.Value.Type on zero Value (1:1)\n | int(evt.Unmarshaled.traefik.Duration)\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : interface conversion: interface {} is nil, not string (1:1)\n | Split(evt.Unmarshaled.traefik.RequestProtocol, '/')[1]\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

time="2025-07-31T23:20:16+02:00" level=warning msg="failed to run RunTimeValue : reflect: call of reflect.Value.Type on zero Value (1:1)\n | int(evt.Unmarshaled.traefik.DownstreamStatus)\n | ^" id=damp-shape name=child-crowdsecurity/traefik-logs stage=s01-parse

So you do define the crowdsec-bar plugin but you dont assign it to an entrypoint or at least a container

I am not well versed in traefik but for the coolify article I wrote I had

- '--providers.file.directory=/traefik/dynamic/'
- '--providers.file.watch=true'
- '--entryPoints.http.http.middlewares=globalcrowdsec@file'
- '--entryPoints.https.http.middlewares=globalcrowdsec@file'

but for your setup its:

- '--entryPoints.websecure.http.middlewares=globalcrowdsec@file'

Which then I had a crowdsec.yml within the file provider setup with the contents:

http:
  middlewares:
    globalcrowdsec:
      plugin:
        crowdsec:
          enabled: true
          crowdsecLapiKey: CHANGEME
          crowdsecLapiScheme: http
          crowdsecLapiHost: 'crowdsec:8080'
          crowdsecMode: stream
          #logLevel: DEBUG

you can most likely do the same thing with traefik labels, but I couldnt figure it out myself and coolify allows easy creation of dynamic files so I just used that.

Thanks!

Do I have to assign this via the cowdsec.ymal? It’s not possible to set this via command line in the compose file?

Also am I correct in setting labels for crowdsec-bar on the traefik container? And what (if any) labels do I need to set for my debug container to make it use crowdsec?