Self-Hosting & Operations

One JAR. One database. Your infrastructure.

Java 25 on PostgreSQL, four deployment modes that live in the database rather than a properties file, a cluster that tells you exactly what is wrong with it, streaming backups, and updates that apply themselves and hand the restart back to systemd.

Trakr ships

In the box, on every install

  • Flyway-managed schema, applied on first run
  • A setup wizard that installs the schema and the service unit
  • Cluster registration, heartbeat and scheduler leadership
  • Backups, retention cron and path-safe downloads
  • SFTP or manual update with checksum verification
  • Migrations console, log viewer and garbage collection
You decide

Choices the product deliberately leaves open

  • Bare metal, a VM, a container or Kubernetes
  • Which node runs which deployment mode
  • Backup cron and retention, per tenant
  • Whether restart uses an external command or in-process
  • Garbage collection cadence, per category
  • Where attachments, backups and translations live on disk
You are responsible for

The four things nobody else can do for you

  • The PostgreSQL instance and its own backups
  • Verifying that a backup actually restores
  • TLS termination in front of the application
  • The encryption key — lose it and the encrypted data is gone
Layer
Trakr What it is Self-hosted · Belgium
Operational note Why it matters to you
Runtime
Java 25 · Spring Boot 4.0.6
GraalVM native-image capable
Database
PostgreSQL only
One dialect to tune, one backup story · HikariCP pooling
Schema management
Flyway · 120 migrations
Versioned, ordered, inspectable — and listed in an admin console
User interface
Server-rendered Thymeleaf
Vanilla ES modules, no SPA framework — nothing separate to build or serve
Fonts and assets
Self-hosted webfonts
No external font requests from the browser
Cryptography
BouncyCastle · AES-256-GCM
Field-level encryption of stored secrets — see Security
Documents and charts
OpenPDF · Apache POI · JFreeChart
Rendered server-side; ticket PDF uses Playwright with headless Chromium
Git
JGit · SSHJ
Embedded git server and local clones; SSHJ carries SFTP updates
Also inside Caffeine caching commonmark with GFM tables jsoup sanitiser springdoc-openapi plus a custom API explorer
Mode / ALL_IN_ONE

UI, REST and scheduler in one process

The default and, for most installs, the last topology you will ever need. One process serves the Thymeleaf interface, answers the REST API and runs the background work.

Start here. Split only when you have a reason — an isolation requirement, a network boundary, or a scheduler you want on its own hardware.

Mode / FRONTEND_ONLY

Interface only, calling an external backend

Serves the Thymeleaf UI and nothing else, pointing at an external backend URL. Useful when the presentation tier belongs in a DMZ and the API does not.

Mode / BACKEND_ONLY

REST API only, CORS-configured

Answers the API and skips the interface entirely, with CORS configured for the frontend nodes that call it. The cluster health check verifies that CORS correctness itself, so a mismatch shows up as a verdict rather than as a browser console error somebody eventually reports.

Mode / SCHEDULER_ONLY

Background work, and only background work

Mail polling, SLA sweeps, garbage collection, report delivery, materialised-view refresh and OAuth token refresh — with no user traffic competing for the same heap.

Scheduler leadership means it does not matter how many nodes could run this work: it runs once. A node checks whether the scheduler is held elsewhere before it starts.

01 / Membership

Self-registration and heartbeat

A node registers itself on startup and heartbeats from then on. Three missed beats and it is treated as dead. From the admin screen you can forget a node, probe it, or enable and disable it, and a cross-node liveness probe endpoint at /api/system/cluster/ping lets nodes check each other directly.

02 / Role assignment

Change a node's job without touching the filesystem

Assign a deployment mode to any node from the interface. An optional autoApply flag lets the node restart itself to adopt the new mode, so promoting a spare node to backend duty is a click rather than an SSH session.

One node can be designated the backup node, and the health check will tell you if none is.

03 / Scheduler leadership

Background work runs exactly once

The thing that goes wrong first in a naively clustered tracker is two nodes both polling the same mailbox, both sweeping the same SLA timers, and both emailing the same scheduled report.

Trakr resolves it with explicit leadership: a node asks whether the scheduler is held elsewhere before it takes the work. Duplicated mail, duplicated escalations and duplicated report sends simply do not arise.

04 / Health check

Nine verdicts, each with a fix

The cluster health check evaluates node registration, per-node liveness, frontend coverage, backend and API coverage, scheduler coverage, whether a backup node is present, version skew across nodes, CORS correctness, and the source of each node's mode.

Each verdict carries a remedial action. Version skew after a partial rollout is the classic silent failure in a clustered deployment; here it is a named finding on a screen, not something you deduce from an odd stack trace three days later.

The setup wizard

