Self-hosting

Run Sepal yourself with Docker or a jar, and set every option it reads.

You can run Sepal on your own machine with Docker or with a jar. This page describes both, and it lists every setting Sepal reads.

Running Sepal

Sepal runs as one process backed by one SQLite database. On first start, Sepal provisions that database and applies any pending migrations. Sepal then runs a setup wizard that creates the first admin user and downloads the taxon data. SEPAL_SECRET is the only variable you have to set.

Docker

projects/app/Dockerfile builds everything Sepal needs. The build produces the frontend assets and the uberjar, and it produces a runtime image that carries SpatiaLite.

docker build -f projects/app/Dockerfile -t sepal .
docker run -d -p 3000:3000 \
    -e SEPAL_SECRET="$(openssl rand -hex 16)" \
    -v sepal-data:/root/.local/share/Sepal \
    sepal

After the container starts, open http://localhost:3000 and follow the setup wizard.

bin/smoke-test runs the same two commands against an empty volume. The script asserts that the container provisions the database, applies migrations, loads SpatiaLite, and serves HTTP.

From a jar

To run Sepal from a jar, build the uberjar and then start it with java.

bin/build-uberjar.sh
SEPAL_SECRET="$(openssl rand -hex 16)" \
    java -Duser.timezone=UTC --enable-native-access=ALL-UNNAMED \
         -jar projects/app/target/sepal.jar

Outside Docker, you also need mod_spatialite on disk. Set EXTENSIONS_LIBRARY_PATH to the directory that holds it.

Where data lives

Sepal writes the database, the thumbnail cache, and the backups under SEPAL_DATA_HOME. If you do not set SEPAL_DATA_HOME, Sepal falls back to $XDG_DATA_HOME/Sepal. If that variable is unset too, Sepal uses ~/Library/Application Support/Sepal on macOS and ~/.local/share/Sepal elsewhere.

Schema versions

Sepal records the migrations it has applied in a schema_version table inside the database. If the database is older than the minimum version the build supports, Sepal refuses to start and reports :schema-version-unsupported. A database at or above that minimum works, including one newer than the running build. Rolling back to a previous release therefore does not strand a database that has already been migrated.

Sepal applies pending migrations automatically at startup, so a database it created and has kept current is always accepted. The minimum version matters when a newer build has migrated the database that an older build now runs.

Configuration

Sepal reads the environment in one place, sepal.app.main/env-opts. The tables below list every variable Sepal reads. Sepal does not read a variable that is not listed.

Required

VariableDescription
SEPAL_SECRETMaster secret, minimum 16 characters. The session cookie key and the password reset token secret are both HKDF-derived from it, so it has no default and changing it invalidates every session.
SEPAL_SECRET has no default, and it must be at least 16 characters. Sepal derives both the session cookie key and the password reset token secret from it, so changing it signs every user out.

Server

VariableDefaultDescription
APP_DOMAINlocalhostHost used to build links in outgoing email. May include a port
APP_URL_SCHEMEhttpsScheme for those links. Set to http for an install not behind TLS
HOST0.0.0.0Jetty bind address
PORT3000Jetty port
LOG_LEVELDEBUGDEBUG, INFO, WARN or ERROR
SEPAL_DATA_HOMEplatform defaultDirectory for the database, cache and backups
BACKUP_PATH$SEPAL_DATA_HOME/backupsWhere nightly backups are written
EXTENSIONS_LIBRARY_PATHDirectory containing mod_spatialite
WFO_SYNONYM_REF_PATH$SEPAL_DATA_HOME/sepal-synonyms.dbPath to the WFO synonym reference file built by bin/build-synonym-ref.sh. Resolved whether or not a file is there yet, because it is also where the setup wizard downloads it to; with no file at the path, synonym search covers local rows only

Email

Email is optional. If you do not set SMTP_HOST, Sepal builds no mail client at all, and password resets and invitations go nowhere.

VariableDefaultDescription
SMTP_HOSTSMTP hostname. Setting it is what turns email on
SMTP_PORT587587 for STARTTLS, 465 for SSL
SMTP_USERNAMESMTP auth username
SMTP_PASSWORDSMTP auth password
SMTP_AUTHtrueWhether to authenticate
SMTP_TLSstarttlsstarttls, ssl or none
SMTP_DEBUGoffLog the SMTP conversation to stdout. Prints every address and every server reply
FORGOT_PASSWORD_EMAIL_FROMsupport@sepal.appSender for password reset emails
FORGOT_PASSWORD_EMAIL_SUBJECTSepal - Reset PasswordSubject for password reset emails
INVITATION_EMAIL_FROMnoreply@sepal.appSender for invitation emails
INVITATION_EMAIL_SUBJECTYou've been invited to SepalSubject for invitation emails

Media uploads

Media upload is optional, and any S3-compatible store works, Cloudflare R2 included. If you do not set AWS_ACCESS_KEY_ID, Sepal builds no S3 client and media upload stays off.

VariableDefaultDescription
AWS_ACCESS_KEY_IDAccess key. Setting it is what turns media upload on
AWS_SECRET_ACCESS_KEYSecret key
AWS_S3_ENDPOINTEndpoint origin, e.g. https://<accountid>.r2.cloudflarestorage.com. No bucket path: the bucket is appended
AWS_REGIONRegion used to sign requests. With R2, auto
MEDIA_UPLOAD_BUCKETmediaBucket for media uploads
MEDIA_KEY_PREFIXmedia/Prefix every media key is stored under. Must end in a slash
IMAGE_CACHE_SIZE_MB500Thumbnail cache ceiling, cached under $SEPAL_DATA_HOME/cache

Cloudflare R2 needs some specific settings:

  • Use an API token scoped to the one bucket.
  • Set AWS_REGION=auto. The region only signs requests, and R2 accepts auto for all buckets.
  • The browser uploads directly to the bucket with a presigned PUT, so the bucket needs a CORS rule that allows PUT from your app’s origin with the content-type header. Reads need no CORS rule, because the app proxies them.