Introduction
Cloudy Pocket is a personal budget management application that I developed entirely from scratch, first as my bachelor's final project, then as a personal project maintained and enhanced continuously. It has been in production for over two years, used by about ten users, and registration has recently been opened to the public.
This is a project I built entirely by hand, before the era of AI assistants. It represents my longest experience maintaining a production application with sensitive user data.
Architecture
The application is built on four independent repositories: a Laravel API, a React application frontend, a React admin frontend, and a React showcase site. This separation, resulting from a refactoring of a single frontend, enables independent deployments and development.
Features
- Bank account creation mirroring real accounts
- Recording all banking transactions with cross-referencing between app and bank
- Receipt management: photographing and temporarily storing receipts for accounting sessions
- Scheduler: automatic line integration and automated transfers between accounts
- Account sharing with other users (joint accounts)
- Dedicated admin frontend: metrics, account activity, account creation, connection monitoring
Security
Banking data is encrypted using AES-256-CBC. I initially implemented the encryption manually, which gave me deep understanding of symmetric and asymmetric encryption algorithms, before migrating to Laravel's native system upon realizing my implementation matched the built-in system almost exactly.
An IP/email blacklist system protects the application against malicious login attempts, with graduated sanctions based on the number of failed attempts. This system was later replicated on other professional projects.
CI/CD and Deployment
CI/CD is built on GitHub Actions, replicated across all four repositories:
- CI pipeline: tests, SAST, secret detection
- CD pipeline: automated deployments
- Daily scheduled pipeline: dependency scanning
A specific constraint from the O2Switch hosting provider led to a creative solution: since GitHub runner IPs are dynamic and unpredictable, but O2Switch requires a fixed IP for SSH connections, I set up an external VPS bastion with a fixed IP as a mandatory gateway for all deployments. This bastion is deliberately separate from my personal infrastructure for availability and network flow separation reasons.