A platform any organisation must be able to host on its own
Our project started as a platform to help students and high-school pupils learn computer science. After several days of discussion, we refocused it on a free e-learning platform that any organisation, whether a school, a company or a non-profit, could take over, adapt and host itself to keep control of its data. We also decided to host the main instance of the platform ourselves, to publish our own computer science courses on it and show that the whole thing was viable.
This shift raised the stakes considerably on the infrastructure side. The question was no longer only whether the application worked, but whether a third party could deploy it, update it and monitor it without us, since a solution presented as free and sovereign has to be able to live on its user's server without depending on a team that knows the tricks to keep it running. My role was therefore to build around the application code the chain that tests it automatically, deploys it reproducibly, secures it and observes it in production, while keeping it modular and portable.
Three Docker Compose services with monitoring on top
The application runs as three services orchestrated by Docker Compose: a PostgreSQL database, a Java Spring Boot backend and a Next.js frontend, each built in several stages to keep only a lightweight image, with the backend waiting for the database to be ready before starting. In production, Caddy sits in front of them as a reverse proxy, obtains and renews Let's Encrypt certificates on its own, adds security headers such as HSTS or clickjacking protection, and writes JSON access logs that the monitoring stack can use.
The server is a VPS that I rented and configured, with a domain name and a DNS zone pointing to it, a prerequisite for the certificate authority to issue the certificate. An install script turns a blank machine into a running one with a single command: it installs Docker, hardens the host by opening only the SSH and web ports and installing fail2ban, then generates random secrets. The script can be replayed without overwriting anything and never rewrites an existing configuration file.
Monitoring comes as an overlay of six containers. Prometheus collects system, container and JVM metrics, Loki and Promtail centralise the logs, and Grafana presents them in dashboards provisioned from files, with fifteen days of retention for metrics and seven for logs.
From Dokploy to a script-driven monorepo
We started with Dokploy, on a setup split into two repositories for the backend and the frontend. It let us quickly put in place a minimal, clean continuous deployment and get metrics with no effort. As the project progressed, we wanted more freedom over deployment and a chain that could scale without getting in the way of those who would take over the platform, since imposing Dokploy on a third party meant imposing one more tool. We therefore moved to a monorepo whose deployment relies on two scripts I wrote, install and update, which anyone can read and run on their own server.
The update script backs up the database, pulls the new code, rebuilds the services and then waits for their health check to respond. If the service does not come back up, it automatically rolls the code back and restores the backed-up database. This rollback makes the script longer to write, but it lets an organisation without an operations team update the platform without risking leaving it down. A lock prevents two updates from running at once, and backups are rotated so they do not fill the disk. A GitHub Actions workflow is ready to trigger this update over SSH as soon as a host is configured in the repository secrets, and it skips itself otherwise so that it does not fail on a fork.
Monitoring is self-hosted, consistent with the project's promise, at no cost and for what setting it up taught me. None of the six containers exposes a public port, and Grafana is only reachable through an SSH tunnel: its history includes several known vulnerabilities, which led me not to expose it, even behind authentication. Along the same lines, the backend's health check endpoints are opened through an explicit allow list in Spring Security instead of broad exposure.
For continuous integration, I separated what has to be fast from what can wait. The pull request pipeline only tests the parts that changed, checks the consistency of database migrations and the parity of translation keys between French and English, scans for leaked secrets and reviews dependencies, and a single summary job acts as the required check for the protection of the main branch. Heavier analyses, such as CodeQL, Trivy on dependencies and images, or the OpenSSF Scorecard, run in a separate nightly workflow that also produces an SBOM with a signed provenance attestation. Finally, I mirrored the CI for GitLab so the project is not locked into a single forge.

A production incident diagnosed the same evening
Because the VPS was shared with another project, Grafana clashed with a service already using its port, which I solved by moving it. Later, the backend started restarting in a loop after a rebuild, and the logs revealed the cause that same evening: a database migration edited after it had been applied, whose checksum no longer matched the recorded one. After checking that the schema was intact, I realigned the checksum and restarted the service, then suggested to the team that applied migrations should never be edited and only new migrations added.
Installable in one command and moving to a dedicated server
The platform lets teachers create and publish courses and manage groups, lets users browse those courses, and lets administrators adapt the branding to their organisation. Above all, it is genuinely self-hostable: installable with one command, updated safely and reversibly, served over HTTPS and monitored, with a CI that acts as the single checkpoint before any merge and a supply chain whose security is tooled and not just stated.
The main instance is being migrated to a new production server. The first VPS hosted several projects, one of them behind Dokploy, and this move makes it possible to configure the Caddyfile cleanly on a machine dedicated to Codestar. Upcoming work focuses on teaching features and on the user experience.