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 -d
Check 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.

On every subsequent start the definitions load straight from the clamav_db volume, so startup is fast. Only the very first cold start (or after a volume wipe) triggers the long download.
Do not send scan requests while the container is still in 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:
Inside a Compose network, use the service name as the hostname. In the example above, 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.