Skip to content

Database

PostgreSQL is the only supported database for Kalendee: JDBC settings, the Hikari pool, startup Flyway migrations, version requirements, and connection troubleshooting.

Kalendee stores all state in PostgreSQL. There is no SQLite, MySQL, or other backend: the code connects with the PostgreSQL JDBC driver, Exposed, and startup Flyway migrations written for PostgreSQL. Any non-PostgreSQL JDBC URL is outside the supported configuration.

Connection keys

KeyTypeDefaultEnv fallbackMeaning
database.urlstringjdbc:postgresql://127.0.0.1:5432/kalendeeKALENDEE_DATABASE_URLJDBC URL. May embed credentials in the userinfo section.
database.userstringkalendee (or parsed from the URL)KALENDEE_DATABASE_USERRole used to connect.
database.passwordstringrequiredKALENDEE_DATABASE_PASSWORDPassword. An empty value is allowed for trust/peer auth.
database.migrationsstringclasspath:db/migrationFlyway location. Only needed for tests or external migration directories.

The password has no default and startup fails without it:

KALENDEE_DATABASE_PASSWORD is required (set an empty value for trust auth)

Setting KALENDEE_DATABASE_PASSWORD= (empty) satisfies the requirement, which is what you want behind trust/local peer authentication.

Credentials embedded in the JDBC URL

If the URL starts with jdbc:postgresql:// and contains a user:password@ userinfo segment, the server splits it off and rewrites the URL. For example:

Input database.urlRewritten JDBC URLParsed userParsed password
jdbc:postgresql://alice:s3cret@db:5432/kalendeejdbc:postgresql://db:5432/kalendeealices3cret
jdbc:postgresql://db:5432/kalendeeunchangednonenone
jdbc:postgresql://alice@db:5432/kalendeejdbc:postgresql://db:5432/kalendeealicenone

Resolution order:

  1. database.user if set, otherwise the URL user, otherwise kalendee.
  2. database.password if set, otherwise the URL password; if neither exists, startup fails.

Embedding credentials in the URL is supported but not recommended — prefer the dedicated keys so the password can come from the environment. URLs that do not begin with jdbc:postgresql:// are passed through untouched.

Connection pool (HikariCP)

The pool is created in code and is not configurable from HOCON; only the URL, user, and password come from configuration:

SettingValue
maximumPoolSize10
minimumIdle2
autoCommitfalse
transactionIsolationTRANSACTION_REPEATABLE_READ

Size PostgreSQL’s max_connections for at least 10 connections per Kalendee instance, plus whatever else shares the server. If you run multiple app replicas, budget 10 × replicas connections against the same database.

Migrations

Flyway runs automatically on startup, before the server accepts traffic. It creates the flyway_schema_history table and applies any migration whose version is newer than what the database has recorded. Migrations live in server/src/main/resources/db/migration and are named V<n>__<description>.sql, from V1__calendars_and_events.sql through V19__discord_event_routes.sql.

  • Migrations are forward-only and must not be edited after release.
  • Upgrading the image applies new migrations on the next boot; see Backups and upgrades.
  • The configured database.migrations location is used verbatim if it starts with classpath: or filesystem:; any other value is prefixed with filesystem:. The default classpath:db/migration reads the migrations bundled in the jar.

Required PostgreSQL version

The repository does not perform a version check in code; the supported target is whatever the project ships and tests against. docker-compose.yml and docker-compose.dev.yml both use postgres:17-alpine, and the bundled driver is PostgreSQL JDBC 42.7.7.

For a self-managed server, PostgreSQL 17 is the tested baseline. Newer major versions generally work because the schema uses standard SQL types (UUID, TIMESTAMPTZ, BOOLEAN, TEXT, BIGINT), but the project only guarantees the version used in its images.

Troubleshooting

SymptomLikely causeFix
KALENDEE_DATABASE_PASSWORD is required at startupNo password key and no password in the URL.Set KALENDEE_DATABASE_PASSWORD (empty is allowed for trust auth).
Connection refused / Connection to ... refusedWrong host, port, or database not started.Verify database.url; in compose the host is the postgres service name, not localhost. For a host-run server use 127.0.0.1.
FATAL: password authentication failedCredentials mismatch.Ensure KALENDEE_DATABASE_PASSWORD equals POSTGRES_PASSWORD, and KALENDEE_DATABASE_USER matches POSTGRES_USER.
FATAL: database "kalendee" does not existDB not created.Create it or set POSTGRES_DB. In compose the postgres service creates it from the env.
Flyway Validate failed / checksum mismatchA released migration file was edited, or the DB was migrated by a different version.Never edit applied migrations; restore from backup and re-run the correct version.
Flyway relation already existsSchema created outside Flyway or a partially applied migration.Inspect flyway_schema_history; reconcile the schema instead of blindly rerunning.
too many connectionsPool (10 per instance) times replicas exceeds max_connections.Raise max_connections or reduce replicas.

Verify connectivity independently of the app:

psql "postgresql://kalendee:password@127.0.0.1:5432/kalendee" -c 'select 1'

Health check once the server is up:

curl -fsS http://127.0.0.1:8080/api/v1/health   # {"status":"ok"}

The health route calls store.ping(), so it fails if the database is unreachable.

Edit this page on GitHub