I secured the web stack with CrowdSec, an open-source, collaborative Intrusion Prevention System (IPS)1. The integration with the existing Docker stack is tight: CrowdSec monitors the proxy logs in real time and blocks malicious traffic before it reaches the applications.
Here’s an overview of my updated architecture and how the pieces fit together.
My Website Architecture
The site is built with Hugo, a fast static site generator. For performance and security, docker-compose splits the stack into distinct layers:
- The Content Builder: A Hugo container (
hugomods/hugo) that watches my source directory for changes. Whenever a post (like this one!) is written, it automatically builds the static HTML files to a./publicdirectory. - The Web Server: A lightweight Nginx Alpine container that solely serves those statically built files. Fast, simple, and isolated.
- The Gatekeeper: I use Nginx Proxy Manager (specifically the
zoeyvid/npmplusimage) to handle SSL termination, manage domains, and act as my reverse proxy routing external traffic to my internal Nginx web server.
While Nginx Proxy Manager does a great job at routing traffic, I wanted a layer that would block brute-force attacks, port scanners, and malicious bots. CrowdSec fills that role.
Implementing CrowdSec
CrowdSec acts as my active defense layer. Instead of relying on static IP blocklists, CrowdSec analyzes my logs in real-time and shares threat intelligence with a massive global network. If an IP attacks another CrowdSec user anywhere in the world, my server knows to block it preemptively.
The Integration
The CrowdSec integration into the docker-compose file is clean. The implementation:
- Log Parsing: Nginx Proxy Manager handles all inbound requests and writes access logs. I mounted these logs (
./npm-data/logs) into the CrowdSec container as read-only. I set theLOGROTATE=trueenvironment variable in NPM to ensure log files remain manageable. - The NPM Collection: I configured CrowdSec to use the
ZoeyVid/npmpluscollection. A collection in CrowdSec is a bundle of parsers and scenarios tailored for a specific service. This allows CrowdSec to instantly understand the Nginx Proxy Manager log format and identify common attack signatures. - The Bouncer: The
zoeyvid/npmplusimage ships with a CrowdSec bouncer built in. When the CrowdSec container detects malicious activity in the logs, it flags the IP, and the NPMplus bouncer queries CrowdSec and drops that traffic at the proxy level.
The Compose Configuration
My CrowdSec service definition in docker-compose.yaml looks like this:
crowdsec:
image: crowdsecurity/crowdsec:latest
container_name: crowdsec
restart: unless-stopped
environment:
- TZ=America/Chicago
- COLLECTIONS=ZoeyVid/npmplus
volumes:
- ./crowdsec/data:/var/lib/crowdsec/data
- ./crowdsec/config:/etc/crowdsec
- ./npm-data/logs:/var/log/npm:ro
Looking Forward
Decoupling site generation (Hugo) from static serving (Nginx) and layering Nginx Proxy Manager with CrowdSec on top produced a resilient, performant, secure stack. Malicious bots drop at the edge, so server resources go to legitimate users.
I will report on how it performs, and I plan to contribute back to the CrowdSec network.
-
CrowdSec. (2026). CrowdSec: Open-source, self-hosted, real-time network protection [Software]. https://github.com/crowdsecurity/crowdsec ↩︎