A document suite application built with Spring Boot 4.0.0 and Kotlin, featuring server-side rendered views with Thymeleaf and HTMX.
epistola-suite/
├── apps/
│ └── epistola/ # Main Spring Boot application (Thymeleaf + HTMX)
├── modules/
│ └── editor/ # Rich text editor component (Vite + TypeScript)
├── build.gradle.kts # Root build configuration
└── settings.gradle.kts # Module includes
The frontend uses a server-side rendering approach:
- Thymeleaf: Template engine for rendering HTML on the server
- HTMX: For dynamic interactions without full page reloads
- Client components: Embedded JavaScript modules (like the editor) for features requiring rich client-side interactivity
-
Install mise version manager
-
Run the init script to set up your development environment:
./scripts/init.shThis will:
- Configure mise in your shell (bash/zsh) if not already done
- Install the required tool versions (Java Temurin 25, Node.js 24)
- Install Git hooks (commitlint for conventional commit validation)
- Configure SSH commit signing (if using SSH remote)
# Build the entire project
gradle build
# Run the application (requires a profile — see Authentication below)
gradle :apps:epistola:bootRun --args='--spring.profiles.active=local'A Spring profile must be set to configure authentication. Without one, the app will fail to start.
| Profile | Auth Method | Use Case |
|---|---|---|
local |
Form login (in-memory users) | Local development |
demo |
Form login (in-memory users) | K8s demo environments |
keycloak |
OAuth2/OIDC (local Keycloak) | Testing OAuth2 locally |
prod |
OAuth2/OIDC (external Keycloak) | Production |
local,keycloak |
Both form + OAuth2 | Testing both login methods |
Local development — uses in-memory users, no external dependencies:
gradle :apps:epistola:bootRun --args='--spring.profiles.active=local'
# Login: admin@local / admin or user@local / userWith local services — start PostgreSQL and Keycloak via the unified Docker Compose:
# Start PostgreSQL + Keycloak (admin console at http://localhost:8080, admin/admin)
docker compose -f apps/epistola/docker/docker-compose.yaml up -d
# Or start only PostgreSQL (sufficient for local profile)
docker compose -f apps/epistola/docker/docker-compose.yaml up -d postgres
# Run with OAuth2 only
gradle :apps:epistola:bootRun --args='--spring.profiles.active=keycloak'
# Or run with both form login and OAuth2
gradle :apps:epistola:bootRun --args='--spring.profiles.active=local,keycloak'Production — requires environment variables for your Keycloak (or any OIDC provider):
export KEYCLOAK_CLIENT_ID=epistola-suite
export KEYCLOAK_CLIENT_SECRET=<your-secret>
export KEYCLOAK_ISSUER_URI=https://keycloak.example.com/realms/epistolaSee docs/auth.md for full details on Keycloak setup, auto-provisioning, and safety guards.
Build a Docker image:
gradle :apps:epistola:bootBuildImageRun the container:
docker run --rm -p 8080:8080 epistola:0.0.1-SNAPSHOTThe application can be deployed to Kubernetes using the Helm chart published to the OCI registry.
Install from the OCI registry:
helm install epistola oci://ghcr.io/epistola-app/charts/epistola --version <version>Upgrade an existing release:
helm upgrade epistola oci://ghcr.io/epistola-app/charts/epistola --version <version>Customize the deployment with values:
helm install epistola oci://ghcr.io/epistola-app/charts/epistola \
--version <version> \
--set image.tag=1.0.0 \
--set ingress.enabled=true \
--set ingress.hosts[0].host=epistola.example.comSee charts/epistola/values.yaml for all available configuration options.
The main Spring Boot application that serves the document suite. Uses Thymeleaf for server-side HTML rendering and HTMX for dynamic interactions.
A Vite-based TypeScript editor component that builds as a library and is embedded in the main application for rich text editing functionality. The built assets are served from /editor/.
To develop the editor standalone:
cd modules/editor
npm install
npm run devThis project uses ktlint for Kotlin code style. Run the linter:
# Check code style
gradle ktlintCheck
# Auto-format code
gradle ktlintFormatgradle testTests use Testcontainers which requires Docker to be running.
This project includes a GitHub MCP server for AI-assisted issue and project management. It enables AI assistants (like Claude Code) to interact with GitHub Issues and Projects.
Setup:
pnpm run setup:github-mcpThis will:
- Open GitHub to create a fine-grained Personal Access Token with minimal permissions
- Validate the token
- Store it securely in your OS credential manager (macOS Keychain or Windows Credential Vault)
Features:
- Create and manage GitHub Issues
- Work with Pull Requests
- Track backlog in GitHub Projects
The configuration is in .mcp.json (safe to commit - no secrets stored).
The project uses GitHub Actions for CI/CD:
- Build and test runs on all PRs and main branch pushes
- Docker images are published to ghcr.io:
- On main: tagged with semantic version (based on conventional commits), SHA, and
latest - On PRs with
publishlabel: tagged with{version}-pr-{number}-{run}
- On main: tagged with semantic version (based on conventional commits), SHA, and
- Helm charts are published to ghcr.io OCI registry:
- Triggered when changes are detected in the
charts/directory - Tagged with
chart-X.Y.Z(separate versioning from the app) - Published to
oci://ghcr.io/epistola-app/charts
- Triggered when changes are detected in the
Version bumps are determined automatically from commit messages:
feat:- minor version bumpfix:- patch version bumpfeat!:orfix!:withBREAKING CHANGE- major version bump
Both the app and Helm chart follow this convention. The Helm chart version is only bumped when changes are made to the charts/ directory.
See LICENSE for details.