Blog · engineering article
From idea to production
What broke on the way to a public URL, what AI accelerated and what still needed judgement.
← Lab · Case study
A complete asset-maintenance application, designed, built, containerised, secured and deployed by one engineer, running today at assetcare.janaka.me. Java 25 and Spring Boot 4.1 behind an Angular 22 front end, Keycloak for identity, PostgreSQL 18, S3-compatible object storage, all on K3s with Helm, Traefik and Let's Encrypt on one Hetzner Cloud server. AI-assisted engineering accelerated the implementation, the investigation and the documentation; the architecture, the security decisions, the validation and the production delivery stayed engineering-led.
Why it exists
Most reference projects stop at "runs on my laptop". AssetCare was built to go the rest of the way: a real identity provider, migrations, container images that run as non-root, a Helm chart, a cloud server created from Terraform, Kubernetes, a certificate that renews itself, health probes, backups, and a public URL that a stranger can log into.
The domain is deliberately small. People own assets (a car, a boiler, a laptop), assets need maintenance on a schedule, and someone has to prove what was done and when. Small enough to hold in your head, rich enough to need every piece of a serious system.
What it demonstrates
Live demo
Open assetcare.janaka.me and use the shared demo account. It is deliberately public, holds only fictional sample data (a household and home office in Zug) and has the plain USER role.
Username: demo
Password: AssetCare-Demo-2026
Read/write demo environment, sample data only. Other visitors see and can change the same data; it is reseeded from time to time. No administrator, database or infrastructure credential is public anywhere.
If-Match).Architecture
Traefik is the only public entry point. The Angular SPA is static files behind nginx. The Spring Boot API is the only process that writes to the database. Identity is external. Attachments live in object storage. Every one of the stateful pieces can be swapped for a managed service by changing a Helm value, not code.
Each part, in one line
| Part | Role | Why this choice |
|---|---|---|
| Traefik | The only public door: routes, terminates TLS, rate-limits /api | Bundled with K3s; one IP, one certificate, several services |
| Angular 22 SPA | Static bundle on unprivileged nginx; runtime config.json rendered from the environment | One image for every environment; strict Content-Security-Policy without inline scripts |
| Spring Boot 4.1 API | Business rules, authorization policy, audit events, Problem Details errors, Liquibase at start-up | Clean architecture with the dependency direction enforced by ArchUnit |
| PostgreSQL 18 | All business data, the audit trail and idempotency keys, on a persistent volume | Managed PostgreSQL or Oracle is a values change; the migration path is documented |
| Keycloak 26 | Login, roles USER / ADMIN / AUDITOR, JWT issuance; the application never sees a password | Standard OIDC with PKCE; swapping in a corporate identity provider is configuration |
| MinIO | S3-compatible storage for attachment bytes, outside the database | The API speaks S3, so any object storage replaces it |
| cert-manager | Asks Let's Encrypt for the certificate and renews it before expiry | HTTPS without a human in the loop |
| Helm chart | One command creates or upgrades every object; every environment difference is a value | Rollback is helm rollback; enterprise switches for external DB, IdP, storage and vault secrets |
| Terraform | The server, its firewall and backups as code | The VM can be destroyed and recreated in a minute from the repository |
Technology stack
Backend
Tests: JUnit 6, AssertJ, Mockito, Testcontainers, ArchUnit. Build: Maven wrapper.
Frontend
One set of design tokens, dark mode, phone layout, command menu, skeleton loading, accessibility lint rules.
Platform and delivery
Observability profile: Prometheus, Alertmanager, Grafana, Loki, Tempo, OpenTelemetry Collector (see below for its status).
Security
Identity and authorization
401, verified on the live host on 23 September 2026.USER sees own assets, AUDITOR reads everything and writes nothing, ADMIN manages configuration and can restore or hard-delete. One AuthorizationPolicy decides; controllers never check roles.Edges and supply chain
X-Frame-Options: DENY, nosniff, a Referrer-Policy and a Permissions-Policy (observed in the response headers)./api at the Traefik ingress; the API answers 429 with Problem Details if a per-user limit is hit.If-Match (428 without it, 409 when stale). Creates accept an Idempotency-Key.Testing and CI/CD
Infrastructure and operations
Infrastructure as code
hetznercloud/hcloud) creates the CX33, a stateful cloud firewall that admits SSH from the operator's IP and HTTP/HTTPS from anywhere, a cloud-init ubuntu user with root login disabled, and nightly Hetzner server backups. The API token lives only in an environment variable.Ready and the padlock appeared.existingSecret; the chart's probes, resource requests and limits keep the whole stack inside 8 GB with headroom.Day two
/actuator/health and /actuator/info are exposed through the ingress. Metrics and everything else stay inside the cluster. A one-command health script prints OK / WARN / FAIL for pods, probes, certificate, backup age and the HTTP checks, naming the runbook section for anything wrong.pg_dump nightly at 02:30 UTC, keeps fourteen, and can upload to any S3-compatible bucket; Hetzner server backups give an off-VM copy. The restore is a script that CI rehearses on every run. Recovery objective of the reference deployment: 24 hours.Observability
Running on the live server
requestId, traceId and spanId; a request id enters with or comes back in the response./actuator/prometheus endpoint, not exposed publicly.spring-boot-starter-opentelemetry; telemetry is exported over OTLP only when a collector is present.Available as an observability profile
Prometheus, Alertmanager, the blackbox exporter, Grafana (a 22-panel dashboard in Git), Loki, Tempo and the OpenTelemetry Collector, with thirteen alert rules that each link to a runbook section. The profile starts locally with make observability and has Helm values for the cluster.
Status, honestly: it is not running on the production server. The 8 GB CX33 holds the core chart with headroom; the observability profile needs a larger node or a second server, which the project's production-gaps document records. It is architecture and configuration ready to enable, not a monitoring capability currently in operation.
The engineering journey
01 · Architecture and standards
version matrix · ADRs · definition of done · clean architecture
02 · Local environment
PostgreSQL 18 · Keycloak 26 with the realm · MinIO
03 · Data and migrations
Hibernate validates the schema; nothing is created by the ORM
04 · Authentication
roles USER · ADMIN · AUDITOR
05 · Backend and frontend
Problem Details · If-Match · Idempotency-Key · CSV export · attachments · command menu
06 · Testing and CI
unit · ArchUnit · Testcontainers · Vitest · Playwright · restore drill
07 · Containers and registry
amd64 + arm64 · Trivy · SBOM · cosign · provenance
cosign verify passes.08 · Infrastructure
CX33 · cloud firewall · cloud-init · server backups
09 · Kubernetes and Helm
one node · Traefik bundled · probes · resource limits · secrets created on the cluster
10 · HTTPS
Let's Encrypt · HTTP-01 through Traefik · certificate issued 23 September 2026
11 · Health verification
liveness UP · readiness UP · database reachable · anonymous 401 · Keycloak discovery 200
12 · Live
showcase data seeded through the API · development passwords rotated on the cluster
Verified
Checked from outside the server on 23 September 2026. The repository's own verification report follows the same rule: a check is marked as passed only when it was run.
| Check | Result |
|---|---|
GET https://assetcare.janaka.me/ | 200, valid certificate, issuer Let's Encrypt, subject assetcare.janaka.me |
| Response headers on the SPA | Content-Security-Policy, X-Frame-Options DENY, X-Content-Type-Options nosniff, Referrer-Policy, Permissions-Policy, Cache-Control no-store on the shell |
GET /api/v1/assets without a token | 401 Unauthorized |
GET /actuator/health | {"status":"UP"} with the groups dependencies, liveness, readiness |
/actuator/health/liveness, /readiness | 200, UP, UP |
/actuator/info | artifact assetcare-api, version 1.0.0, built 23 September 2026 |
/auth/realms/assetcare/.well-known/openid-configuration | 200, Keycloak serving the realm behind the same host |
/actuator/prometheus, /grafana/ | not routed: the SPA shell answers, as the ingress intends |
| GitHub Actions | ci, security and release workflows green on 23 September 2026 (run ids in the repository's path-to-production document) |
AI-assisted engineering
This project was also an experiment in how far AI-assisted engineering can accelerate a senior engineer without removing the discipline. The answer was: a long way on implementation, investigation and documentation, and not at all on the decisions that decide whether a system deserves to be in production.
What the assistant accelerated
CLAUDE.md states the non-negotiables: dependency direction, migrations only as changesets, archive instead of delete, an audit event for every write, Problem Details, authorization as a policy. Project skills (/add-feature, /add-migration, /write-adr, /architecture-review, /operate, /release) encode the playbook the assistant follows.inject() after await that froze the SPA at "Loading", Keycloak's iss parameter left in the URL.docker compose up --wait, a backups volume that had to be bound at install for helm --wait to succeed.What stayed with the engineer
What it is not
The point of the project is the honest boundary. The application code is written so that an enterprise replaces the pieces on the right without a rewrite; the reference deployment deliberately stops short of them.
Go deeper
Blog · engineering article
What broke on the way to a public URL, what AI accelerated and what still needed judgement.
Academy · course
The seven-course walkthrough of the repository, with an interactive board that assembles the system step by step.
AI · practice
The workflow behind this and the five products: what the model does, what the engineer keeps.
Showcase
A view-only public showcase. The full source is private and available on request.
Twenty-two years of enterprise Java, the current cloud-native stack, and AI used to move faster without lowering the bar. If that is the problem on your desk, my inbox is open.