Vulnsy Docs
Self-hosted

Configuration

Reference for every setting in the self-hosted .env file, covering hostnames, ports, secrets, databases, storage, SMTP email, the first administrator, the license and AI.

All configuration lives in .env, in the directory that holds docker-compose.yml. docker/generate-env.sh creates it from .env.self-hosted.example, which documents every variable. Docker Compose reads .env to fill in docker-compose.yml, and passes it to the app container as its environment.

Editing .env

  • An empty value means the setting is not set.
  • ${NAME} refers to another variable. For example, NEXTAUTH_URL=https://${APP_HOST} follows APP_HOST, so a hostname only needs to change in one place.
  • Wrap values that contain $, # or spaces in single quotes, for example SMTP_PASSWORD='p@ss#word$1'. The setup script does this for the administrator password and the SMTP credentials it writes.
  • Flags take the values true or false.

Applying Changes

After editing .env, recreate the containers whose settings changed:

docker compose --profile tls up -d   # with the bundled Caddy
cd /opt/vulnsy && sudo docker compose up -d                 # with your own reverse proxy

docker compose restart is not enough: it restarts the containers with the environment they were created with.

In the tables below, Default is the value that generate-env.sh writes, and After install says whether you can change the setting later.

Edition and Image

VariableMeaningDefaultAfter install
VULNSY_EDITIONSelects the self-hosted editionself-hostedDo not change
VULNSY_VERSIONVersion of the installed package; set by install.sh from the package's VERSION file. Do not edit by hand: install.sh upgrade updates it.Yes, by upgrading
COMPOSE_PROFILEStls to run the bundled Caddy (the default), empty when you run your own reverse proxy (--no-tls).Yes

Hostnames

VariableMeaningDefaultAfter install
APP_HOSTPublic hostname of the app, without https://Set by the scriptYes. Update DNS first, and your certificate if you provide your own. Links in emails that were already sent keep the old hostname.
FILES_HOSTPublic hostname for file downloads, served by MinIO. Must differ from APP_HOST.files. followed by the app hostYes, as for APP_HOST
NEXTAUTH_URLPublic URL of the app, used for sign-in callbacks and linkshttps://${APP_HOST}Follows APP_HOST
NEXT_PUBLIC_APP_URLPublic URL of the app, used for linkshttps://${APP_HOST}Follows APP_HOST

Published Ports

VariableMeaningDefaultAfter install
BIND_ADDRESSServer address on which the app and MinIO ports are published127.0.0.1Yes
APP_PORTHost port of the app. Your own reverse proxy forwards the app host here.3000Yes
MINIO_PORTHost port of MinIO's S3 API. Your own reverse proxy forwards the files host here.9000Yes

With the bundled Caddy, only ports 80 and 443 need to be reachable. Change BIND_ADDRESS to 0.0.0.0, or to one of the server's addresses, only when your reverse proxy runs on another machine.

Secrets

VariableMeaningDefaultAfter install
NEXTAUTH_SECRETSigns user sessionsGeneratedChanging it signs everyone out
AUTH_SECRETThe same secret, under the name that newer sign-in code reads${NEXTAUTH_SECRET}Leave as it is
PLATFORM_ADMIN_JWT_SECRETSigns sessions of the vulnsy.com operator portal, which the self-hosted edition does not serveGeneratedYes, with no effect on users
API_KEY_PEPPERSecret used to hash REST API keysGeneratedNo, see below
AI_SETTINGS_ENCRYPTION_KEYEncrypts stored AI credentials. Exactly 64 hexadecimal characters.GeneratedNo, see below

Three secrets cannot change without consequences. The same applies when you restore the database with a different .env:

SecretIf it changesTo recover
NEXTAUTH_SECRETEvery user is signed outUsers sign in again
API_KEY_PEPPEREvery existing REST API key stops workingUsers create new API keys
AI_SETTINGS_ENCRYPTION_KEYStored AI credentials (an OpenRouter key or an endpoint token) can no longer be decryptedAn administrator enters them again under Admin > Organization > AI Assistant

Back up .env together with the databases, and restore them together. See Backup & Restore.

PostgreSQL

