blog.back_article_list

CrowdSec Docker Compose setup with reverse proxy logs

CrowdSec Docker Compose setup with reverse proxy logs

The most common broken CrowdSec-in-Docker setup I get asked about isn't broken at all, in the sense that every container is green. The engine runs, cscli alerts list fills up with scanner traffic and nothing ever gets blocked, because the Security Engine is a detector and nobody gave it an enforcer. The official Docker install docs say this in a big warning box and people still sail past it, so I'm putting it in the first paragraph instead of the conclusion: the crowdsecurity/crowdsec container decides, a bouncer somewhere else blocks.

This guide builds the full chain on Docker Compose, reverse proxy logs in, enforced bans out. Let's get to it!

The docker-compose.yml

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    environment:
      COLLECTIONS: "crowdsecurity/nginx-proxy-manager"
      TZ: "Etc/UTC"
    ports:
      - 127.0.0.1:8080:8080
    volumes:
      - crowdsec-config:/etc/crowdsec
      - crowdsec-data:/var/lib/crowdsec/data
      - ./acquis.d:/etc/crowdsec/acquis.d:ro
      - ./npm/data/logs:/var/log/npm:ro
    networks:
      - proxy

volumes:
  crowdsec-config:
  crowdsec-data:

networks:
  proxy:
    external: true

Walking through the choices. COLLECTIONS installs hub collections at container start; here it's the parser set for Nginx Proxy Manager logs. The two named volumes are non-negotiable: since CrowdSec 1.7.0 the container refuses to start if /var/lib/crowdsec/data isn't persisted, and you'll want /etc/crowdsec in a seperate volume anyway so credentials and local tweaks survive a recreate. The proxy network is whatever network your reverse proxy already sits on. The LAPI port is published to the host loopback only, for a bouncer we'll install later, and the log mount is read-only because the engine has no business writing to another container's files.

Getting reverse proxy logs into the container

CrowdSec doesn't sit in the traffic path. It reads logs, so the job is making your proxy's log directory visible inside the CrowdSec container and then describing it in an acquisition file. With Nginx Proxy Manager the logs live in the data/logs folder of your NPM directory, which the compose file above bind-mounts to /var/log/npm. The matching acquisition goes in ./acquis.d/npm.yaml:

