# open-isms self-host configuration. Save as `.env` next to compose.yaml. # # Everything above "Optional" has to be filled in before the stack will start. # Full walkthrough: https://www.nisd2.eu/docs/self-hosting/installation # -------------------------------------------------------------------------- # Version # -------------------------------------------------------------------------- # `stable` follows the current release. Pin an exact version (e.g. 0.2.8) if # you would rather decide when to move. This is also how you roll back: set # the previous version here and run `docker compose up -d`. OPEN_ISMS_VERSION=stable # The revision of compose.yaml you are running. The app compares this with # what the release expects and tells you when an update also needs a change # to that file. Do not edit by hand — update it when you update the file. COMPOSE_REVISION=3 # Which optional services run. Comma-separated, no spaces. # minio bundled object store (leave out if you use real S3) # proxy Caddy on 443 with automatic HTTPS # updater the in-app update button # backup scheduled offsite backups # # Add `proxy` once your domain's DNS points at this server. It obtains real # certificates on first start, so turning it on before then fails the # certificate order rather than serving anything. COMPOSE_PROFILES=minio,backup # -------------------------------------------------------------------------- # Required # -------------------------------------------------------------------------- POSTGRES_PASSWORD= # openssl rand -base64 32 AUTH_SECRET= # openssl rand -hex 32 ERASURE_EMAIL_HASH_SALT= # The address your users actually type, including https://. AUTH_URL=https://isms.example.com NEXT_PUBLIC_APP_URL=https://isms.example.com # ── The first account, with no email at all ────────────────────────────── # Sign-up verifies the address with a one-time code. These two variables # create your account at startup instead, so a single-operator instance needs # no mail transport whatsoever. Set both, start the stack, sign in with # exactly these credentials, then delete the password line. # # Only ever creates; an address that already has an account is left alone, so # this cannot be used to reset a password. Same bcrypt hashing and the same # 8-128 character rule as the sign-up form. A second person still needs a # code, and a code still needs one of the transports below. BOOTSTRAP_ADMIN_EMAIL= BOOTSTRAP_ADMIN_PASSWORD= # Nobody else can finish registering without working email: sign-up verifies # the address with a one-time code. Two transports, pick one. # # Option A: your own SMTP relay. Setting SMTP_HOST selects it, and it wins if # a Resend key is also present. 587 upgrades with STARTTLS, 465 turns on # implicit TLS by itself. SMTP_HOST= SMTP_PORT=587 SMTP_USER= SMTP_PASSWORD= # Only to override the port pairing above. Leave unset unless your relay # disagrees with it. # SMTP_SECURE=true # Skips certificate verification, which removes the guarantee that you are # talking to the server you think you are. For an internal relay with a # self-signed certificate, on a network you control. Never over the internet. # SMTP_ALLOW_SELF_SIGNED=1 # # No relay yet? Add `mail` to COMPOSE_PROFILES above and set SMTP_HOST=mailpit # with SMTP_PORT=1025. Everything the app sends is caught in a local inbox at # http://localhost:8025 instead of being delivered. Good for evaluating, not # for running: it cannot reach a real address. # Option B: Resend, which needs an account with a verified sending domain. RESEND_API_KEY= # From address and display name, either transport. RESEND_FROM_EMAIL is the # older name and still works; MAIL_FROM_EMAIL takes precedence when both are # set. With SMTP the address is NOT optional: the app refuses to send rather # than fall back to this project's own address on a domain you do not own. MAIL_FROM_EMAIL= # What recipients see as the sender. Defaults to "NISD2", which will mean # nothing to your colleagues. MAIL_FROM_NAME= # The two messages written in a person's voice (course follow-up, activation # nudge). Defaults to a person at nisd2.eu, so set it or those two arrive # signed by a stranger. MAIL_FROM_NAME_PERSONAL= RESEND_FROM_EMAIL= # -------------------------------------------------------------------------- # Proxy profile # -------------------------------------------------------------------------- APP_DOMAIN=isms.example.com # Only with the bundled MinIO. Presigned upload URLs are signed for exactly # this host, so it has to be reachable by your users' browsers. STORAGE_DOMAIN=storage.example.com # -------------------------------------------------------------------------- # Storage # -------------------------------------------------------------------------- # Bundled MinIO: keep the minio profile on and set these three. AWS_S3_BUCKET=evidence AWS_ACCESS_KEY_ID=openisms # at least 8 characters AWS_SECRET_ACCESS_KEY= # exactly 32 bytes, base64: openssl rand -base64 32 MINIO_KMS_KEY= # The address the browser uses, and the one the server uses. Different on # purpose with bundled MinIO; identical on real S3 (leave the internal one # empty there). # # Defaults to the bundled MinIO as published by the compose file, so evidence # uploads work on a fresh install with nothing to fill in. Presigned URLs are # signed for exactly this host and the browser has to reach it, so the moment # you put the instance on a domain this becomes https://storage.example.com # or wherever MinIO actually answers. It is also what the app names in its # Content-Security-Policy, so a wrong value here blocks uploads in the browser # rather than failing on the server. AWS_S3_ENDPOINT=http://localhost:9000 AWS_S3_INTERNAL_ENDPOINT=http://minio:9000 # Real S3 instead: drop `minio` from COMPOSE_PROFILES, leave both endpoints # empty, and set AWS_S3_REGION plus your own bucket and credentials. AWS_S3_REGION=eu-north-1 # -------------------------------------------------------------------------- # Updates # -------------------------------------------------------------------------- # Not implemented yet: no application code reads this. Left here because the # compose file passes it through, and a variable that silently does nothing is # worse than one that says so. Checking for updates is manual for now, see # https://www.nisd2.eu/docs/self-hosting/updating # Planned: `notify` = one GET a day against the GitHub # releases API, no instance identifier and no telemetry. Unattended updates # are deliberately not planned for a compliance database. UPDATE_MODE=off # Only for the updater profile. openssl rand -hex 32 # Authenticates the app to the updater over the internal network. The updater # publishes no port and is not reachable from outside this stack. UPDATE_API_TOKEN= # -------------------------------------------------------------------------- # Backups # -------------------------------------------------------------------------- # Without a target, archives stay in a volume on this machine and die with it. # Full setup and the verified restore procedure: # https://www.nisd2.eu/docs/self-hosting/backup-and-restore BACKUP_S3_BUCKET= # Host only, no scheme. The scheme goes in the PROTO variable. BACKUP_S3_ENDPOINT= BACKUP_S3_ENDPOINT_PROTO=https BACKUP_S3_ACCESS_KEY= BACKUP_S3_SECRET_KEY= # Encrypts each archive with GPG before it leaves the machine. Keep this # passphrase somewhere other than this server: without it nobody can read the # archives, including you, and a passphrase stored only on the machine being # backed up protects nothing. BACKUP_PASSPHRASE= BACKUP_CRON=0 3 * * * BACKUP_RETENTION_DAYS=30 # Optional shoutrrr URL, notified only when a backup fails. BACKUP_NOTIFICATION_URL= # -------------------------------------------------------------------------- # Optional # -------------------------------------------------------------------------- # Each of these degrades exactly one feature if left empty. GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= # AI prefill in the intake forms XAI_API_KEY= # company lookup in the applicability wizard RAPIDAPI_KEY= # bearer token for the deadline and reminder cron endpoints CRON_SECRET= # your support address: reply-to on outbound mail, shown on # /email/unsubscribed, and offered in the in-product help dialog. Leave it # empty and the dialog simply shows no address. SUPPORT_EMAIL= # Cross-tenant admin tier. Most single-company installs leave this empty. PLATFORM_ADMIN_EMAILS= # Set only once you are HTTPS-only: turns on HSTS with a two-year max-age. CSP_UPGRADE_INSECURE= # -------------------------------------------------------------------------- # Network bindings # -------------------------------------------------------------------------- # Loopback by default so nothing is exposed by accident. Under the proxy # profile leave these alone. Publishing on 0.0.0.0 bypasses ufw — Docker # writes its iptables rules ahead of it — so use a provider-level firewall. APP_BIND=127.0.0.1 APP_PORT=3026 POSTGRES_BIND=127.0.0.1 POSTGRES_PORT=5432 MINIO_BIND=127.0.0.1 MINIO_PORT=9000