# HarborGuard scans container images with six tools behind one web UI

> HarborGuard is a container image vulnerability scanning platform that wraps Trivy, Grype, Syft, Dockle, OSV and Dive behind a Next.js interface, with optional patching and notification webhooks. It is a good fit for teams that want one dashboard, and a poor fit for anyone unwilling to expose a Docker socket.

**HarborGuard/HarborGuard** — Modern image vulnerability scanning & patching platform with multi-tool integration.

- Repository: https://github.com/HarborGuard/HarborGuard
- Website: https://demo.harborguard.co/
- Stars: 627 · Forks: 27
- Language: TypeScript
- License: AGPL-3.0
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/harborguard-harborguard

## One dashboard over Trivy, Grype, Syft, Dockle, OSV and Dive

HarborGuard describes itself as a container security scanning platform that gives a web interface for managing and visualising security assessments of Docker images. The audience is a team that already has images in a registry and wants a single place to look at findings, with a dashboard, a scans view and a library view, rather than six command lines with six output formats. The repository is TypeScript, built on Next.js 15.4.6 with React 19.1.0, and the description calls it an image vulnerability scanning and patching platform with multi tool integration. Patching is a separate mode, not the default. The distinction matters, because the read only mode and the mode that rewrites image layers have very different privilege requirements, and the documentation treats them as different commands.

## The Docker socket is the hinge of the whole design

HarborGuard needs to see local images, and the way it gets that access is by mounting the Docker socket into its own container. The minimal command exposes only the web port.

```bash
docker run -p 3000:3000 ghcr.io/harborguard/harborguard:latest
```

To let it reach the images already on your machine, add the socket mount.

```bash
docker run -p 3000:3000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  ghcr.io/harborguard/harborguard:latest
```

The port mapping is the same in both cases, so the interface sits at http://localhost:3000 either way. A container that can write to the Docker socket can, in effect, ask the host daemon for a privileged container, and that is worth thinking about before mounting it on a shared or multi tenant machine.

## Patching needs a privileged container, and that is the trade

The patching mode changes the container flags, and the difference is not cosmetic. The documentation states that filesystem permission requires privileged access, and the command below adds it. A run without that flag is for scanning only.

```bash
docker run --privileged \
  -p 3000:3000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  ghcr.io/harborguard/harborguard:latest
```

A privileged container with the Docker socket mounted is close to root on the host by another route, so that flag is a decision for the platform owner rather than a default. The compose file reinforces the point with explicit resource limits, a cap of 2.0 CPUs and 4G of memory for the service.

## Six scanners, one comma separated switch

Every scanner is optional, and the ENABLED_SCANNERS variable decides which ones run. The default list is trivy, grype, syft, dockle, osv and dive, and the accepted values are exactly those six names. Two more variables control the pace of the work, because scanners are the expensive part of the operation. MAX_CONCURRENT_SCANS defaults to 3 with a valid range of 1 to 20, described as a limit on concurrent scanner execution to prevent resource exhaustion, and SCAN_TIMEOUT_MINUTES defaults to 30 with a range of 5 to 180. The container image pins the tool versions it ships, so a rebuild picks up known versions rather than whatever the package registry offers that day.

```dockerfile
ARG TRIVY_VERSION=v0.69.3
ARG DOCKLE_VERSION=0.4.15
ARG OSV_SCANNER_VERSION=v2.2.2
ARG DIVE_VERSION=0.13.1
```

The runtime image is built on node:20-alpine and adds PostgreSQL 16, Buildah, Podman, Skopeo and Chromium alongside the application, and a separate Go module in the harborguard-sensor directory is compiled first in the Dockerfile, which is the piece aimed at distributed sensor deployments.

## Findings leave through Teams, Slack, Gotify or Apprise

Notification targets are configured entirely through environment variables, and the list covers the common options. TEAMS_WEBHOOK_URL takes a Microsoft Teams webhook, SLACK_WEBHOOK_URL takes a Slack webhook, GOTIFY_SERVER_URL and GOTIFY_APP_TOKEN pair up for a self hosted Gotify server, and the Apprise trio of APPRISE_API_URL, APPRISE_CONFIG_KEY and APPRISE_URLS covers a self hosted Apprise instance or a direct list of service URLs. The last one matters more than it looks, because an Apprise deployment can fan out to many services from a single configuration. One switch decides how much noise you get: NOTIFY_ON_HIGH_SEVERITY defaults to true, so a notification goes out only for high and critical findings unless you set it to false.

## A bundled PostgreSQL, or a database you point at

