Install
Install the Vulnsy self-hosted edition from the offline package. Prepare DNS, download and extract the package, run the installer, sign in, and install your license.
Vulnsy is delivered as an offline package: one file that contains the installer and every container image Vulnsy runs. The installer sets up the server, loads the images and starts Vulnsy with Docker Compose. Nothing is pulled from a container registry, so the server needs no internet access to install or upgrade Vulnsy. Check the requirements first.
The examples use reports.example.com as the app host, files.reports.example.com as the files host and 1.4.0 as the version. Replace them with your own hostnames and the version you downloaded.
Prepare the server and DNS
Use a server that meets the requirements. The installer installs Docker Engine and the Docker Compose plugin from Docker's official repositories if they are missing, which needs access to download.docker.com. On a server without internet access, install Docker Engine and the Compose plugin from your own mirror first: the installer then uses them as they are.
Create DNS records for two hostnames, both pointing at the server:
| Hostname | Example | Serves |
|---|---|---|
| App host | reports.example.com | The Vulnsy web app |
| Files host | files.reports.example.com | Evidence images and file downloads, from the bundled file store |
Open TCP ports 80 and 443 to your users. For automatic certificates from Let's Encrypt, both ports must also be reachable from the internet. The server must also be able to reach your SMTP server.
The files host must be a separate hostname
Browsers download evidence and files directly from the files host, using signed URLs. The signature covers the hostname and the path, so the files host cannot be a path on the app host (such as reports.example.com/files), and any proxy in front of it must pass the Host header through unchanged.
Download and extract the package
Download the package for your version from the link in your license email. There are two files:
| File | Purpose |
|---|---|
vulnsy-1.4.0-offline.tar.gz | The package, about 300 MB |
vulnsy-1.4.0-offline.tar.gz.sha256 | Its SHA-256 checksum |
Copy both files to the server, for example with scp, then check the download and extract it:
sha256sum -c vulnsy-1.4.0-offline.tar.gz.sha256
tar xzf vulnsy-1.4.0-offline.tar.gz
cd vulnsy-1.4.0sha256sum must print vulnsy-1.4.0-offline.tar.gz: OK. The package directory contains:
| Path | Purpose |
|---|---|
install.sh | The installer. It also upgrades, backs up, restores and removes Vulnsy. |
images.tar.gz | Every container image: the Vulnsy app, PostgreSQL, Silo (file storage) and Caddy |
docker-compose.yml | Defines the services |
.env.self-hosted.example | Template for your configuration file, .env, with every setting documented |
docker/ | The configuration generator, the Caddy template and the database setup script |
VERSION, README.txt | The version, and a short guide |
SHA256SUMS | Checksums of every file, which the installer verifies |
Run the installer
From the package directory, as root:
sudo ./install.sh install \
--app-host reports.example.com \
--files-host files.reports.example.com \
--admin-email admin@example.comThe installer:
- Checks the server: the operating system, memory, free disk space, and that the ports it needs are free.
- Installs Docker Engine and the Docker Compose plugin if they are missing, after asking you.
- Verifies every file of the package against
SHA256SUMS, and stops if any file is damaged. - Copies the Compose files and itself into
/opt/vulnsy(choose another directory with--home). - Loads the container images from
images.tar.gz. - Writes
/opt/vulnsy/.envwith fresh random secrets: the session signing keys, the REST API key pepper, the AI credential encryption key, and the PostgreSQL and file storage passwords. It asks for any setting you did not pass as a flag..envis readable only by root. - Starts Vulnsy, waits until it reports healthy, and prints the address to open and the installation's instance ID.
The first administrator's password is generated and printed once, unless you choose it with --admin-password or at the prompt. It is also stored in .env as INITIAL_ADMIN_PASSWORD, and you must change it at first sign-in. Every run of the installer is logged in /opt/vulnsy/install.log.
| Flag | Sets | Default |
|---|---|---|
--app-host | APP_HOST | Required |
--files-host | FILES_HOST | files. followed by the app host |
--admin-email | INITIAL_ADMIN_EMAIL | Required |
--admin-password | INITIAL_ADMIN_PASSWORD | Generated |
--email-from | EMAIL_FROM | noreply@ followed by the app host |
--abuse-email | VULNSY_ABUSE_EMAIL | The administrator's email address |
--smtp-host | SMTP_HOST | Empty, so no email is sent |
--smtp-port | SMTP_PORT | 587 |
--smtp-user | SMTP_USER | Empty, so no SMTP authentication |
--smtp-password | SMTP_PASSWORD | Empty |
--smtp-secure | SMTP_SECURE | false. Use true for implicit TLS on port 465. |
--no-tls | COMPOSE_PROFILES | The bundled Caddy runs. With --no-tls, your own reverse proxy handles TLS (see the next step). |
--bind | BIND_ADDRESS | 127.0.0.1 |
--app-port | APP_PORT | 3000 |
--minio-port | MINIO_PORT | 9000 |
--license | VULNSY_LICENSE | Empty. A license token is bound to an instance ID, so this is for installations you already licensed. |
The installer's own options:
| Option | Effect |
|---|---|
--home DIR | Install into DIR instead of /opt/vulnsy |
--unattended | Never prompt. Every required setting must come from a flag, and confirmations are answered yes. |
--yes | Answer yes to confirmations, such as installing Docker |
--skip-docker-install | Never install Docker. Docker Engine and the Compose plugin must already be installed. |
--force | Continue on an operating system that is not supported, or with busy ports |
Run ./install.sh help for every command and option. The setting flags apply to a new installation only. If /opt/vulnsy/.env already exists, the installer keeps it, so you can run it again safely: edit .env to change a setting. See Configuration.
Back up .env now, and keep a copy with every backup. Some of its secrets cannot be changed later without side effects. See Secrets.
Choose how TLS is handled
By default, Caddy terminates TLS for both hostnames. When DNS points at the server and ports 80 and 443 are reachable from the internet, Caddy obtains Let's Encrypt certificates automatically, with no further setup.
On a private network, or to use your own certificate, edit /opt/vulnsy/docker/Caddyfile and enable one of the tls lines in both site blocks:
tls internal: Caddy issues certificates from its own local certificate authority. Install Caddy's root certificate on your users' devices. Copy it out withcd /opt/vulnsy && sudo docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt ./caddy-root.crt.tls /certs/fullchain.pem /certs/key.pem: your own certificate, which must cover both hostnames. Put the two files in/opt/vulnsy/docker/certs/.
Then apply the change:
cd /opt/vulnsy
sudo docker compose restart caddyUpgrades never replace docker/Caddyfile or docker/certs/.
Install with --no-tls, so that Caddy does not run:
sudo ./install.sh install --no-tls \
--app-host reports.example.com \
--files-host files.reports.example.com \
--admin-email admin@example.comThe app listens on 127.0.0.1:3000 and the file store's S3 API on 127.0.0.1:9000, as set by BIND_ADDRESS, APP_PORT and MINIO_PORT in .env (or --bind, --app-port and --minio-port). Configure your proxy to terminate TLS for both hostnames and to forward them:
| Hostname | Forward to |
|---|---|
| App host | http://127.0.0.1:3000 |
| Files host | http://127.0.0.1:9000 |
For both hostnames:
- Pass the original
Hostheader through unchanged. - Serve each hostname at its root, with no path prefix.
- Send the
X-Forwarded-Proto: httpsheader. - Allow request bodies of at least 50 MB. Uploads go through the app host.
If the proxy runs on another machine, install with --bind set to an address of this server that the proxy can reach, or to 0.0.0.0, and make sure that only the proxy can connect to the two ports.
Configure email
Vulnsy sends account emails (sign-in details for new users and temporary passwords) and client portal emails through your SMTP server. If you did not pass the SMTP flags, set these values in /opt/vulnsy/.env:
EMAIL_FROM=vulnsy@example.com
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=vulnsy@example.com
SMTP_PASSWORD='your-smtp-password'Use port 587 with SMTP_SECURE=false (STARTTLS), or port 465 with SMTP_SECURE=true (implicit TLS). For a relay that does not require authentication, leave SMTP_USER and SMTP_PASSWORD empty. Wrap values that contain $, # or spaces in single quotes. See Email (SMTP) for the details. Then apply the change:
cd /opt/vulnsy
sudo docker compose up -dIf SMTP_HOST is empty, Vulnsy sends no email. You can still add users, but you have to give them their temporary password yourself, and Forgot password? on the sign-in page cannot work.
Sign in and set your password
Open https://reports.example.com/login and sign in with the first administrator's email address and initial password. Vulnsy asks you to choose a new password before you continue.
No license is installed yet, so Vulnsy then shows the License page. Until a license is installed, only the sign-in page and the License page are available.
Install your license
The License page (/license) shows this installation's instance ID, which the installer also printed. Request a license token bound to it with the link in your license email, which opens vulnsy.com/offline-licensing. The token is emailed to the contact on your agreement. Paste it on the License page. Licensing covers the other ways to install it.
Once a valid license is installed, Vulnsy opens normally.
After Installing
- Add your team under Admin > Users & Access. See Permissions for what each role can do.
- Schedule backups with
sudo /opt/vulnsy/install.sh backup. See Backup & Restore. - Check the installation at any time with
sudo /opt/vulnsy/install.sh status, and follow its logs withsudo /opt/vulnsy/install.sh logs. - Optionally, remove
INITIAL_ADMIN_PASSWORDfrom/opt/vulnsy/.env. It is read only on the first start. - Keep local changes to the Compose setup in
/opt/vulnsy/docker-compose.override.yml. Upgrades replacedocker-compose.yml, but never that file.