ScanopyScanopy

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:

  1. Built-in defaults
  2. A .env file in the server's working directory (or any parent directory)
  3. SCANOPY_* environment variables
  4. 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/scanopy

systemd (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:60072

Reload after editing: sudo systemctl restart scanopy-server.

Command-line (for binary builds):

./scanopy-server --server-port 60072 --database-url postgresql://...

Parameter Reference

Environment VariableCLI FlagDefaultDescription
SCANOPY_SERVER_PORT--server-port60072Port the server listens on
SCANOPY_LOG_LEVEL--log-levelinfoLogging verbosity: trace, debug, info, warn or error
SCANOPY_RUST_LOG--rust-logNoneLog filter for dependencies, in RUST_LOG syntax. Empty uses the log level for everything
SCANOPY_DATABASE_URL--database-urlpostgresql://postgres:password@localhost:5432/scanopyPostgreSQL connection string
SCANOPY_INTEGRATED_DAEMON_URL--integrated-daemon-urlNoneURL 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-cookiesfalseMark session cookies Secure, so browsers send them over HTTPS only. Set it when the UI is served over HTTPS
SCANOPY_DISABLE_REGISTRATION--disable-registrationfalseTurn off sign-up for new users
SCANOPY_DISABLE_PASSWORD_LOGIN--disable-password-loginfalseTurn off email and password login, leaving OIDC as the only way to sign in
SCANOPY_DISABLE_UPDATE_CHECK--disable-update-checkfalseStop 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-usernameNoneUsername for the SMTP relay
SCANOPY_SMTP_PASSWORD--smtp-passwordNonePassword for the SMTP relay
SCANOPY_SMTP_EMAIL--smtp-emailNoneSender address for email Scanopy sends over SMTP
SCANOPY_SMTP_RELAY--smtp-relayNoneSMTP relay host, for example smtp.gmail.com
SCANOPY_SMTP_PORT--smtp-port465SMTP relay port. 465 uses implicit TLS; any other port uses STARTTLS
SCANOPY_SMTP_MAX_PER_SECOND--smtp-max-per-second10Most 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-emailfalseHold 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-arnNoneARN of the SNS topic that delivers SES bounce and complaint notifications
SCANOPY_AWS_REGION--aws-regionNoneAWS 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-urlhttp://localhost:60072URL users reach the server at. Used in password reset links, invite links and webhooks
SCANOPY_CLIENT_IP_SOURCE--client-ip-sourceNoneWhere 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-providersNoneOIDC providers users can sign in with
SCANOPY_METRICS_TOKEN--metrics-tokenNoneBearer token the Prometheus metrics endpoint requires
SCANOPY_TRUSTED_CA_BUNDLE--trusted-ca-bundleNonePEM file of extra CA certificates to trust for outbound TLS, in addition to the bundled roots
SCANOPY_WEB_EXTERNAL_PATH--web-external-pathNoneServe 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-keyNoneLicense key for the commercial edition. Omit it for the community edition
SCANOPY_NON_PRODUCTION--non-productionfalseMark 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-emailNoneEmail 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-overrideNoneDays 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_IPS limits 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_KEY enables the Commercial Edition.
  • SCANOPY_SNAPSHOT_RETENTION_DAYS_OVERRIDE sets how long topology snapshots are kept.
  • SCANOPY_TRUSTED_CA_BUNDLE is 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=587

Microsoft 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:ro

The 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> and SCANOPY_<NAME>_FILE stops the server at startup with an error naming the variable. Entries in a .env file count.
  • A _FILE path 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_url

The 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 config

Session Security

Secure Cookies

Important: Enable secure cookies when running Scanopy behind HTTPS.

environment:
  - SCANOPY_USE_SECURE_SESSION_COOKIES=true

Effect:

  • true: Cookies marked as Secure, only sent over HTTPS
  • false: 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/24

Reference 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 config

Running 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.

On this page