Runbook: clamav (clamd) Sidecar Operations
On this page
The clamd sidecar backs canopy-applications' upload quarantine (ADR-042). It is availability-decoupled by design: no canopy service depends_on it, uploads are always accepted (they quarantine pending), and already-clean content keeps serving through any scanner outage.
Signature definitions
-
The devstack image (
devstack/clamav/Dockerfile, digest-pinnedclamav/clamav:1.4) ships a build-time definition snapshot; thecanopy_clamav_dbvolume receives it on first mount and the in-containerfreshclamdaemon keeps it fresh incrementally. After editing the bakedtest.ndborclamd.conf,docker volume rm canopy_clamav_dbso the next boot re-initializes. -
Freshness is enforced service-side, fail-closed:
ClamdScannerparses the definition date from clamd’sVERSIONline; older thanCANOPY_APPLICATIONS__SCANNER_MAX_DEFINITION_AGE_DAYS(default 7) fails the scan — uploads staypending, the backlog gauges climb. Recovery: restore freshclam’s egress (or a private mirror), wait for the daemon’s next check to update the volume; the workers drain the backlog automatically once clamd serves fresh definitions. -
Boot ordering (#1522): the image’s stock init starts freshclam and clamd concurrently, and a definition swap that lands while clamd is mid-load leaves clamd pinned to the OLD inode — its SelfCheck baseline stats the new file already in place, reports "Database status OK" forever, and the reload never comes (waiting, the previous recovery advice here, does not work for this mode). The canopy entrypoint (
devstack/clamav/entrypoint.sh) closes it: one bounded freshclam pre-pass completes BEFORE clamd loads, so there is nothing left to swap; the daemon’s later checks notify through the then-present socket normally. If a container somehow still serves definitions older than the volume’s (VERSIONdate vsls /var/lib/clamav/daily.cld), the fix is a container restart — clamd reloads from disk on boot. -
The pre-pass is bounded (
FRESHCLAM_PREPASS_TIMEOUT, default 120 s) and fails OPEN on boot — an airgapped or slow-mirror stack still starts on its on-disk definitions; the upload path stays fail-closed regardless via the service-side gate above (the control that matters). -
Production must decide freshclam’s egress path explicitly (direct
database.clamav.net, or a private mirror for air-gapped deployments) and sizestart_periodaccordingly — the pre-pass rides the same egress.
Outage recovery / backlog
-
Symptoms:
canopy_applications_scan_pending_countandcanopy_applications_scan_oldest_pending_age_secondsclimb (WARN past 1h); worker logs showscan deferred with backoff. -
clamd down → restore it; nothing else is needed. Deferred rows retry with exponential backoff (60s×2ⁿ, cap 6h) and the claim budget (
SCAN_MAX_ATTEMPTS, default 8 claims ⇒ 7 real scans) converges permanently-failing rows to terminalerror. -
Terminal
errorrows: recover withcanopy application document-rescan --application-id … --document-id …(or the service-tokenPOST …/rescan) — never direct SQL. Rescan resets the rowpendingunder a bumped scan generation and REVOKES any standing acceptance (the verdict is in doubt). -
CANOPY_APPLICATIONS__SCAN_WORKER_CONCURRENCY=0is the operator kill switch: boot logs ERROR,canopy_applications_scan_worker_disabled=1, every upload strandspendinguntil re-enabled.
Override audit
Supervisor releases of quarantined-skipped documents (application_document.scan_overridden) land in the canopy-security chain with the releasing actor and a reason_sha256; the prose reason lives on the row (scan_override_reason). To verify a release: hash the row’s reason and compare with the chained digest.
Resources / isolation
-
Compose pins
mem_limit: 4g(official guidance 3–4 GiB; reloads spike),cpus: 2,pids_limit: 256,MaxThreads 4/MaxQueue 16inclamd.conf(≥ replicas ×SCAN_WORKER_CONCURRENCYwith headroom). OOM kills during reload → raisemem_limitbefore anything else. -
clamd TCP is unauthenticated and unencrypted: never expose it beyond the service network. The devstack publishes loopback-only (host-lane tests); production should not publish at all. The container runs unprivileged (
/init-unprivileged,USER clamav).