Repo: https://github.com/hhftechnology/crowdsec_manager
Suggested labels: enhancement, good first issue, area: independent-image
Suggested area: cmd/server/main.go (prereq check) + internal/config/config.go (new env var)
Summary
The independent image's startup prerequisite check (checkPrerequisites in cmd/server/main.go:60-63 + :191-214) fatally aborts the manager when no Docker container with the name set in CROWDSEC_CONTAINER_NAME (default crowdsec) exists. The check is non-configurable, which forces every operator deploying crowdsec-manager:independent to also deploy a sibling crowdsec Docker container — even when their real CrowdSec deployment is a non-containerized one (apt-installed systemd unit, k8s DaemonSet, remote LAPI instance, etc.).
Please add an opt-in environment variable (e.g. SKIP_CROWDSEC_PREREQ=true) that, when set, downgrades the prereq check from Fatal to a Warn, so operators using the independent image without a co-located crowdsec container can still run the manager.
Our setup (why this hurts us)
We run SporeHarbor (a spore-based deployment platform for ~12 production hosts across WordPress, FreePBX, Zammad, ERPNext, etc.). On every host we install CrowdSec via the Debian/Ubuntu apt package, which gives us a systemd crowdsec.service running both the log-processing agent AND the Local API (LAPI) on 127.0.0.1:8080. This is intentional — the systemd-managed crowdsec is the single source of truth for local log processing, bouncer registration, and metrics on 127.0.0.1:6060.
We opted into the crowdsec-manager:independent image (love the no-Traefik / no-Pangolin / no-Gerbil footprint — exactly what we want) to get a web UI for managing decisions, allowlists, scenarios, and hub collections. The spec we wrote is at SporeHarbor/docs/specs/2026-07-08_crowdsec-manager-opt-in-docker.md.
Our exact deploy (one host, one systemd unit, no sibling container):
# /etc/systemd/system/crowdsec-manager.service
[Service]
ExecStart=/usr/bin/docker run --rm \
--name crowdsec-manager \
--network host \
-e PORT=8081 \
-e ENVIRONMENT=production \
-e INCLUDE_CROWDSEC=false \
-e CONFIG_DIR=/app/config \
-e DATABASE_PATH=/app/data/settings.db \
-v /var/lib/crowdsec-manager/config:/app/config \
-v /var/lib/crowdsec-manager/data:/app/data \
-v /var/lib/crowdsec-manager/logs:/app/logs \
-v /var/lib/crowdsec-manager/backups:/app/backups \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
hhftechnology/crowdsec-manager:independent
Restart=always
On startup, the manager's own log shows:
{"level":"info","time":"...","msg":"Database initialized","path":"/app/data/settings.db"}
{"level":"info","time":"...","msg":"History database initialized","path":"./data/history.db"}
{"level":"info","time":"...","msg":"Multi-host Docker client initialized with single default host"}
{"level":"info","time":"...","msg":"Checking prerequisites..."}
{"level":"info","time":"...","msg":" Docker daemon is running"}
{"level":"fatal","time":"...","msg":"Prerequisite check failed","error":"required container not found: crowdsec"}
The systemd unit then enters a crashloop. The crashloop is silent (no stdout/stderr in journalctl beyond the unit log line), which is itself a UX issue but separate from the prereq problem.
Why we'd benefit from a skip flag
We (and, we suspect, many other operators using independent against a non-containerized CrowdSec) currently have three options, all bad:
-
Run a no-op sibling container named crowdsec (e.g. docker run --name crowdsec busybox sleep infinity). This is what we ended up doing as a workaround. Cost: ~5 MB RAM for a container whose only purpose is to satisfy the prereq check. Risk: if the container dies, the manager crashloops again. Risk: it pollutes docker ps output with a container that has no real purpose, confusing future operators.
-
Run a real crowdsecurity/crowdsec container as a sidecar, in addition to the systemd crowdsec. Cost: duplicate log processing, duplicate metric endpoints, two CrowdSec processes competing for the same /var/log files. Risk: double-reporting to the Central API, double-bouncer-registration attempts, configuration drift between the two.
-
Replace our systemd crowdsec with the container entirely. Cost: we lose the apt-managed upgrade path, lose the integration with our existing crowdsec-firewall-bouncer (apt), and have to maintain a Dockerfile / compose for a service that the distro already packages. Risk: when we add a new spore with a different log source, we have to remember to mount it into the container.
A SKIP_CROWDSEC_PREREQ=true env var would let us go with option 0: zero changes to our existing crowdsec deployment, no sidecar, no duplicate processes. The manager would still validate Docker daemon reachability (which is the only check that actually matters for its own operation — it needs the socket to query CrowdSec via ExecCommand / WriteFileToContainer / GetContainerLogs), and it would gracefully warn-and-continue when the optional crowdsec container isn't there.
Source locations in this repo
For reference, the relevant source as of the :independent image tag (org.opencontainers.image.revision = be96391de9a7d685bdb96c426cf801fcb55b0ba0):
// cmd/server/main.go:54-63
multiHost, err := docker.NewMultiHostClient(cfg.DockerHosts)
if err != nil {
logger.Fatal("Failed to initialize Docker client", "error", err)
}
defer multiHost.Close()
dockerClient := multiHost.DefaultClient()
if err := checkPrerequisites(dockerClient, cfg); err != nil {
logger.Fatal("Prerequisite check failed", "error", err)
}
// cmd/server/main.go:191-214
func checkPrerequisites(client *docker.Client, cfg *config.Config) error {
logger.Info("Checking prerequisites...")
if err := client.Ping(); err != nil {
return fmt.Errorf("docker daemon not running: %w", err)
}
logger.Info(" Docker daemon is running")
containers := []string{cfg.CrowdsecContainerName}
for _, name := range containers {
exists, err := client.ContainerExists(name)
if err != nil {
return fmt.Errorf("failed to check container %s: %w", name, err)
}
if exists {
logger.Info(" Container exists", "name", name)
} else {
return fmt.Errorf("required container not found: %s", name)
}
}
return nil
}
A minimal patch (illustrative, not a PR):
// internal/config/config.go (add to Config struct + Load())
SkipCrowdsecPrereq bool // env: SKIP_CROWDSEC_PREREQ, default false
// cmd/server/main.go:checkPrerequisites
for _, name := range containers {
exists, err := client.ContainerExists(name)
if err != nil {
if cfg.SkipCrowdsecPrereq {
logger.Warn("Failed to check container (SKIP_CROWDSEC_PREREQ=true)", "name", name, "error", err)
continue
}
return fmt.Errorf("failed to check container %s: %w", name, err)
}
if exists {
logger.Info(" Container exists", "name", name)
} else {
if cfg.SkipCrowdsecPrereq {
logger.Warn(" Container not found (SKIP_CROWDSEC_PREREQ=true)", "name", name)
continue
}
return fmt.Errorf("required container not found: %s", name)
}
}
(Keeping the Ping() check fatal — that's the one the manager truly depends on at runtime.)
Suggested env var naming
We'd be happy with any of these (in rough order of preference):
SKIP_CROWDSEC_PREREQ=true — explicit, scoped to the one check
REQUIRE_CROWDSEC_CONTAINER=false — explicit boolean inversion, follows INCLUDE_CROWDSEC style
CROWDSEC_CONTAINER_REQUIRED=false — names the specific variable being affected
Option 1 is our top pick because it composes well with future prereq relaxations (e.g. if you ever add a traefik or pangolin container check, the same flag could be reused).
Related discussion
There's already an unanswered question on this exact topic: #116 — the asker is in the same boat (apt-installed crowdsec, wants to use the manager, gets the same required container not found: crowdsec error). Upvoting + linking this issue from there would help consolidate the signal.
Our workaround (for context, not asking upstream to adopt it)
For now, on each affected host we deploy a sidecar container:
docker run -d --name crowdsec --restart=always \
docker.io/library/busybox:latest \
/bin/sh -c "while true; do sleep 3600; done"
The manager's CROWDSEC_METRICS_URL still points at our systemd crowdsec's metrics endpoint (http://127.0.0.1:6060/metrics), so the dashboard works against real data — the busybox container is purely a presence-satisfier for the prereq check.
This works but feels wasteful, and we worry about it being cargo-culted by future operators who don't understand why there's a busybox container named crowdsec on every host. A native opt-out would let us delete the workaround and ship a cleaner deploy.
Reproduction steps (operator-facing)
# On any host with Docker but no `crowdsec` container running
docker run --rm --name crowdsec-manager-test \
--network host \
-e PORT=8080 \
-e ENVIRONMENT=production \
-e INCLUDE_CROWDSEC=false \
-e CONFIG_DIR=/app/config \
-e DATABASE_PATH=/app/data/settings.db \
hhftechnology/crowdsec-manager:independent
# Expected (with proposed SKIP_CROWDSEC_PREREQ=true): web UI starts on :8080
# Actual (current): fatal "required container not found: crowdsec", container exits
# Workaround
docker run -d --name crowdsec docker.io/library/busybox:latest sleep infinity
docker run --rm --name crowdsec-manager-test \
--network host \
-e PORT=8080 \
-e ENVIRONMENT=production \
-e INCLUDE_CROWDSEC=false \
-e CONFIG_DIR=/app/config \
-e DATABASE_PATH=/app/data/settings.db \
hhftechnology/crowdsec-manager:independent
# Now starts cleanly
Repo: https://github.com/hhftechnology/crowdsec_manager
Suggested labels:
enhancement,good first issue,area: independent-imageSuggested area:
cmd/server/main.go(prereq check) +internal/config/config.go(new env var)Summary
The
independentimage's startup prerequisite check (checkPrerequisitesincmd/server/main.go:60-63+:191-214) fatally aborts the manager when no Docker container with the name set inCROWDSEC_CONTAINER_NAME(defaultcrowdsec) exists. The check is non-configurable, which forces every operator deployingcrowdsec-manager:independentto also deploy a siblingcrowdsecDocker container — even when their real CrowdSec deployment is a non-containerized one (apt-installed systemd unit, k8s DaemonSet, remote LAPI instance, etc.).Please add an opt-in environment variable (e.g.
SKIP_CROWDSEC_PREREQ=true) that, when set, downgrades the prereq check fromFatalto aWarn, so operators using theindependentimage without a co-locatedcrowdseccontainer can still run the manager.Our setup (why this hurts us)
We run SporeHarbor (a spore-based deployment platform for ~12 production hosts across WordPress, FreePBX, Zammad, ERPNext, etc.). On every host we install CrowdSec via the Debian/Ubuntu apt package, which gives us a systemd
crowdsec.servicerunning both the log-processing agent AND the Local API (LAPI) on127.0.0.1:8080. This is intentional — the systemd-managed crowdsec is the single source of truth for local log processing, bouncer registration, and metrics on127.0.0.1:6060.We opted into the
crowdsec-manager:independentimage (love the no-Traefik / no-Pangolin / no-Gerbil footprint — exactly what we want) to get a web UI for managing decisions, allowlists, scenarios, and hub collections. The spec we wrote is atSporeHarbor/docs/specs/2026-07-08_crowdsec-manager-opt-in-docker.md.Our exact deploy (one host, one systemd unit, no sibling container):
On startup, the manager's own log shows:
The systemd unit then enters a crashloop. The crashloop is silent (no stdout/stderr in journalctl beyond the unit log line), which is itself a UX issue but separate from the prereq problem.
Why we'd benefit from a skip flag
We (and, we suspect, many other operators using
independentagainst a non-containerized CrowdSec) currently have three options, all bad:Run a no-op sibling container named
crowdsec(e.g.docker run --name crowdsec busybox sleep infinity). This is what we ended up doing as a workaround. Cost: ~5 MB RAM for a container whose only purpose is to satisfy the prereq check. Risk: if the container dies, the manager crashloops again. Risk: it pollutesdocker psoutput with a container that has no real purpose, confusing future operators.Run a real
crowdsecurity/crowdseccontainer as a sidecar, in addition to the systemd crowdsec. Cost: duplicate log processing, duplicate metric endpoints, two CrowdSec processes competing for the same/var/logfiles. Risk: double-reporting to the Central API, double-bouncer-registration attempts, configuration drift between the two.Replace our systemd crowdsec with the container entirely. Cost: we lose the apt-managed upgrade path, lose the integration with our existing
crowdsec-firewall-bouncer(apt), and have to maintain a Dockerfile / compose for a service that the distro already packages. Risk: when we add a new spore with a different log source, we have to remember to mount it into the container.A
SKIP_CROWDSEC_PREREQ=trueenv var would let us go with option 0: zero changes to our existing crowdsec deployment, no sidecar, no duplicate processes. The manager would still validate Docker daemon reachability (which is the only check that actually matters for its own operation — it needs the socket to query CrowdSec viaExecCommand/WriteFileToContainer/GetContainerLogs), and it would gracefully warn-and-continue when the optionalcrowdseccontainer isn't there.Source locations in this repo
For reference, the relevant source as of the
:independentimage tag (org.opencontainers.image.revision = be96391de9a7d685bdb96c426cf801fcb55b0ba0):A minimal patch (illustrative, not a PR):
(Keeping the
Ping()check fatal — that's the one the manager truly depends on at runtime.)Suggested env var naming
We'd be happy with any of these (in rough order of preference):
SKIP_CROWDSEC_PREREQ=true— explicit, scoped to the one checkREQUIRE_CROWDSEC_CONTAINER=false— explicit boolean inversion, followsINCLUDE_CROWDSECstyleCROWDSEC_CONTAINER_REQUIRED=false— names the specific variable being affectedOption 1 is our top pick because it composes well with future prereq relaxations (e.g. if you ever add a traefik or pangolin container check, the same flag could be reused).
Related discussion
There's already an unanswered question on this exact topic: #116 — the asker is in the same boat (apt-installed crowdsec, wants to use the manager, gets the same
required container not found: crowdsecerror). Upvoting + linking this issue from there would help consolidate the signal.Our workaround (for context, not asking upstream to adopt it)
For now, on each affected host we deploy a sidecar container:
docker run -d --name crowdsec --restart=always \ docker.io/library/busybox:latest \ /bin/sh -c "while true; do sleep 3600; done"The manager's
CROWDSEC_METRICS_URLstill points at our systemd crowdsec's metrics endpoint (http://127.0.0.1:6060/metrics), so the dashboard works against real data — the busybox container is purely a presence-satisfier for the prereq check.This works but feels wasteful, and we worry about it being cargo-culted by future operators who don't understand why there's a busybox container named
crowdsecon every host. A native opt-out would let us delete the workaround and ship a cleaner deploy.Reproduction steps (operator-facing)