# AntiBot/WAF GW 1.6.8 — turnkey docker-compose deployment. # # What's bundled # ────────────── # • appsec-antibot-gw — the gateway (image ships GeoLite2-ASN + # GeoLite2-City pre-seeded from build context; # auto-copied to /data on first boot) # • appsec-timescaledb — Postgres 16 + TimescaleDB; gateway always has # POSTGRES_DSN ready, switch backends via the # Controls dashboard's pill toggle # • appsecgw-redis — Redis 7 with requirepass (shared store for # mesh-sync + cross-replica bans) # • crowdsec — CrowdSec LAPI (community blocklist) — first-run # bouncer registration: see "Post-up steps" below # # Network exposure (by default) # ────────────────────────────── # Only the GATEWAY exposes a port to the host (8443). All other services # are reachable ONLY from the internal Docker network `default` — # Redis (no -p), CrowdSec LAPI (no -p), Postgres (no -p). Auth is also # enforced inside the network (Redis password, CrowdSec api-key, # Postgres password) as defence-in-depth. # # Quick start # ─────────── # 1. cp .env.example .env # 2. edit .env — at minimum set: # UPSTREAM, ADMIN_ALLOWED_IPS, TRUSTED_PROXIES, # POSTGRES_PASSWORD, REDIS_PASSWORD # (and any external-integration keys you want active) # 3. docker compose up -d # 4. Post-up — register the gateway as a CrowdSec bouncer: # BKEY=$(docker exec crowdsec cscli bouncers add appsecgw -o raw) # echo "CROWDSEC_LAPI_KEY=$BKEY" >> .env # echo "CROWDSEC_LAPI_URL=http://crowdsec:8080" >> .env # docker compose up -d --force-recreate appsec-antibot-gw # # MaxMind GeoLite2 # ──────────────── # The gateway image SHIPS GeoLite2-ASN.mmdb + GeoLite2-City.mmdb in # /usr/local/share/maxmind/. On first boot they're auto-copied to /data # (the persistent volume), so the ASN + City lookups work out-of-the-box # with NO MaxMind license needed for an initial install. To refresh # them with the latest feed: set MAXMIND_LICENSE_KEY in .env and the # `_maxmind_auto_fetch` loop downloads new versions weekly. services: appsec-timescaledb: image: timescale/timescaledb:latest-pg16@sha256:51eb3bcdfc41f481c797026813d9d457fb5cbc8ea370a65640d8cda13a4040c1 container_name: appsec-timescaledb restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-appsec} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD env var is required} POSTGRES_DB: ${POSTGRES_DB:-appsec} volumes: - appsec-timescaledb-data:/var/lib/postgresql/data healthcheck: test: ["CMD", "pg_isready", "-U", "${POSTGRES_USER:-appsec}", "-d", "${POSTGRES_DB:-appsec}"] interval: 10s timeout: 3s retries: 10 # 1.6.8 — Redis sidecar for mesh-sync + cross-replica ban propagation. # No host port mapping → reachable only from the docker network. # `requirepass` set via env so even an attacker that joined the network # still needs the password. appsecgw-redis: image: redis:7-alpine container_name: appsecgw-redis restart: unless-stopped command: > redis-server --requirepass ${REDIS_PASSWORD:?REDIS_PASSWORD env var is required} --maxmemory 256mb --maxmemory-policy allkeys-lru --appendonly no healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "PING"] interval: 10s timeout: 3s retries: 5 # 1.6.8 — CrowdSec LAPI sidecar. Bouncer key generated post-up (see # header). The single volume holds /etc/crowdsec (config) + the local # decisions DB so bans persist across compose recreate. crowdsec: # 1.8.14 iter-18 — wrapped image with a `cscli` shim that skips # `hub update` / `hub upgrade` calls when today's stamp already exists in # /var/lib/crowdsec/data/.hub_update_stamp. Persistent volume holds the # stamp, so a same-day restart re-downloads nothing; the next UTC midnight # naturally invalidates it and the next start fetches fresh feeds. # See ./crowdsec/Dockerfile.crowdsec-cached + cscli-shim.sh. build: context: ./crowdsec dockerfile: Dockerfile.crowdsec-cached image: crowdsec-cached:latest container_name: crowdsec restart: unless-stopped environment: COLLECTIONS: "crowdsecurity/linux crowdsecurity/http-cve crowdsecurity/base-http-scenarios" DISABLE_AGENT: "false" DISABLE_LOCAL_API: "false" volumes: - crowdsec-data:/var/lib/crowdsec/data - crowdsec-config:/etc/crowdsec healthcheck: test: ["CMD", "cscli", "lapi", "status"] interval: 30s timeout: 5s retries: 5 appsec-antibot-gw: image: appsec-antibot-gw:1.9.12 container_name: appsec-antibot-gw1.9.12 restart: unless-stopped depends_on: appsec-timescaledb: condition: service_healthy appsecgw-redis: condition: service_healthy crowdsec: condition: service_started ports: - "8443:8443" environment: # ── Required ────────────────────────────────────────────────────── UPSTREAM: ${UPSTREAM:?UPSTREAM env var is required} PORT: ${PORT:-8443} TLS_ENABLED: ${TLS_ENABLED:-0} APPSECGW_KEY_DIR: /data # ── Admin / hardening ───────────────────────────────────────────── ALLOWED_HOSTS: ${ALLOWED_HOSTS:-} ADMIN_ALLOWED_IPS: ${ADMIN_ALLOWED_IPS:-127.0.0.1/32} TRUSTED_PROXIES: ${TRUSTED_PROXIES:-127.0.0.1/32} TRUST_XFF: ${TRUST_XFF:-last} ADMIN_KEY: ${ADMIN_KEY:-} # ── Cookie gate ────────────────────────────────────────────────── JS_CHALLENGE: ${JS_CHALLENGE:-1} JS_CHAL_OPEN_PATHS: ${JS_CHAL_OPEN_PATHS:-/content/,/static/,/assets/,/api/} # ── Risk-score thresholds (also tunable live at /__controls) ───── SOFT_CHALLENGE_SCORE: ${SOFT_CHALLENGE_SCORE:-4} RISK_BAN_THRESHOLD: ${RISK_BAN_THRESHOLD:-50} TURNSTILE_RISK_THRESHOLD: ${TURNSTILE_RISK_THRESHOLD:-0} # ── External integrations (off-by-default; set keys to enable) ─── TURNSTILE_SITEKEY: ${TURNSTILE_SITEKEY:-} TURNSTILE_SECRET: ${TURNSTILE_SECRET:-} TURNSTILE_ENABLED: ${TURNSTILE_ENABLED:-} ABUSEIPDB_KEY: ${ABUSEIPDB_KEY:-} MAXMIND_LICENSE_KEY: ${MAXMIND_LICENSE_KEY:-} ANUBIS_ENABLED: ${ANUBIS_ENABLED:-0} ANUBIS_DIFFICULTY_BOOST: ${ANUBIS_DIFFICULTY_BOOST:-1} # ── 1.6.8 — Redis (mesh-sync + cross-replica ban store) ────────── # Default points at the bundled appsecgw-redis sidecar. Override # in .env to point elsewhere (e.g. managed Redis cloud). REDIS_URL: ${REDIS_URL:-redis://:${REDIS_PASSWORD}@appsecgw-redis:6379} # ── 1.8.8 — Redis TLS enforcement ──────────────────────────────── # REDIS_REQUIRE_TLS defaults to true. Set false for local dev where # the bundled Redis sidecar uses plain redis:// (no TLS). REDIS_REQUIRE_TLS: ${REDIS_REQUIRE_TLS:-false} # ── 1.6.8 — CrowdSec (community blocklist) ─────────────────────── # Default URL is the bundled sidecar. The bouncer key has to be # generated post-up (see compose header) — operator pastes it into # .env then `docker compose up -d --force-recreate appsec-antibot-gw`. CROWDSEC_LAPI_URL: ${CROWDSEC_LAPI_URL:-http://crowdsec:8080} CROWDSEC_LAPI_KEY: ${CROWDSEC_LAPI_KEY:-} # ── Event store: POSTGRES-ONLY ─────────────────────────────────── # Single-DB mode: the active backend is auto-derived from POSTGRES_DSN. # POSTGRES_DSN set → Postgres-only (this deployment). # POSTGRES_DSN unset → SQLite-only. # The legacy DB_BACKEND env is IGNORED while POSTGRES_DSN is set, so it # is intentionally NOT mapped here — setting it would be a no-op and # misleading. SQLite is never a selectable backend in this mode; it is # only an in-process emergency fallback if Postgres becomes unreachable, # and the gateway auto-recovers back to Postgres once it is healthy again # (no restart needed). To run SQLite-only instead, delete the # appsec-timescaledb service + this POSTGRES_DSN line. POSTGRES_DSN: postgresql://${POSTGRES_USER:-appsec}:${POSTGRES_PASSWORD}@appsec-timescaledb:5432/${POSTGRES_DB:-appsec} # ── Logging ────────────────────────────────────────────────────── LOG_LEVEL: ${LOG_LEVEL:-info} LOG_FORMAT: ${LOG_FORMAT:-json} healthcheck: test: ["CMD", "python3", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8443/antibot-appsec-gateway/live', timeout=3).read()==b'ok' else 1)"] interval: 10s timeout: 3s retries: 3 read_only: true tmpfs: - /tmp:size=64m,mode=1777,nosuid,nodev,noexec cap_drop: - ALL security_opt: - no-new-privileges:true deploy: resources: limits: # 1.8.15 — raised 256M → 512M. With Postgres backend, ~170 hot-reload # knobs, Redis mesh client, Tor exit list (~1.2k entries), and the # ip_state/timeline structures, the previous 256M caused the kernel # to swap-page the process under load — gateway felt sluggish. RSS # high-water-mark is ~180M; 512M leaves real headroom. memory: 512M cpus: "1.0" pids: 200 volumes: - antibot-data:/data extra_hosts: - "host.docker.internal:host-gateway" volumes: antibot-data: external: true appsec-timescaledb-data: external: true name: appsec-timescaledb-data crowdsec-data: external: true name: crowdsec-data crowdsec-config: external: true name: crowdsec-conf networks: default: external: true name: antibot-net