Why use Docker?
The default pompelmi path spawns a local clamscan binary. That works
great, but it means ClamAV must be installed on every machine that runs your code.
If you prefer to keep your application container lean, or if you are running in an
environment where installing system packages is not practical, running ClamAV in its
own container is the cleaner option.
The trade-off is a TCP hop instead of a process fork. In practice, on a local or private network, the latency difference is negligible compared to the scan time itself.
docker-compose.yml
Drop this into your project. It starts a clamd instance on port 3310 and persists virus definitions across restarts using a named volume.
services:
clamav:
image: clamav/clamav:stable
ports:
- "3310:3310"
volumes:
- clamav_db:/var/lib/clamav # definitions survive restarts
healthcheck:
test: ["CMD", "clamdcheck.sh"]
interval: 60s
retries: 3
start_period: 120s # see First boot below
volumes:
clamav_db:
Start the container
docker compose up -dCheck it is healthy
docker compose ps # clamav running (healthy)
First boot — expect 1–2 minutes
The very first time the container starts, ClamAV downloads its virus definition database (~300 MB). This takes 1–2 minutes depending on your connection speed. clamd will not accept scan requests until the download is complete.
The start_period: 120s in the healthcheck gives it that runway.
During this window the container reports starting, not
healthy. That is expected — do not restart it.
clamav_db volume, so startup is fast. Only the very first
cold start (or after a volume wipe) triggers the long download.
starting
state. clamd will refuse the connection and pompelmi will reject with
ECONNREFUSED. Use the healthcheck or a retry mechanism in your
application startup path.
Using it with pompelmi
Once the container is healthy, point pompelmi at it by passing host
and port. No other changes to your code are needed.
const pompelmi = require('pompelmi');
const result = await pompelmi.scan('/path/to/upload.zip', {
host: '127.0.0.1',
port: 3310,
});
// "Clean" | "Malicious" | "ScanError" — exactly the same as the CLI path
See API → options object for the full
list of options including timeout.
App integration
If your application runs in the same Compose file, use depends_on
with a health condition so your app waits for clamd to be ready before it starts
accepting traffic.
services:
clamav:
image: clamav/clamav:stable
ports:
- "3310:3310"
volumes:
- clamav_db:/var/lib/clamav
healthcheck:
test: ["CMD", "clamdcheck.sh"]
interval: 60s
retries: 3
start_period: 120s
app:
build: .
depends_on:
clamav:
condition: service_healthy
environment:
CLAMAV_HOST: clamav
CLAMAV_PORT: "3310"
volumes:
clamav_db:
host: 'clamav' (not '127.0.0.1') is the correct
value for CLAMAV_HOST.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
ECONNREFUSED |
clamd is not yet running or still downloading definitions. | Wait for the healthcheck to report healthy, then retry. |
Container stays starting for more than 5 minutes |
Definition download stalled or start_period is too short. |
Check docker compose logs clamav. Increase start_period if on a slow connection. |
clamd connection timed out |
The file is very large, or the container is under heavy load. | Increase timeout in options, or check container CPU/memory. |
Always returns "ScanError" |
Definitions are present but corrupted, or clamd restarted mid-stream. | Run docker compose restart clamav and wait for healthy. |