filenames:
  - /var/log/npm/*.log
labels:
  type: nginx-proxy-manager

The type label is the routing key that tells the engine which parser handles these lines, and it has to match what the collection expects. This is the single most common misconfiguration I see in shared compose files: proxy logs labelled type: syslog, parsed line count stuck at zero, everyone confused. For a plain nginx container it's type: nginx with the crowdsecurity/nginx collection; for Traefik, enable Traefik's access log to a file, mount that file and use the Traefik collection's documented label. A recent r/selfhosted thread on this exact stack is worth skimming to see how other people wire the same pieces.

Docker socket acquisition vs mounted log files

The engine can also read container output straight from the Docker daemon with the docker acquisition source, no bind mounts needed. I don't use it on anything I care about, for one architectural reason and one practical one. Architecturally, handing the Docker socket to any container gives it root-equivalent control of the host, which is a lot of trust to extend to a log reader. Practically, mounted files behave identically on every stack, survive engine restarts without drama and can be tailed from the host when something looks off. Mounted files are boring. Boring is exactly what I want from the component that decides who gets banned.

Registering a bouncer against the containerized LAPI

Every bouncer authenticates to the engine's local API with a key. Generate one inside the container:

docker exec crowdsec cscli bouncers add traefik-bouncer

Copy the key it prints; it's shown once. The part that trips people up is the API URL. Inside a compose network the LAPI is http://crowdsec:8080, the service name, while 127.0.0.1 means "this container" to whoever dials it. A bouncer container pointed at http://127.0.0.1:8080 is talking to itself and will log connection errors forever. If you end up staring at a 403 from the LAPI instead, I've collected the causes in fixing the CrowdSec API access forbidden error.

You can also seed bouncer keys declaratively with the BOUNCER_KEY_<name> environment variable on the crowdsec service, documented in the Docker image README, which keeps the whole setup reproducible from the compose file alone. I switched to that approach after the second time I rebuilt a stack and couldn't remember where I'd stashed a key.

Host firewall bouncer vs proxy-level bouncer

Path one is the firewall bouncer installed on the host from the normal package repo, pointed at http://127.0.0.1:8080 through the published port. It blocks at the kernel, which protects every service on the machine, SSH included. The catch is Docker itself. Published ports bypass the INPUT chain entirely, so a stock firewall bouncer config happily bans IPs that keep reaching your containers anyway. Docker performs its own iptables surgery at daemon start, wiring published ports through PREROUTING and FORWARD with a set of DOCKER chains it rewrites at will; the first time I watched it silently reorder rules I'd placed by hand was the day I stopped assuming I understood my own firewall. The fix is enforcing in the DOCKER-USER chain, and my firewall bouncer deep dive covers it properly.

Path two blocks in the proxy itself, via the Traefik bouncer plugin or an nginx bouncer baked into the proxy image. HTTP-level blocking gets you proper 403 responses and per-request decisions, and it's immune to the Docker chain mess. I pick the proxy path when the only exposed surface is HTTP behind one reverse proxy, and the host firewall path (with the DOCKER-USER fix) when the box also exposes SSH or mail or anything else worth defending. On servers that matter, both at once; they don't conflict.

Persistence, upgrades and container recreation

Decisions live in a SQLite database under /var/lib/crowdsec/data, next to downloaded hub content. With the named volume in place you can run docker compose pull && docker compose up -d and active bans survive the recreate; without it, every recreate amnesties every attacker and re-registers the machine from scratch. Hub objects update inside the running container:

docker exec crowdsec cscli hub update
docker exec crowdsec cscli hub upgrade

The official example-docker-compose repo has maintained variants of all of this if your stack looks different from mine, including split LAPI and agent setups I'd skip until you genuinely need multi-node.

Health checks and daily cscli use

healthcheck:
  test: ["CMD", "cscli", "lapi", "status"]
  interval: 30s
  retries: 3

cscli lapi status exits non-zero when the local API is unreachable or its credentials are bad, which makes it a usable health probe. Day to day, everything from a bare-metal install works through exec: docker exec crowdsec cscli metrics to confirm lines are being read and parsed, docker exec crowdsec cscli decisions list for active bans. If metrics show zero lines read, suspect the mount path or the filename glob in your acquisition before you suspect the parsers. It's the mount. It's nearly always the mount.

If you'd rather run this without the container indirection, the bare-metal Debian install guide is the same stack on the host, and when the thing you're protecting is a web app the logical next step is the AppSec WAF setup on nginx rather than more IP banning. As for what to run it on, any Docker VPS with 2 GB of RAM carries this entire stack with room left over for the apps it's guarding.

Your idea deserves better hosting

24/7 support 30-day money-back guarantee Cancel anytime
Billing Cycle

VPS.S1

€5.18 packages.save  17 %
€4.32 Monthly
  • 2 vCPU AMD EPYC
  • 2 GB RAMPAMIĘĆ
  • 30 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie

VPS.S3

€12.97 packages.save  33 %
€8.65 Monthly
  • 4 vCPU AMD EPYC
  • 6 GB RAMPAMIĘĆ
  • 70 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie

EPYC VPS.P1

€7.78 packages.save  22 %
€6.05 Monthly
  • 2 vCPU AMD EPYC
  • 4 GB RAMPAMIĘĆ
  • 40 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

EPYC VPS.P2

€14.70 packages.save  24 %
€11.24 Monthly
  • 2 vCPU AMD EPYC
  • 8 GB RAMPAMIĘĆ
  • 80 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

EPYC VPS.P4

€25.96 packages.save  23 %
€19.90 Monthly
  • 4 vCPU AMD EPYC
  • 16 GB RAMPAMIĘĆ
  • 160 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

EPYC VPS.P5

€34.61 packages.save  25 %
€25.96 Monthly
  • 8 vCPU AMD EPYC
  • 16 GB RAMPAMIĘĆ
  • 180 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

EPYC VPS.P6

€51.92 packages.save  25 %
€38.94 Monthly
  • 8 vCPU AMD EPYC
  • 32 GB RAMPAMIĘĆ
  • 200 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

EPYC VPS.P7

€60.58 packages.save  29 %
€43.27 Monthly
  • 16 vCPU AMD EPYC
  • 32 GB RAMPAMIĘĆ
  • 240 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

Genoa VPS.G2

€21.63 packages.save  20 %
€17.30 Monthly
  • 2 vCPUAMD EPYC Genoa 4. generacji 9xx4 z 3,25 GHz lub podobny, na architekturze Zen 4. AMD EPYC G4
  • 4 GB DDR5PAMIĘĆ
  • 50 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

Genoa VPS.G4

€38.94 packages.save  22 %
€30.28 Monthly
  • 4 vCPUProcesor AMD EPYC z dedykowanymi rdzeniami vCPU, na serwerowym sprzęcie dla firm. AMD EPYC G4
  • 8 GB DDR5PAMIĘĆ
  • 100 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

Genoa VPS.G6

€77.89 packages.save  22 %
€60.58 Monthly
  • 8 vCPUProcesor AMD EPYC z dedykowanymi rdzeniami vCPU, na serwerowym sprzęcie dla firm. AMD EPYC G4
  • 16 GB DDR5PAMIĘĆ
  • 200 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

Genoa VPS.G7

€138.47 packages.save  22 %
€108.18 Monthly
  • 8 vCPUProcesor AMD EPYC z dedykowanymi rdzeniami vCPU, na serwerowym sprzęcie dla firm. AMD EPYC G4
  • 32 GB DDR5PAMIĘĆ
  • 250 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii. w cenie
  • Darmowa autokopiaZawiera jeden slot kopii zapasowej, który możesz ustawić na codzienne, cotygodniowe lub comiesięczne uruchamianie.

AMD Ryzen VPS.R1

€14.70 packages.save  18 %
€12.11 Monthly
  • 1 dedykowane CPU AMD Ryzen 9 7950X z 4,5 GHz lub podobny, na architekturze Zen 4. vCPU
  • 4 GB DDR5PAMIĘĆ
  • 50 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6 w cenie Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii.
  • Auto kopia zapasowa w cenie

AMD Ryzen VPS.R2

€25.96 packages.save  17 %
€21.63 Monthly
  • 2 dedykowane CPU AMD Ryzen 9 7950X z 4,5 GHz lub podobny, na architekturze Zen 4. vCPU
  • 8 GB DDR5PAMIĘĆ
  • 100 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6 w cenie Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii.
  • Auto kopia zapasowa w cenie

AMD Ryzen VPS.R4

€95.20 packages.save  18 %
€77.89 Monthly
  • 8 dedykowane CPU AMD Ryzen 9 7950X z 4,5 GHz lub podobny, na architekturze Zen 4. vCPU
  • 32 GB DDR5PAMIĘĆ
  • 400 GB NVMeDYSK
  • Nielimitowane łącze
  • IPv4 & IPv6 w cenie Obsługa IPv6 jest obecnie niedostępna we Francji, Finlandii ani w Holandii.
  • Auto kopia zapasowa w cenie

Answers to other questions you might have...

Can I run the firewall bouncer itself in a container?

You can, but it needs host networking plus NET_ADMIN to touch the host's netfilter, at which point the container boundary is mostly theater. I install it from the package on the host and keep the engine containerized. Fewer moving parts, and a bouncer that survives Docker daemon restarts.

GPU products are in high demand at the moment. Fill the form to get notified as soon as your preferred GPU server is back in stock.