KD8 — LMS Platform Modernization

After a few years of keeping KD7 healthy on an end-of-life stack, I wrote up a plan to modernize it from the ground up. It didn’t get the go-ahead at Apti, but I couldn’t stop thinking about it. So I kept building it on my own time.

KD8 is a full rebuild of a multi-tenant learning platform: Laravel 11 on PHP 8.3 instead of PHP 5.6 and Phalcon, an Angular 19+ Nx monorepo instead of AngularJS 1.7, and an isolated server for every client instead of shared ones.

Originally proposed as a platform modernization initiative at Apti; continued independently as a personal project after it did not receive organizational buy-in.

Out with the old

Every layer of the stack gets a fresh start:

  • Backend: PHP 5.6 and Phalcon 2 → Laravel 11 on PHP 8.3
  • Frontend: AngularJS 1.7 → Angular 19+ in an Nx monorepo
  • Hosting: shared servers → a Terraform-provisioned, isolated EC2 instance per client
  • Delivery: hand-configured Jenkins jobs → a GitLab CI/CD pipeline (test, build, deploy)

One client, one server, zero hands

The biggest architectural change is isolation. In KD7, every client shares the same servers and the separation happens in code. In KD8, Terraform provisions a separate EC2 instance for each client, and cloud-init bootstraps it from scratch with nobody logging in to set it up. Data isolation rolls out in phases, so the move can happen gradually instead of in one terrifying weekend.

Getting clients there is its own project, so I built custom tooling that pulls legacy KD7 data, transforms it for the new schema and loads it into KD8.

KD8 per-client infrastructure Terraform provisions a separate, isolated EC2 instance for each client. Each instance bootstraps itself with cloud-init, with no manual setup. Migration tooling extracts each client's data from the legacy KD7 platform, transforms it for the new schema and loads it into that client's KD8 instance. provisions Terraform one instance per client Client A · EC2 cloud-init, zero-touch setup Client B · EC2 cloud-init, zero-touch setup Client C · EC2 cloud-init, zero-touch setup Migration tooling extract, transform, load KD7 data legacy platform
Terraform provisions an isolated instance per client, cloud-init sets each one up automatically, and migration tooling brings each client’s KD7 data across.

Designed, not inherited

Users with a real lifecycle

Accounts move through clear states (pending, active, deactivated), gated by email activation. That’s a direct lesson from KD7, where I once found a deactivated user could reactivate themselves through a password reset. In KD8, the account lifecycle is designed in from the start instead of patched on later.

  • Role-Based Access Control
  • Laravel

Categories and groups

Each user belongs to exactly one category, and can join any number of groups for course assignment.

  • Data Architecture
  • Laravel
  • Angular
  • TypeScript

A pipeline from day one

GitLab CI tests, builds and deploys every change, so the new platform starts life with the delivery habits the old one had to have bolted on later.

  • GitLab
  • CI/CD

Claude as a research partner

I use Claude to dig through how the legacy platform actually behaves and to scaffold new work, which keeps a one-person rebuild moving at a steady clip. The architecture and the plan are mine; the AI helps me get there faster.

  • AI-Assisted Development

Looking back

Maintaining KD7 taught me where it hurts. KD8 is my chance to design those pain points out instead of working around them.

Stack & skills

  • Laravel 11
  • PHP
  • Angular 19+
  • Nx
  • Terraform
  • cloud-init
  • AWS EC2
  • GitLab CI
  • Laravel
  • Angular
  • IaC
  • GitLab
  • AWS
  • Multi-Tenant Architecture
  • Data Migration
  • Role-Based Access Control
  • TypeScript