The wizard is reachable before anyone has authenticated — that is the whole point of a first-run wizard — and it is designed around that fact rather than in spite of it.

  1. Database connection. Enter the PostgreSQL details and test them before going further.
  2. Install schema. Flyway applies the migrations against the empty database.
  3. Create account. Tenant, organisation and administrator are created in a single transaction — there is no half-built install to clean up if something fails.
  4. App settings. An allow-list of properties merged into application.properties: Application (app.name, server.port), Security (jwt.secret, minimum 64 characters; app.encryption.key, a base64 256-bit key auto-generated if you leave it blank; and jwt.expiration), and API documentation. Secret fields are never echoed back, because the endpoint is unauthenticated by design. Database credentials and the deployment mode are deliberately excluded, and there is no email step — email is configured per organisation, tenant or platform afterwards.
  5. Service install. The wizard detects the deployment — the JAR, the Java executable, the run-as user — generates a systemd unit you can copy or download, and offers install, start, stop and status.
  6. Complete. Scheduling stays gated until setup is marked complete, so a half-configured install never starts polling mailboxes or emailing reports.
01 / The allow-list

A curated set of keys, not a free-form file editor

The configuration editor exposes a superadmin-editable allow-list of application.properties keys, grouped: General (app.name, app.base-url, app.api.base-url), Server (server.port), Database (datasource URL, username, password), Security (jwt.secret, app.encryption.key), email-to-ticket polling, translation (DeepL enabled, key, URL), storage paths for attachments, backups and translations, and software update and restart (enabled, JAR path, restart command, maximum file size).

Each key carries a label, a group, a type, a danger flag and a description. Handing an administrator a text area over the whole properties file is how installs get bricked; this is not that.

02 / Secrets

Masked, and only a "set" flag reported

Secret values are masked. The API reports whether a value is set, never what it is. Changes are validated and written to an override file rather than trampling the shipped defaults, and saving requires confirming with your password.

A public-URL helper checks that the base URL you have configured is the one your users will actually reach — the cause of most broken email links in a self-hosted deployment.

03 / Restart

A detached script, so the response flushes first

Restarting is either an allow-listed external command, giving a clean full restart, or an in-process restart. Either way Trakr writes a short detached script and lets it do the work.

That detail matters: a process that kills itself inside a request handler leaves the administrator staring at a dead connection with no idea whether the restart was accepted. Detaching means the HTTP response flushes first, and then the process goes down.

04 / System settings

A cached key/value store with hard kill-switches above it

Beyond the properties file there is a cached, typed key/value store covering subscriptions, garbage collection, the git server, Stripe keys, the base URL, agentic coding (enabled, default agent, maximum budget per run) and the whole branding family.

Two of those have a properties-level hard kill-switch that overrides the database toggle: billing and the internal git server. A feature you have disabled at the filesystem level cannot be re-enabled from a web form.

Organisation backup

One organisation — all of its data, plus its attachments
  • Scope A single organisation
  • Contents Data export + attachment files
  • Execution Asynchronous
  • Export shape JSON, streamed element by element
  • Packaging ZIP
  • On completion Email to the requester
  • Statuses pending · running · completed · failed
  • Download Path-traversal-safe, access-scoped
Streaming matters at scale: the export is written element by element rather than assembled in memory, so backing up a large organisation does not turn into a heap incident.

Tenant backup

Every organisation in the tenant, with a manifest
  • Scope All organisations in a tenant
  • Extra Top-level manifest
  • Schedule Per-tenant cron
  • Retention Per-tenant, in days
  • Due check Hourly scheduler
  • Expiry cleanup Daily
  • Listing Per tenant or per organisation
  • Delete Removes the record and the file
Download access is split between superadmin and organisation scope, and every path is checked against traversal — a backup archive is the single most valuable file on the box, and it is treated that way. Completion and failure both have their own email template.

How an update is applied

  1. Find a release. Either Trakr checks an SFTP manifest for a newer version, or you upload an update ZIP by hand — exactly one root JAR plus its migrations, nothing else accepted.
  2. Download and verify. The JAR and the SQL migrations download asynchronously, with checksum verification before anything is trusted.
  3. Stage the migrations. Applying copies the SQL migrations into the Flyway directory so the next boot picks them up in order.
  4. Exit the JVM. Trakr deliberately exits rather than attempting a hot swap. The swap script replaces the JAR and the service manager restarts the process — the same restart path you already trust for every other service on the box.
  5. Check the history. Paginated update history, the latest update, and the running version are all visible from the admin screen. The version string is validated against an allow-list, because it is interpolated into file paths.

SFTP settings — host, username, password or private key, and known hosts — are stored in the database and testable from the interface, so you find out that the credentials are wrong before you need them, not during an upgrade window.

01 / Migrations

View, execute, mark, unmark

The migrations screen lists every bundled migration file with its applied status. You can view the SQL — capped at 5 MiB so a monster migration cannot take the page down — execute a migration, mark one as applied without executing it, and unmark it to make it pending again.

Mark and unmark are the escape hatch every Flyway operator eventually needs when a migration was applied out of band. Having it in the product beats connecting to the database and editing the schema history table by hand.

02 / Garbage collection

Staggered, so runs never overlap