Storage is a Postgres database handled through Prisma, and the container can run without you thinking about it. The compose file states that if DATABASE_URL is not set or the connection fails, the container uses its bundled PostgreSQL. The .env.example file shows the connection string shape the application expects, including a database named harborguard. An external database is one variable, and the documented example is a standard PostgreSQL URL.

```bash
docker run -p 3000:3000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/harborguard" \
  -v /var/run/docker.sock:/var/run/docker.sock \
  ghcr.io/harborguard/harborguard:latest
```
 The lifecycle of that data is handled too, with CLEANUP_OLD_SCANS_DAYS defaulting to 30 and accepting 1 to 365, described as automatic deletion of old scans. Two endpoints matter for orchestration: HEALTH_CHECK_ENABLED, on by default, exposes /api/health and /api/ready. Distributed sensor deployments, which the repository covers with a separate Dockerfile.distributed and docker-compose.distributed.yml, expect S3 compatible object storage, and the variables for it accept AWS style names as alternatives.

## Running from source means Prisma first, Next.js second

For a team that wants to modify the application rather than run the published image, the repository ships a normal Next.js and Prisma toolchain. The package.json scripts show the order of operations, and database commands are the part worth copying.

```json
    "db:init": "node scripts/init-database.js",
    "db:migrate": "npx prisma migrate deploy",
    "db:push": "npx prisma db push",
    "db:reset": "npx prisma migrate reset --force && npx prisma generate",
    "fresh": "rm -rf src/generated/prisma node_modules/.prisma && npx prisma generate",
    "test:e2e": "playwright test",
```

The development server and the production start command are defined next to it.

```json
    "dev": "NODE_ENV=development next dev --turbopack",
    "start": "node scripts/init-database.js && next start -p ${PORT:-3000}",
```

The start script runs the same init script before next start, so a first boot creates its schema without a separate step. The test:e2e entry drives Playwright, and the repository carries a playwright.config.ts plus tests/ directory, so the end to end suite is part of the project rather than something you add later. The build path also generates an OpenAPI document through a script in the scripts directory, which is useful if you plan to drive the platform from something other than its web interface.

## AGPL-3.0 and a release history that stopped in 2025

HarborGuard is licensed under AGPL-3.0, and that choice has consequences for anyone who modifies it and serves it over a network, so read the licence text before adopting it internally or building a product on top of it. The dependencies have their own terms, and the image bundles PostgreSQL, Buildah, Podman, Skopeo, Chromium and several scanner binaries alongside the application. On the release side the record is thin: the two published versions are v0.1b on 2025-08-14 and v0.2b on 2025-09-02, both carrying a b suffix that marks them as early. The last push was on 2026-05-23, so the code base is still moving, but the tagged releases are over a year old. A team that wants a versioned upgrade path with release notes will not find one here.

## Conclusion

HarborGuard fits a platform team that already runs containers on one host and wants a single place to see findings across Trivy, Grype, Syft, Dockle, OSV and Dive. It does not fit a locked-down environment where the Docker socket cannot be mounted, and patching is off the table unless you accept a privileged container. Before deploying, run the documented docker run command with the socket mounted, decide whether the container needs --privileged, and confirm that the ENABLED_SCANNERS list matches the scanners you have licensed or can download.

## FAQ

### How do I run HarborGuard with Docker?

The documented command is docker run -p 3000:3000 ghcr.io/harborguard/harborguard:latest, and the application is then reached at http://localhost:3000. Mounting /var/run/docker.sock is what gives it access to your local images.

### Which vulnerability scanners can HarborGuard use?

The ENABLED_SCANNERS variable takes a comma separated list drawn from trivy, grype, syft, dockle, osv and dive, and all six are enabled by default. The container image pins specific versions of Trivy, Dockle, OSV Scanner and Dive at build time.

### Can HarborGuard patch container images automatically?

There is an automatic patching mode, and the documentation states that filesystem permission requires privileged access, so the run command adds --privileged. A standard run without that flag is for scanning only.

### Does HarborGuard need an external database?

No. If DATABASE_URL is not set or the connection fails, the container falls back to its bundled PostgreSQL, and an external PostgreSQL can be supplied with the DATABASE_URL connection string. Scan history is trimmed automatically after CLEANUP_OLD_SCANS_DAYS, which defaults to 30.

## Sources

- [HarborGuard/HarborGuard on GitHub](https://github.com/HarborGuard/HarborGuard)
- [License: AGPL-3.0](https://github.com/HarborGuard/HarborGuard/blob/main/LICENSE)
- [Project website](https://demo.harborguard.co/)
- [README](https://github.com/HarborGuard/HarborGuard/blob/main/README.md)
- [Releases](https://github.com/HarborGuard/HarborGuard/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/harborguard-harborguard
