This is a full-stack, cloud-native web application for tracking and splitting expenses and settling debts among 2 individuals. It is built on PHP 8.4, Symfony 8, MySQL 8, and React 19, and uses event sourcing to maintain a complete audit trail of all financial transactions.
It follows DDD principles to separate the application into generic, core, and supporting domains β enforced by deptrac. This makes it easy to achieve full code coverage for the core domain.
It also showcases Kubernetes deployment using Helm.
![]() |
![]() |
![]() |
- Make
- Docker
- Kubernetes (kind, minikube, or Docker Desktop)
- Helm 3
make start # Build images, start services, and open in browser
make help # Show all available targetsVisit http://localhost:8000 in your browser.
Browser / Client
β
ββββββββββββββββββΌββββββββββββββββββ
β :8000 β :8080 β :5173 (dev)
βΌ βΌ βΌ
ββββββββββββββ ββββββββββββββββ βββββββββββββββββ
β dashboard β β web (Nginx) β β npm-dev β
β (Homer) β β β β (Vite/React/ β
ββββββββββββββ ββββββββ¬ββββββββ β TypeScript) β
β FastCGI βββββββββββββββββ
β :9000
βΌ
ββββββββββββββββββββ async ββββββββββββββββ
β app (PHP-FPM) βββββmessagesβββββΆβ worker β
β Symfony β β (Messenger) β
ββββββββββββββββββββ ββββββββββββββββ
β β
ββββββββββββββββββ¬βββββββββββββββββ
β SQL
βΌ
βββββββββββββββββββ
β db (MySQL) β
βββββββββββββββββββ
βββββββββββββββββββββββββ Backend Layers βββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Supporting β Controllers Β· Auth Β· Repositories Β· Async β
β β Normalizers Β· Instrumentation Β· EventListeners β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Core β ExpenseTracker Β· Calculator β
β (Domain) β Event Sourcing Β· Expenses Β· Compensation β
ββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Generic β Symfony Β· Doctrine Β· Twig Β· DomPDF Β· Monolog |
| β PhpParser Β· phpDocumenter Β· OpenAPI |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Supporting depends on Core & Generic Β· Core has no deps
# Build production images
make prod
# Deploy to cluster
helm install app ./helm
# or:
helm upgrade --install app ./helm
# Watch pods come up
kubectl get pods -w
# View logs for all pods with the PHP label (app + worker)
kubectl logs -f -l technology=php
# Access the application via NodePort:
# Link: http://localhost:30190
# Or configure port forwarding via:
kubectl port-forward svc/app-split-fairly-web 8080:80
# Link: http://localhost:8080ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Kubernetes Cluster β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β
β β nginx (web) β β app (PHP-FPM) β β worker β β
β β Deployment β β Deployment β β (Messenger) β β
β β NodePort:30190 β β Pod Γ 1 β β Pod Γ 1 β β
β β Pods Γ 1 ββββ FastCGI :9000 β β One-shot pattern β β
β ββββββββββββββββββββ ββββββββββββββββββββ ββββββββββββββββββββ β
β β β β β
β β Serves SPA β Business logic β Async tasks β
β β EasyAdmin β API endpoints β from queue β
β β Static assets β Session mgmt (DB) β β
β β β Event sourcing (DB) β β
β β β β β
β βββββββββββββ¬ββββββββββββ΄ββββββββββββ¬ββββββββββββ β
β β β β
β ββββββββΌββββββββββββββββββββββββΌβββ β
β β MySQL StatefulSet β β
β β PVC Storage (8Gi) β β
β β - Event store β β
β β - Sessions β β
β β - Application data β β
β βββββββββββββββββββββββββββββββββββ β
β β³ β
β β init Job β
β ββββββββ΄βββββββ β
β β db-init β β
β β (one-time) β β
β βββββββββββββββ β
β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Grafana Alloy (k8s-monitoring) β β
β β - Collects logs from all pods β β
β β - Metrics collection & forwarding β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Components:
- nginx (web): Serves React SPA frontend, static assets, EasyAdmin UI. Proxies API requests to PHP-FPM via FastCGI.
- PHP-FPM (app): Symfony backend handling business logic, API endpoints, and session management. Stores sessions in MySQL.
- Worker: Processes async jobs via Messenger with one-shot pattern (processes single message per pod lifecycle, then restarts for fresh environment).
- MySQL: Persistent data storage with StatefulSet and PVC. Stores event sourcing audit trail, sessions, and application data.
- db-init Job: One-time database initialization (schema, fixtures, migrations).
- Grafana Alloy: Log collection and forwarding for observability across all cluster components.


