Environment variables
Split config: the web image is Nuxt, the API image is Spring Boot. Copy .env.example at the repo root and api/.env.example before the first boot.
Web (Nuxt)
These variables are mandatory for the frontend to talk to the API:
API_URL / NUXT_PUBLIC_API_URL
- Type: String (URL)
- Required: ✅ Yes in production
- Description: Public API origin used by the browser (
https://api.example.com). Baked in at Docker build as well as runtime. - Example:
https://api.trythreatless.com
NUXT_PUBLIC_GOOGLE_OAUTH_ENABLED
- Type: Boolean string (
true/false) - Required: 🔶 Optional (default
truein Docker) - Description: Shows the Google button on login/register. Must match API
GOOGLE_OAUTH_ENABLED.
Stripe price IDs in the root .env.example are consumed by the API, not Nuxt. Keep them on the API service.
API (Spring Boot)
Database (required)
SPRING_DATASOURCE_URL
- Type: JDBC URL
- Required: ✅ Yes
- Default:
jdbc:postgresql://threatless-db:5432/threatless
SPRING_DATASOURCE_USERNAME / SPRING_DATASOURCE_PASSWORD
- Required: ✅ Yes
- Default:
threatless/threatless
HTTP
SPRING_APP_PORT
- Default:
8080 - Description: Host port used only with
docker-compose.local.yml. Coolify must not publish this port on the host; the proxy reaches container port 8080 on the Docker network.
On Coolify, set the API service domain to the public origin plus the container port, for example https://api.trythreatless.com:8080. Visitors still use 443; the :8080 suffix is only the internal port Traefik must target.
CLIENT_ALLOWED_ORIGINS
- Type: Comma-separated origins
- Required: ✅ Yes
- Default:
http://localhost:3000 - Description: CORS allowlist for the dashboard (
credentials: include). Include every web origin that will send cookies (scheme + host + port, no path).wwwand apex are paired automatically, sohttps://trythreatless.comalso allowshttps://www.trythreatless.com. Spring also readsTHREATLESS_APP_ALLOWED_ORIGINSas the same property — set one of them, not two conflicting lists.
Google OAuth
GOOGLE_OAUTH_ENABLED
- Default:
true
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
- Required when OAuth is enabled
GOOGLE_REDIRECT_URI
- Default:
http://localhost:8080/web/auth/google/callback - Description: Must match the Google Cloud authorized redirect URI.
Stripe (paid plans)
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRICE_ID_STARTER=
STRIPE_PRICE_ID_PRO=
STRIPE_PRICE_ID_BUSINESS=
STRIPE_PRICE_ID_ENTERPRISE=
STRIPE_PRICE_ID_ELITE=
STRIPE_PRICE_ID_ULTIMATE=
Webhook endpoint: POST /webhooks/stripe (public, signature verified).
ClamAV
Configured in application.properties (override with Spring env):
| Property | Default |
|---|---|
clamd.host | 127.0.0.1 |
clamd.port | 3310 |
clamd.maxfilesize | 10MB |
spring.servlet.multipart.max-file-size | 10MB |
The Docker image runs clamd locally inside the API container. You normally do not point these at another host.
JWT and cookies
| Property | Default | Notes |
|---|---|---|
threatless.app.jwtExpirationMs | 900000 (15 min) | Access token |
threatless.app.refreshExpirationMs | 1296000000 (15 days) | Refresh cookie |
threatless.app.cookieSecure | false | Set true behind HTTPS |
threatless.app.jwtSecret | sample value | Override in production with a long random secret |
Generate a secret:
openssl rand -base64 48
Spring Mail settings (spring.mail.*) send confirmation and password-reset messages. Point them at your SMTP provider. client.context.url should be the public web origin so links in emails resolve.