Server Configuration
Configuration options for self-hosted Scanopy servers.
Configuration reference for self-hosted Scanopy server deployments.
Configuration Methods
Every setting is an environment variable prefixed with SCANOPY_. That is the primary interface, and it works the same however you run the server. Each setting in the parameter reference also has a command-line flag; the flag wins when both are set.
Sources are layered, later ones overriding earlier:
- Built-in defaults
- A
.envfile in the server's working directory (or any parent directory) SCANOPY_*environment variables- Command-line flags
Docker Compose — the published docker-compose.yml reads a .env file placed beside it and passes everything in it to the server, so most settings need no change to the compose file. The file is optional; Compose starts without one. To set a variable in the compose file itself, add it to the server service:
environment:
- SCANOPY_SERVER_PORT=60072
- SCANOPY_DATABASE_URL=postgresql://postgres:password@db:5432/scanopysystemd (for native installs) — keep them in an EnvironmentFile so secrets stay out of the unit file:
[Service]
WorkingDirectory=/opt/scanopy
EnvironmentFile=/etc/scanopy/server.env
ExecStart=/opt/scanopy/scanopy-server# /etc/scanopy/server.env — chmod 600
SCANOPY_DATABASE_URL=postgresql://scanopy:password@localhost:5432/scanopy
SCANOPY_PUBLIC_URL=http://your-server:60072Reload after editing: sudo systemctl restart scanopy-server.
Command-line (for binary builds):
./scanopy-server --server-port 60072 --database-url postgresql://...Parameter Reference
| Environment Variable | CLI Flag | Default | Description |
|---|---|---|---|
SCANOPY_SERVER_PORT | --server-port | 60072 | Port the server listens on |
SCANOPY_LOG_LEVEL | --log-level | info | Logging verbosity: trace, debug, info, warn or error |
SCANOPY_RUST_LOG | --rust-log | None | Log filter for dependencies, in RUST_LOG syntax. Empty uses the log level for everything |
SCANOPY_DATABASE_URL | --database-url | postgresql://postgres:password@localhost:5432/scanopy | PostgreSQL connection string |
SCANOPY_INTEGRATED_DAEMON_URL | --integrated-daemon-url | None | URL the server uses to reach the integrated daemon. The published Docker Compose file sets it to http://host.docker.internal:60073 |
SCANOPY_USE_SECURE_SESSION_COOKIES | --use-secure-session-cookies | false | Mark session cookies Secure, so browsers send them over HTTPS only. Set it when the UI is served over HTTPS |
SCANOPY_DISABLE_REGISTRATION | --disable-registration | false | Turn off sign-up for new users |
SCANOPY_DISABLE_PASSWORD_LOGIN | --disable-password-login | false | Turn off email and password login, leaving OIDC as the only way to sign in |
SCANOPY_DISABLE_UPDATE_CHECK | --disable-update-check | false | Stop checking GitHub for new Scanopy releases. The check is an anonymous request to api.github.com twice a day; with it off, admins see no banner when an upgrade is out |
SCANOPY_SMTP_USERNAME | --smtp-username | None | Username for the SMTP relay |
SCANOPY_SMTP_PASSWORD | --smtp-password | None | Password for the SMTP relay |
SCANOPY_SMTP_EMAIL | --smtp-email | None | Sender address for email Scanopy sends over SMTP |
SCANOPY_SMTP_RELAY | --smtp-relay | None | SMTP relay host, for example smtp.gmail.com |
SCANOPY_SMTP_PORT | --smtp-port | 465 | SMTP relay port. 465 uses implicit TLS; any other port uses STARTTLS |
SCANOPY_SMTP_MAX_PER_SECOND | --smtp-max-per-second | 10 | Most emails this server sends per second. Set it below your relay's sending limit; Amazon SES production accounts start at 14 per second. Release announcements use half of it |
SCANOPY_HOLD_RELEASE_EMAIL | --hold-release-email | false | Hold the release email for the version this server runs. The email goes out on the first start without this set. The release notes still show in the app |
SCANOPY_EMAIL_FEEDBACK_SNS_TOPIC_ARN | --email-feedback-sns-topic-arn | None | ARN of the SNS topic that delivers SES bounce and complaint notifications |
SCANOPY_AWS_REGION | --aws-region | None | AWS region of the Amazon SES account. Signup validation calls SES in this region, and the SNS topic ARN and an SES SMTP relay are checked against it |
SCANOPY_PUBLIC_URL | --public-url | http://localhost:60072 | URL users reach the server at. Used in password reset links, invite links and webhooks |
SCANOPY_CLIENT_IP_SOURCE | --client-ip-source | None | Where the server reads the client IP from when it sits behind a reverse proxy, for accurate IPs in auth event logs. Unset uses the connecting address |
SCANOPY_OIDC_PROVIDERS | --oidc-providers | None | OIDC providers users can sign in with |
SCANOPY_METRICS_TOKEN | --metrics-token | None | Bearer token the Prometheus metrics endpoint requires |
SCANOPY_TRUSTED_CA_BUNDLE | --trusted-ca-bundle | None | PEM file of extra CA certificates to trust for outbound TLS, in addition to the bundled roots |
SCANOPY_WEB_EXTERNAL_PATH | --web-external-path | None | Serve the web UI from this directory instead of the copy compiled into the binary. The Docker image sets it to /app/static. If the directory has no readable index.html, the server logs a warning and serves the compiled-in UI |
SCANOPY_LICENSE_KEY | --license-key | None | License key for the commercial edition. Omit it for the community edition |
SCANOPY_NON_PRODUCTION | --non-production | false | Mark this server as non-production (staging, testing or disaster recovery). It takes a non-production slot on the license key and shows a banner saying so |
SCANOPY_SERVER_ADMIN_CONTACT_EMAIL | --server-admin-contact-email | None | Email address shown to users who can't create an organization because the server has reached its organization limit |
SCANOPY_SNAPSHOT_RETENTION_DAYS_OVERRIDE | --snapshot-retention-days-override | None | Days to keep topology snapshots before pruning them. Unset uses the plan's retention window |
Related settings documented on their own pages:
SCANOPY_EXTERNAL_SERVICE_PROMETHEUS_ALLOWED_IPSlimits the metrics endpoint to a comma-separated list of IPs and CIDRs. See Prometheus setup.- OIDC providers are configured in
oidc.toml. See OIDC. SCANOPY_LICENSE_KEYenables the Commercial Edition.SCANOPY_SNAPSHOT_RETENTION_DAYS_OVERRIDEsets how long topology snapshots are kept.SCANOPY_TRUSTED_CA_BUNDLEis covered in Trusted CA Certificates.
Any variable in this table can also be read from a file. See Secrets from Files.
Integrated Daemon URL
The server calls this URL once, when the first user registers, to hand the integrated daemon its site and API key. The integrated daemon runs in its own container with host networking, so the default compose points the server at the Docker host:
services:
server:
environment:
SCANOPY_INTEGRATED_DAEMON_URL: http://host.docker.internal:60073
extra_hosts:
- "host.docker.internal:host-gateway"If you change the daemon's port, update the URL to match. Leave the variable unset to run without an integrated daemon.
SMTP Configuration
SMTP settings enable email-based features such as password reset.
All SMTP parameters are optional. If not configured, email features will be disabled. Relay, username, password and email must all be set together — with any of them missing, email stays disabled.
Every SMTP connection is encrypted, and there is no setting to turn encryption on or off. SCANOPY_SMTP_PORT selects which kind: the default of 465 is implicit TLS, and any other port uses STARTTLS, which is what submission ports such as 587 and 25 expect. STARTTLS is required rather than opportunistic — if the upgrade fails, the message is not sent.
Set SCANOPY_SMTP_EMAIL to the same mailbox as SCANOPY_SMTP_USERNAME unless that account has been granted permission to send as another address. Most providers reject a mismatch.
Configuration:
environment:
- SCANOPY_SMTP_RELAY=smtp.gmail.com
- [email protected]
- SCANOPY_SMTP_PASSWORD=your-app-password
- [email protected]
- SCANOPY_SMTP_PORT=587Microsoft 365
Microsoft 365 accepts authenticated SMTP only on port 587 with STARTTLS; it does not listen on 465, so the default port fails to connect.
environment:
- SCANOPY_SMTP_RELAY=smtp.office365.com
- SCANOPY_SMTP_PORT=587
- [email protected]
- SCANOPY_SMTP_PASSWORD=your-password
- [email protected]Microsoft 365 also requires tenant-side configuration that Scanopy cannot perform:
- SMTP AUTH must be enabled for the mailbox. Microsoft disables it by default for organizations created after January 2020.
- Security defaults must be off, and the account must not require multi-factor authentication. Microsoft documents both as incompatible with SMTP AUTH.
- The mailbox needs a Microsoft 365 license.
Microsoft is retiring basic authentication for SMTP AUTH. It is disabled by default for existing tenants from the end of December 2026, though an administrator can re-enable it, and it is unavailable to tenants created after that date. Scanopy authenticates with a username and password; OAuth is not supported.
See Microsoft's SMTP AUTH documentation for the tenant-side steps.
When a message cannot be sent, the mail server's reply is recorded in the server log. It names the specific reason — a rejected password, a tenant policy, or a sender the account may not send as — which the failure alone does not tell you.
Trusted CA Certificates
By default the server trusts only the public root certificates built into it. SCANOPY_TRUSTED_CA_BUNDLE points to a PEM file of additional CA certificates, which are trusted alongside the built-in roots. Use it when a service the server connects to has a certificate from a private or internal CA.
The bundle applies to the server's connections to:
- OIDC identity providers (discovery, keys and token exchange)
- the SMTP relay
- daemons, for the integrated daemon and daemons in ServerPoll mode
- the license server, for commercial check-ins
environment:
- SCANOPY_TRUSTED_CA_BUNDLE=/etc/scanopy/ca-bundle.pem
volumes:
- /etc/ssl/certs/ca-certificates.crt:/etc/scanopy/ca-bundle.pem:roThe file can hold several certificates, and other PEM sections such as keys or comments are ignored. The server reads it once at startup. If the file is missing, unreadable, contains no certificates, or holds a certificate that cannot act as a CA, the server does not start, and the error names the path. Leaving the variable unset changes nothing.
Secrets from Files
Any SCANOPY_ variable can be read from a file instead: set SCANOPY_<NAME>_FILE to the file's path. This is the convention Docker secrets, Kubernetes and Portainer use for mounted secrets.
- A trailing newline in the file is trimmed. Other whitespace is kept.
- Setting both
SCANOPY_<NAME>andSCANOPY_<NAME>_FILEstops the server at startup with an error naming the variable. Entries in a.envfile count. - A
_FILEpath that is missing or unreadable stops the server, and the error names the variable and the path. The file's contents are never logged.
With Docker secrets, Postgres's own image reads POSTGRES_PASSWORD_FILE. The database URL embeds that password, so put the full URL in a second secret:
services:
postgres:
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/postgres_password
secrets:
- postgres_password
server:
environment:
SCANOPY_DATABASE_URL_FILE: /run/secrets/database_url
secrets:
- database_url
secrets:
postgres_password:
file: ./secrets/postgres_password
database_url:
file: ./secrets/database_urlThe published docker-compose.yml includes this example, commented out.
UI Configuration
The UI automatically uses the hostname and port from your browser's address bar to reach the API.
No configuration needed for standard deployments where UI and API are on the same domain.
Advanced: API on Different Domain
If your API server is on a different hostname than where the UI is served (uncommon):
Rebuild the Docker image with build arguments:
docker build \
--build-arg PUBLIC_SERVER_HOSTNAME=api.example.com \
--build-arg PUBLIC_SERVER_PORT=8080 \
-f backend/Dockerfile \
-t scanopy-server:custom \
.Then use your custom image in docker-compose:
scanopy-server:
image: scanopy-server:custom
# ... rest of configSession Security
Secure Cookies
Important: Enable secure cookies when running Scanopy behind HTTPS.
environment:
- SCANOPY_USE_SECURE_SESSION_COOKIES=trueEffect:
true: Cookies marked as Secure, only sent over HTTPSfalse: Cookies sent over HTTP and HTTPS
Environment Files
For easier management, use .env files:
Create .env:
# Database
SCANOPY_DATABASE_URL=postgresql://postgres:password@db:5432/scanopy
# Server
SCANOPY_SERVER_PORT=60072
SCANOPY_PUBLIC_URL=http://your-domain.com:60072
SCANOPY_LOG_LEVEL=info
SCANOPY_USE_SECURE_SESSION_COOKIES=false
# SMTP (optional - for password reset and notifications)
SCANOPY_SMTP_RELAY=smtp.gmail.com
SCANOPY_SMTP_USERNAME=[email protected]
SCANOPY_SMTP_PASSWORD=your-app-password
SCANOPY_SMTP_EMAIL=[email protected]
SCANOPY_SMTP_PORT=587
# Daemon
SCANOPY_INTEGRATED_DAEMON_URL=http://host.docker.internal:60073
# Metrics (optional - for Prometheus scraping)
SCANOPY_METRICS_TOKEN=your-secure-metrics-token
SCANOPY_EXTERNAL_SERVICE_PROMETHEUS_ALLOWED_IPS=192.168.1.0/24Reference in docker-compose.yml: the published compose file already does this, so a .env beside it is picked up as it is. In a compose file of your own:
services:
scanopy-server:
image: ghcr.io/scanopy/scanopy/server:latest
env_file:
- path: .env
required: false
# ... rest of configRunning without Docker: the server loads a .env file from its working directory (or a parent) automatically, so dropping one next to the binary works with no extra wiring. For a systemd service, prefer EnvironmentFile= — it doesn't depend on the working directory and keeps the file's permissions explicit.