Docker

Run the Threatless web app and API with Docker Compose.

Threatless ships as two images: a Nuxt frontend and a Spring Boot API that embeds ClamAV. They expect an external Docker network named threatless and a PostgreSQL instance reachable as threatless-db.

This matches the Coolify layout used by isolated stacks such as MaCarteAvis: one dedicated destination/network per product, no host port mappings in production, Traefik on standard 80/443.

Architecture

threatless (external Docker network / Coolify destination)
  ├── threatless-web   # root docker-compose.yml — container port 80
  ├── threatless-api   # api/docker-compose.yml — container port 8080, ClamAV inside
  └── threatless-db    # PostgreSQL you provide (Coolify standalone DB)

Prerequisites

  • Docker and Docker Compose
  • A PostgreSQL 16+ database
  • An external network: docker network create threatless

1. Create the network and database

terminal
docker network create threatless

Run Postgres on that network with database/user/password threatless (or change the JDBC URL). Hostname must match SPRING_DATASOURCE_URL (default threatless-db).

2. Start the API

terminal
cd api
cp .env.example .env
# set SPRING_DATASOURCE_* , Google OAuth, CLIENT_ALLOWED_ORIGINS
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build

docker-compose.yml only exposes container port 8080 (Coolify / Traefik). The local overlay publishes it on the host. Do not add a host 8080:8080 mapping in Coolify — that is what triggers Bind for 0.0.0.0:8080 failed: port is already allocated.

The API image:

  • Builds the Spring Boot jar
  • Installs ClamAV, starts clamd on 127.0.0.1:3310
  • Runs freshclam at image build and daily at midnight

3. Start the web app

From the repository root:

terminal
cp .env.example .env
# API_URL must be the public URL browsers use to reach the API
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build

Production compose only exposes container port 80. The local overlay publishes 8032 on the host. Point Traefik or your proxy at the web service (alias threatless-web).

The web image build (nuxt build) needs about 4 GB RAM. Coolify aborting with exit 134 (SIGABRT / JavaScript heap out of memory) means the builder ran out of heap. The Dockerfile sets --max-old-space-size=4096. Give the Coolify server enough free memory for that step; do not lower the heap flag below 4096.

4. First account

Open the web origin, register, confirm email, then create an API key.

CLIENT_ALLOWED_ORIGINS on the API must include the exact web origin (scheme + host + port). API_URL on the web image must be the API origin the browser can call, not the Docker-internal hostname.

Coolify

Create a Docker destination whose network is threatless, then put the PostgreSQL resource, the API, and the web app on that destination. Do not leave the database on the default coolify network.

Compose files attach to that external network so the API can reach Postgres by hostname. Coolify generates Traefik labels from each service's Domains field.

ServiceCompose nameDomain example
Webwebhttps://trythreatless.com,https://www.trythreatless.com
APIspring-apphttps://api.trythreatless.com:8080

The :8080 suffix is the container port Coolify's proxy must target. Public HTTPS stays on 443. A suffix such as :8082 (or any port Spring is not listening on) produces 502 Bad Gateway.

Do not enable Raw Compose Deployment. Do not publish ports: on Coolify. Host mappings belong only in the docker-compose.local.yml overlays.

Next

Configure environment variables before going to production — especially JWT secrets, Stripe, and mail.

Previous

Environment variables

Complete reference of Threatless frontend and API environment variables.

Next