VariableMeaningDefaultAfter install
POSTGRES_USERDatabase role that owns the three Vulnsy databasesvulnsyNo. It is set when the database volume is first created.
POSTGRES_PASSWORDPassword of that role. Use URL-safe characters only, because it is part of the database URLs.Generated (hexadecimal)Not by editing .env alone, see below
CONTROL_PLANE_DATABASE_URLConnection URL of vulnsy_control: installation settings, license, instance ID and AI settingsBuilt from the values aboveLeave as it is
SHARED_DATABASE_URLConnection URL of vulnsy_shared: the Vulnsy libraryBuilt from the values aboveLeave as it is
TENANT_DATABASE_URLConnection URL of vulnsy_tenant: your organization's users and dataBuilt from the values aboveLeave as it is

Changing the Database Password

PostgreSQL reads POSTGRES_PASSWORD only when it creates its data volume, on the first start. To change the password later, change it in PostgreSQL first:

docker compose exec postgres psql -U vulnsy -d postgres -c '\password vulnsy'

Enter a new password made of letters and digits only. Then set the same value as POSTGRES_PASSWORD in .env, and run docker compose up -d. If you changed POSTGRES_USER before the first start, use that name instead of vulnsy.

Object Storage

VariableMeaningDefaultAfter install
MINIO_ROOT_USERMinIO administrator user. The app uses it as its S3 access key.vulnsyKeep the value
MINIO_ROOT_PASSWORDMinIO administrator password, at least 8 characters. The app uses it as its S3 secret key.GeneratedKeep the value
S3_BUCKET_NAMEBucket that holds all uploaded files, created on the first startvulnsyNo. Files already uploaded stay in the old bucket.
S3_ENDPOINTS3 API URL that the app uses, on the internal Docker networkhttp://minio:9000Only when moving to another store
S3_PUBLIC_ENDPOINTS3 API URL in the signed download links that browsers open: the files host, at its roothttps://${FILES_HOST}Follows FILES_HOST
S3_FORCE_PATH_STYLEPuts the bucket name in the URL path rather than in the hostname, as MinIO requirestrueKeep true
AWS_REGIONRegion sent to the S3 APIus-east-1, MinIO's defaultOnly for another store
AWS_ACCESS_KEY_IDS3 access key that the app uses${MINIO_ROOT_USER}Only for another store
AWS_SECRET_ACCESS_KEYS3 secret key that the app uses${MINIO_ROOT_PASSWORD}Only for another store

Storage Options

Bundled object storage (the default). The bundled image is Silo, a maintained MinIO-compatible fork (the MINIO_* variable names above are kept for familiarity). Files are kept in the vulnsy_minio-data Docker volume. The app connects to MinIO over the internal network (S3_ENDPOINT), and signs download links for the files host (S3_PUBLIC_ENDPOINT), which browsers can reach. The files host must be a plain reverse proxy of MinIO, at the root of its own hostname, that passes the Host header through unchanged: a signature covers the hostname and the path, so rewriting either one breaks every link.

Another S3-compatible store. To keep files in an S3-compatible service that you already run:

  1. Create a bucket and credentials for Vulnsy in that store.
  2. Set S3_ENDPOINT to the store's S3 API URL as the server reaches it, and S3_PUBLIC_ENDPOINT to the URL at which your users' browsers reach it. The two are often the same.
  3. Set S3_BUCKET_NAME, AWS_REGION, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY for that store.
  4. Run docker compose up -d.

Vulnsy uses path-style requests, with the bucket name in the URL path, so the store must support them. If you switch an existing installation, first copy every object from the old bucket to the new one, keeping the object names. The bundled MinIO still starts, because the app depends on it, but it no longer receives files.

Vulnsy does not request server-side encryption from an S3-compatible store. For encryption at rest, encrypt the server's disk or enable encryption in the store.

Email (SMTP)

VariableMeaningDefaultAfter install
EMAIL_FROMSender address of the emails Vulnsy sends. Your SMTP server must accept it.noreply@ followed by the app hostYes
EMAIL_FROM_NAMESender display nameVulnsyYes
SMTP_HOSTSMTP server. When it is empty, Vulnsy sends no email.EmptyYes
SMTP_PORTSMTP port587Yes
SMTP_SECUREtrue: TLS from the start of the connection (implicit TLS, usually port 465). false: a plain connection, upgraded with STARTTLS when the server offers it (ports 587 and 25).falseYes
SMTP_USERSMTP username. When it is empty, Vulnsy does not authenticate.EmptyYes
SMTP_PASSWORDSMTP passwordEmptyYes
VULNSY_ABUSE_EMAILAbuse contact printed in the footer of emails sent to your clientsThe first administrator's email addressYes

