Deployment

UniTrack builds an OCI container image with Cloud Native Buildpacks and ships sample Docker Compose stacks for both H2 and PostgreSQL.

1. Building a container image

The docker Maven profile binds Spring Boot’s build-image goal to the package phase:

./mvnw -Pdocker -pl unitrack-web package

This produces a unitrack:<version> image without a Dockerfile. The image name and whether to publish are controlled by the docker.image.name and docker.publish properties. For cross-architecture builds, set the platform:

./mvnw -Pdocker -pl unitrack-web package \
  -Dspring-boot.build-image.imagePlatform=linux/amd64
Buildpack images run as a non-root user. When mounting a host volume for H2, make sure the data directory is writable by that user — the sample H2 stack does this with a small init container.

2. Compose: H2 (self-contained)

deploy/compose.h2.yaml runs the app on embedded H2 with a persistent volume — no external database. It also enables the demo seeder and sets the admin password, making it ideal for evaluation:

docker compose -f deploy/compose.h2.yaml up -d

A mem_limit caps the container so the JVM heap is sized sensibly on small hosts. Copy deploy/.env.example to .env to override ports, memory and passwords.

3. Compose: PostgreSQL

deploy/compose.postgres.yaml runs the app together with a PostgreSQL container for a more production-like setup (no demo data):

docker compose -f deploy/compose.postgres.yaml up -d

4. Remote deploy over SSH

scripts/deploy-remote.sh builds the image locally and ships it to a remote Docker host over an SSH connection (docker save | docker -H ssh://… load), then brings up the chosen Compose stack and health-checks it — handy when the build host and the deploy host differ:

scripts/deploy-remote.sh --host [email protected] --stack h2 --port 8081
Flag Meaning

--host

SSH target user@host of the remote Docker daemon.

--stack

h2 or postgres.

--port

Host port to expose.

--platform

Image platform, e.g. linux/amd64.

--project

Compose project name.

--no-build

Reuse the already-built local image.

5. Kubernetes (Helm)

deploy/helm/unitrack is a self-contained Helm chart — the app plus an optional bundled PostgreSQL (postgresql.enabled, on by default; turn it off to use an external database). It needs no external chart dependencies. See deploy/helm/unitrack/README.md for the full value reference.

5.1. Publish the image

The cluster pulls the app from a registry, so publish there instead of building locally. Two ways to build:

Podman / Docker (recommended; works under rootless Podman): build the jar, then build deploy/Containerfile (a JRE + fat-jar image) and push:

podman login registry.example.com:5000
./mvnw -pl unitrack-web -am package -DskipTests
podman build -t registry.example.com:5000/unitrack:0.1.0-SNAPSHOT -f deploy/Containerfile unitrack-web/target
podman push --tls-verify=false registry.example.com:5000/unitrack:0.1.0-SNAPSHOT

Cloud Native Buildpacks: the docker Maven profile builds a layered image without a Dockerfile, but needs a real Docker daemon (it fails under rootless Podman):

docker login registry.example.com:5000
./mvnw -Pdocker -pl unitrack-web -am package \
  -Ddocker.image.name=registry.example.com:5000/unitrack:0.1.0-SNAPSHOT \
  -Ddocker.publish=true

For GHCR, use …/unitrack:<tag> under ghcr.io/<owner> and log in to ghcr.io. scripts/deploy-k8s.sh automates either path via --builder podman (default) or --builder buildpacks.

5.2. Install with bundled PostgreSQL

helm upgrade --install unitrack deploy/helm/unitrack \
  -n unitrack --create-namespace \
  --set image.repository=registry.example.com:5000/unitrack \
  --set image.tag=0.1.0-SNAPSHOT

The database password is auto-generated on first install and persisted in the chart Secret across upgrades. The chart force-sets SPRING_DOCKER_COMPOSE_ENABLED=false (the app enables Compose lifecycle for local dev), wires liveness/readiness to the actuator probe endpoints, and adds a generous startup probe so Flyway migrations don’t trip liveness on first boot.

When the cluster’s default StorageClass uses NFS with all_squash, PostgreSQL initdb can fail its permission checks. Point the DB volume at a node-local class instead, e.g. --set postgresql.persistence.storageClass=local-path.

5.3. Install against an external database

helm upgrade --install unitrack deploy/helm/unitrack \
  -n unitrack --create-namespace \
  --set postgresql.enabled=false \
  --set externalDatabase.url='jdbc:postgresql://db:5432/unitrack' \
  --set externalDatabase.user=unitrack \
  --set externalDatabase.existingSecret=unitrack-db

5.4. One-shot helper

scripts/deploy-k8s.sh builds + publishes the image, runs helm upgrade --install, waits for the rollout and runs helm test — the Kubernetes counterpart to scripts/deploy-remote.sh:

scripts/deploy-k8s.sh --registry registry.example.com:5000 --namespace unitrack

For a specific cluster (private registry, ingress host, storage class), capture those overrides in your own values file and pass it with --values my-values.yaml.

6. Health & metrics

Actuator is exposed with health, info and metrics, and liveness/readiness probes are enabled — wire these into your orchestrator’s health checks.