CrowdSec agent that seeds hub data into its volume, then drops to a non-root user. https://git.openso.de/openso/crowdsec-agent
  • Shell 45.1%
  • Python 42.8%
  • Dockerfile 7.7%
  • C 4.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Marcel Henneke 60fd0ecb87
All checks were successful
ci / compose (push) Successful in 13s
ci / shellcheck (push) Successful in 2s
ci / conventions (push) Successful in 2s
ci / image (push) Successful in 38s
docs: use short volume syntax in README compose example (#3)
2026-11-01 14:37:42 +01:00
.forgejo Pin yq rebuild with patched x/net, x/text, x/sys; document image usage (#1) 2026-11-01 13:50:33 +01:00
.githooks Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
.openso/scripts Remove the copied Python cache from the CI scripts. 2026-11-01 13:21:33 +01:00
.dockerignore Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
AGENTS.md Pin CrowdSec base by tag and verify yq source by SHA256 (#2) 2026-11-01 14:21:59 +01:00
crowdsec-agent-prepare.sh Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
crowdsec-agent-run.sh Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
Dockerfile Pin CrowdSec base by tag and verify yq source by SHA256 (#2) 2026-11-01 14:21:59 +01:00
entrypoint.crowdsec-agent.sh Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
LICENSE Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00
README.md docs: use short volume syntax in README compose example (#3) 2026-11-01 14:37:42 +01:00
su-exec.c Seed the CrowdSec agent image. 2026-11-01 13:21:28 +01:00

crowdsec-agent

CrowdSec 1.8.2 agent image. It copies the upstream GeoIP and hub data into the data volume, then runs the agent as a non-root user. The customer stack lives in docker-templates at templates/crowdsec_agent. This repository is the image only.

The upstream image only symlinks /staging/var/lib/crowdsec/data. Those files are root and mode 0600, and so is the staging config under /staging/etc/crowdsec. This image copies a missing data entry, or a symlink, into the writable volume and leaves a regular file that cscli hub upgrade already wrote. On an empty config mount it also copies the staging config.

Image

git.openso.de/openso/crowdsec-agent:main

docker-templates uses git.openso.de/openso/crowdsec-agent with no tag. Docker resolves that reference to :latest. :latest tracks the same build as :main.

Tag Contents
main, latest Current Dockerfile on main. Use this reference with no tag.
v* The commit that tag points at. A release tag is optional.

One image. A newer CrowdSec release is a new FROM tag in this Dockerfile.

Leave the container user unset. The entrypoint starts as root, then drops to CROWDSEC_AGENT_UID:CROWDSEC_AGENT_GID (default 1000:1000). A hardened service keeps read_only: true, drops every capability, and adds SETUID, SETGID, CHOWN, and DAC_OVERRIDE for that seed. docker-templates leaves user unset on crowdsec_agent for this reason. Setting user to 1000:1000 skips the seed and the container exits.

The agent does not publish a port. DISABLE_LOCAL_API=true keeps the local API off. Alerts go to LOCAL_API_URL.

Use

Mount a writable volume at /var/lib/crowdsec/data and a writable config directory at /etc/crowdsec. The upstream start exits when /var/lib/crowdsec/data is not a mount. Put acquisition files in the config directory (acquis.yaml or acquis.d/). Log files the agent reads are separate read-only mounts.

LOCAL_API_URL is required. TZ sets the container clock.

services:
  crowdsec_agent:
    image: git.openso.de/openso/crowdsec-agent
    read_only: true
    cap_drop:
      - ALL
    cap_add:
      - SETUID
      - SETGID
      - CHOWN
      - DAC_OVERRIDE
    security_opt:
      - no-new-privileges:true
    init: true
    tmpfs:
      - /run:rw,noexec,nosuid,nodev,size=64m
      - /tmp:rw,noexec,nosuid,nodev,size=128m
      - /var/tmp:rw,noexec,nosuid,nodev,size=128m
    volumes:
      - crowdsec_agent_data:/var/lib/crowdsec/data:rw
      - ./appdata/crowdsec_agent/config:/etc/crowdsec:rw
      - ./appdata/logs:/var/log/appdata:ro
    environment:
      TZ: Europe/Berlin
      DISABLE_LOCAL_API: "true"
      LOCAL_API_URL: http://192.0.2.1:8080
      COLLECTIONS: crowdsecurity/traefik
      CROWDSEC_AGENT_MACHINE: traefik-crowdsec_agent
    healthcheck:
      test: ["CMD-SHELL", "cscli lapi status > /dev/null 2>&1"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 2m

volumes:
  crowdsec_agent_data:

Create ./appdata/crowdsec_agent/config before the first start. The LAPI must already be listening at LOCAL_API_URL. After the first successful cscli lapi register, validate that machine on the LAPI. The login and password are written to /etc/crowdsec/local_api_credentials.yaml on the config mount.

Use the template in docker-templates when this container sits in an app stack. That template also mounts the log directory the acquisition file names.

Environment

The entrypoint reads these. Unset names use the default. LOCAL_API_URL has no default.

Variable Default Purpose
LOCAL_API_URL (required) Remote LAPI. Only http://host[:port][/] or https://host[:port][/]. Empty, CHANGE_ME, a user, a path, a query, or a fragment stops startup.
CROWDSEC_AGENT_UID 1000 Numeric uid after the seed.
CROWDSEC_AGENT_GID 1000 Numeric gid after the seed.
CROWDSEC_AGENT_MACHINE container hostname Machine name for cscli lapi register --machine. Set it before the first registration.
TZ (unset) Container time zone, for example Europe/Berlin.

After the uid drop, /docker_start.sh from crowdsecurity/crowdsec 1.8.2 still applies its own variables. This image expects the agent-only set:

Variable Default Purpose
DISABLE_LOCAL_API upstream default starts a local API true is agent-only. The local API stays off and no port is published.
COLLECTIONS (unset) Space-separated hub collections installed on start. The same collections belong on the remote LAPI.
DISABLE_AGENT (unset) true skips the agent process. Leave it unset for this image.

Other upstream variables (PARSERS, SCENARIOS, DSN, LEVEL_INFO, and the rest of that start script) still pass through. They are unchanged from crowdsecurity/crowdsec 1.8.2.

Startup

On start the entrypoint:

  1. Requires uid 0. CROWDSEC_AGENT_UID and CROWDSEC_AGENT_GID must be numeric.
  2. Copies each file or directory from /staging/var/lib/crowdsec/data into /var/lib/crowdsec/data when that name is missing or still a symlink. A regular file or directory already there stays, so a later cscli hub upgrade keeps what it wrote.
  3. Copies /staging/etc/crowdsec into /etc/crowdsec when that mount has neither config.yaml nor local_api_credentials.yaml.
  4. Gives both trees to CROWDSEC_AGENT_UID:CROWDSEC_AGENT_GID.
  5. Drops to that uid and requires a valid LOCAL_API_URL.
  6. Replaces the upstream /does/not/exist acquisition placeholder with /dev/null when acquis.yaml still contains it. Real sources stay in acquis.d/.
  7. Restores api.client.credentials_path to /etc/crowdsec/local_api_credentials.yaml when a saved config.yaml dropped it.
  8. When config.yaml exists and local_api_credentials.yaml has no login: line, runs cscli lapi register -u "${LOCAL_API_URL}" --machine "${CROWDSEC_AGENT_MACHINE}". A file that already has login: is left in place. Step 3 copies the staging config.yaml onto an empty mount, so this registration runs on that same start.
  9. Execs /docker_start.sh. That script calls the same prepare step again after it writes config and before crowdsec.

An empty mount registers during the first start that has a valid LOCAL_API_URL. Validate that machine on the LAPI once. If the LAPI is down, the start exits and the next start retries while login: is still missing. Later starts reuse the saved login.

Build

A push to main builds and pushes :main and :latest. A tag v* builds and pushes :<tag> and :latest. The first commit on main is that push, so the first image is produced without a manual build.

The base is crowdsecurity/crowdsec:v1.8.2. yq v4.50.1 is rebuilt with Go 1.26.10 from a source tarball checked by YQ_SHA256. golang.org/x/net v0.60.0, golang.org/x/text v0.42.0, and golang.org/x/sys v0.49.0 replace the versions in that tag so the image scan can clear fixed HIGH findings. Alpine packages in the su-exec stage are pinned. A version bump is a new CrowdSec tag, the Alpine tag, YQ_VERSION, YQ_SHA256, or one of those module pins. Do not resolve a floating tag during the build.

License

MIT. See LICENSE. Maintainer: openso. Source: https://git.openso.de/openso/crowdsec-agent