SMTP Options

Mail serverSMTP_PORTSMTP_SECURESMTP_USER and SMTP_PASSWORD
Submission with STARTTLS587falseYour account
Implicit TLS465trueYour account
Internal relay that accepts mail from this server without authentication25, or the relay's portfalseLeave both empty

Vulnsy sends account emails through this server: sign-in details for new users and temporary passwords. Client portal and disclosure emails use it as well, unless an administrator sets up a different SMTP server in the app under Admin > Email > Delivery. That server then takes precedence for those emails.

The app's log shows the email settings in use, in a line such as:

Email: SMTP smtp.example.com:587 (STARTTLS if offered, authenticated), from vulnsy@example.com

First Administrator

VariableMeaningDefaultAfter install
INITIAL_ADMIN_EMAILEmail address of the first administratorSet by the scriptNo effect after the first start
INITIAL_ADMIN_PASSWORDInitial password of the first administrator, which must be changed at first sign-inGeneratedNo effect after the first start. You can remove it once you have signed in.

Vulnsy reads these only on the first start, while the database is still empty. Two optional variables are read at the same time. They are included (left blank) in .env.self-hosted.example, so copy the value into .env before the first start if you want them. generate-env.sh does not set these two, so add them by hand:

VariableMeaningDefaultAfter install
INITIAL_ADMIN_NAMEDisplay name of the first administratorAdministratorNo effect after the first start
SELF_HOSTED_COMPANY_NAMEName of the organization created on the first startVulnsyNo effect after the first start

License

VariableMeaningDefaultAfter install
VULNSY_LICENSEThe license tokenEmptyYes, see Licensing
VULNSY_LICENSE_FILEPath, inside the app container, to a file that holds the token. Mount the file into the container. Commented out in the example file.Not setYes

Authentication

VariableMeaningDefaultAfter install
PASSWORD_BREACH_CHECKSet to false to disable the Have I Been Pwned breached-password check. That check is the only outbound network call in the sign-in and password-change path (it sends a 5-character SHA-1 prefix using k-anonymity, never the password). Turn it off for air-gapped installs or where outbound calls are not allowed.EnabledYes, restart the app

AI

VariableMeaningDefaultAfter install
OPENROUTER_API_KEYOptional, and normally left empty. The AI modes of the self-hosted edition use the OpenRouter key or endpoint that an administrator enters in the app.EmptyYes
OPENROUTER_BASE_URLOpenRouter API URL used by the Your OpenRouter key mode. Commented out in the example file.https://openrouter.ai/api/v1Yes
AI_QUALITY_MODELModel for finding drafts, executive summaries and composed text in the Your OpenRouter key mode, when the administrator has not chosen one. Commented out in the example file.Vulnsy's defaultYes
AI_ECONOMY_MODELModel for rewrites and connection tests in the same mode, when the administrator has not chosen one. Commented out in the example file.Vulnsy's defaultYes
AI_ALLOW_PRIVATE_ENDPOINTStrue lets the Your own / local AI endpoint mode use plain HTTP, any port, and private network addressesfalseYes

AI Options

The AI assistant must be included in your license. An administrator chooses how it runs under Admin > Organization > AI Assistant. Vulnsy-managed AI is not available in the self-hosted edition, which leaves two modes:

  • Your OpenRouter key. Requests go to OpenRouter with your own API key, so the server needs outbound HTTPS access to OpenRouter.
  • Your own / local AI endpoint. Any OpenAI-compatible API, such as Ollama or vLLM. Vulnsy calls /chat/completions under the base URL you enter.

By default, an endpoint must use HTTPS on port 443 or 8443, at a public address. For a model server on your own network, set AI_ALLOW_PRIVATE_ENDPOINTS=true and run docker compose up -d app. Vulnsy then also accepts http://, any port, and private addresses, including 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 and IPv6 unique-local addresses.

Loopback and link-local addresses are always blocked, including localhost, 127.0.0.1 and the cloud metadata address 169.254.169.254. Inside the app container, localhost is the container itself. Instead, use:

  • The server's LAN address, when the model server runs on the Docker host, for example http://192.168.1.20:11434/v1. The model server must listen on that address, not only on 127.0.0.1.
  • A service name, when the model server runs as another service in the same Compose project, for example http://ollama:11434/v1.

See Provider Modes for how the modes work in the app.

On this page