Application GC runs in five categories on a deliberate stagger: sessions at T, notifications at T+10 minutes, activity logs at T+20, orphaned attachments at T+30 and audit logs at T+40. You can run everything or one category, with statistics on what was collected.

Separately, a JVM garbage collection runs on a cron stored in the database and is dynamically rescheduled without a restart, with an immediate run available on demand. Both mechanisms enable, disable and toggle independently.

03 / Log viewer

Read the log without shell access

Read the application log, search it with pattern and level filters, inspect metadata — files, sizes, levels — and download or clear it.

There is also a live viewer with a level filter from TRACE to ERROR, a configurable line count, a pause control and an application-versus-system switch. Useful when the person diagnosing the problem has an admin account but not an SSH key.

04 / Demo data

A populated install in one action

Populate a chosen organisation, or add a batch of tickets to an existing project across a month span. The generator produces domain-specific project blueprints, components, coloured labels, member names, customer companies, generated summaries, descriptions, comments in both internal and customer variants, and work-log notes.

It is written by a raw-JDBC bulk writer with a pollable progress job covering projects, users, tickets, comments, work logs, links and history — and a cleanup path, so a demo environment can be reset rather than rebuilt.

Four things Trakr cannot do for you

PostgreSQL. Trakr manages its own schema through Flyway, but it does not run your database. Provisioning, patching, tuning, replication and the database's own backups are yours. PostgreSQL is the only supported engine, which at least means there is exactly one runbook to write.

Verifying that backups restore. Trakr will take an organisation or tenant backup on a cron, stream it, package it, email you on completion and delete it when retention expires. It cannot tell you that the archive restores into a working system. An untested backup is a hypothesis. Restore one into a scratch environment on a schedule you set, and find out while it does not matter.

TLS termination. Put a reverse proxy in front of the application and terminate TLS there. Trakr sets its session cookie Secure and SameSite=Strict and emits a strict Content-Security-Policy with frame-ancestors 'none' — none of which helps if the traffic reaching it is plaintext.

The encryption key. app.encryption.key is a base64 256-bit key, auto-generated at setup if you leave it blank. AES-256-GCM field encryption uses it for OAuth client secrets, LDAP bind passwords, SMTP credentials, MFA secrets and integration tokens. Lose that key and the encrypted data is unrecoverable — not by you, and not by us. Store it the way you store any other root secret, and store it somewhere that is not the machine it protects.

What do I actually need to run Trakr?

A JVM and a PostgreSQL database. Trakr is Java 25 on Spring Boot 4.0.6, with a PostgreSQL-only persistence layer managed by Flyway and pooled with HikariCP. The build is GraalVM native-image capable. The user interface is server-rendered Thymeleaf with vanilla ES modules — there is no separate SPA build to deploy — and the theme ships self-hosted webfonts, so the browser makes no external font request.

How does Trakr scale across more than one node?

Each node registers itself on startup and sends a heartbeat; a node is considered dead after three missed beats. A node's deployment mode — ALL_IN_ONE, FRONTEND_ONLY, BACKEND_ONLY or SCHEDULER_ONLY — lives in the cluster_nodes table rather than a properties file, so you assign a role from the admin screen and optionally let the node restart itself to adopt it. Scheduler leadership ensures background work runs on exactly one node.

How are backups taken and restored?

Backups come in two scopes: organisation, covering all of an organisation's data plus its attachments, and tenant, covering every organisation in the tenant with a top-level manifest. Execution is asynchronous — it streams a JSON export element by element, copies the attachment files, packages a ZIP and emails the requester on completion. Downloads are path-traversal-safe and access-scoped. Per tenant you can set enabled, a cron expression and a retention period, with an hourly scheduler deciding whether a backup is due and a daily cleanup of expired ones.

How do software updates work on a self-hosted install?

Two paths. Trakr can check an SFTP manifest for a newer release and download the JAR and SQL migrations asynchronously with checksum verification, or you can upload an update ZIP by hand — exactly one root JAR plus its migrations. Applying copies the SQL migrations into the Flyway directory and exits the JVM, so the swap script replaces the JAR and your service manager restarts the process. Update history is paginated, and the version string is checked against an allow-list because it is interpolated into file paths.

Is the setup wizard safe to expose before anyone has logged in?

The wizard is designed on the assumption that it is reachable before authentication, which is why the application settings step is an allow-list rather than a free-form properties editor, and why secret fields are never echoed back in a response. Database credentials and the deployment mode are deliberately excluded from it, and scheduling stays gated until setup is marked complete.

What am I responsible for when I self-host?

Four things: the PostgreSQL instance and its own backups; verifying that Trakr's backups actually restore, because an untested backup is a hypothesis; TLS termination in front of the application; and the encryption key. AES-256-GCM field encryption protects OAuth client secrets, LDAP bind passwords, SMTP credentials, MFA secrets and integration tokens — if you lose app.encryption.key, that data is gone, and no one can recover it for you.

Run it on your hardware

No user minimum, no cloud-only tier, no separate SPA to deploy. A JAR, a PostgreSQL database, and a setup wizard that installs the service unit for you.