Configuration
ReadingBat uses a dual-layer configuration system where settings can come from HOCON config files or environment variables. Environment variables take precedence when both are defined.
Configuration Layers
graph LR
A[application.conf<br>HOCON Properties] --> C{Merged Config}
B[Environment Variables] --> C
C --> D[Application]
B -.->|overrides| A
- HOCON Properties (
Propertysealed class) -- Read fromapplication.confvia Ktor's config system - Environment Variables (
EnvVarenum) -- Override HOCON values when defined
HOCON Properties
Properties are defined as singleton objects in the Property sealed class. They are
initialized from application.conf during application startup via assignProperties().
Reading Properties
fun propertyExamples() {
// Read a property with a default value
val isProduction = Property.IS_PRODUCTION.getProperty(false)
val dbmsUrl = Property.DBMS_URL.getProperty("jdbc:pgsql://localhost:5432/readingbat")
// Read a string property or null if not set
val analyticsId = Property.ANALYTICS_ID.getPropertyOrNull()
// Read a required property (throws if missing)
val apiKey = Property.RESEND_API_KEY.getRequiredProperty()
// Check if a property has been defined
val isDefined = Property.DBMS_ENABLED.isADefinedProperty()
}
Property Categories
| Category | Properties | Description |
|---|---|---|
| Content DSL | DSL_FILE_NAME, DSL_VARIABLE_NAME |
Content loading configuration |
| Site | IS_PRODUCTION, IS_TESTING, DBMS_ENABLED |
Runtime behavior flags |
| Database | DBMS_URL, DBMS_USERNAME, DBMS_PASSWORD |
PostgreSQL connection settings |
| OAuth | GITHUB_OAUTH_CLIENT_ID, GOOGLE_OAUTH_CLIENT_ID |
OAuth provider credentials |
RESEND_API_KEY, RESEND_SENDER_EMAIL |
Email sending via Resend | |
| Script Pools | JAVA_SCRIPTS_POOL_SIZE, KOTLIN_SCRIPTS_POOL_SIZE |
JSR-223 engine pool sizes |
| Monitoring | ANALYTICS_ID, PROMETHEUS_URL, GRAFANA_URL |
Observability configuration |
HOCON Configuration File
Properties are read from application.conf using Ktor's configuration system.
Here's a typical structure:
readingbat {
site {
production = false
dbmsEnabled = false
multiServerEnabled = false
contentCachingEnabled = false
googleAnalyticsId = ""
}
content {
fileName = "src/Content.kt"
variableName = "content"
}
scripts {
javaPoolSize = 5
kotlinPoolSize = 5
pythonPoolSize = 5
}
}
dbms {
driverClassName = "com.impossibl.postgres.jdbc.PGDriver"
jdbcUrl = "jdbc:pgsql://localhost:5432/readingbat"
username = "postgres"
password = ""
maxPoolSize = 10
maxLifetimeMins = 30
}
Environment Variables
Environment variables are defined in the EnvVar enum. They override HOCON config
values and are useful for deployment-specific settings.
Using Environment Variables
fun envVarExamples() {
// Check if an environment variable is defined
val hasDbUrl = EnvVar.DBMS_URL.isDefined()
// Get an environment variable with a fallback default
val dbUrl = EnvVar.DBMS_URL.getEnv("jdbc:pgsql://localhost:5432/readingbat")
// Get an environment variable or null
val maybeKey = EnvVar.IPGEOLOCATION_KEY.getEnvOrNull()
// Get a required environment variable (throws if missing)
val requiredPassword = EnvVar.DBMS_PASSWORD.getRequiredEnv()
// Boolean environment variable with default
val agentEnabled = EnvVar.AGENT_ENABLED.getEnv(false)
}
Override Pattern
The standard pattern combines both systems -- the environment variable takes precedence when defined:
fun envOverridesPropertyExample() {
// Typical pattern: environment variable overrides HOCON config value
// EnvVar takes precedence when defined; otherwise falls back to Property
val dbmsUrl =
EnvVar.DBMS_URL.getEnv(
Property.DBMS_URL.getProperty("jdbc:pgsql://localhost:5432/readingbat"),
)
val oauthClientId =
EnvVar.GITHUB_OAUTH_CLIENT_ID.getEnv(
Property.GITHUB_OAUTH_CLIENT_ID.getProperty(""),
)
}
Available Environment Variables
| Variable | Description | Sensitive |
|---|---|---|
DBMS_URL |
Database JDBC URL | Yes |
DBMS_USERNAME |
Database username | No |
DBMS_PASSWORD |
Database password | Yes |
GITHUB_OAUTH_CLIENT_ID |
GitHub OAuth client ID | No |
GITHUB_OAUTH_CLIENT_SECRET |
GitHub OAuth client secret | Yes |
GOOGLE_OAUTH_CLIENT_ID |
Google OAuth client ID | No |
GOOGLE_OAUTH_CLIENT_SECRET |
Google OAuth client secret | Yes |
RESEND_API_KEY |
Resend email API key | Yes |
RESEND_SENDER_EMAIL |
Resend sender email address | No |
IPGEOLOCATION_KEY |
IP geolocation API key | Yes |
AGENT_ENABLED |
Enable Prometheus proxy agent | No |
REDIRECT_HOSTNAME |
Hostname for redirects | No |
OAUTH_CALLBACK_URL_PREFIX |
OAuth callback URL prefix | No |
Sensitive Value Masking
Sensitive environment variables (passwords, API keys, secrets) include a
maskFunc that obfuscates their values in log output. For example,
DBMS_PASSWORD shows only the first character followed by asterisks.
Secrets
Secrets are loaded from secrets/secrets.env (not committed to version control).
The root build.gradle.kts configureSecrets() function reads this file and
injects values as environment variables into JavaExec and Test tasks.
Create your secrets file:
# secrets/secrets.env
DBMS_PASSWORD=my-database-password
GITHUB_OAUTH_CLIENT_SECRET=ghp_xxxxxxxxxxxx
GOOGLE_OAUTH_CLIENT_SECRET=GOCSPX-xxxxxxxxxxxx
RESEND_API_KEY=re_xxxxxxxxxxxx
Property Initialization
Properties are initialized during application startup in Application.module():
assignProperties()iterates throughProperty.initProperties()- Each property's
initFuncreads the HOCON config and/or environment variable assignInitialized()marks the system as ready- Reading an uninitialized property with
errorOnNonInit=truethrows an error