W-01
How I discovered DevSecOps for real
A Kubernetes cluster shared by an entire class, four environments, and one Node.js service to take all the way to production without downtime. What the Escolis project taught me.
For a long time, DevSecOps was mostly a word to me. I could define it: build security into the whole delivery chain instead of checking it at the end. I knew the tool names. But I had never really lived it.
My 2025–2026 year at ENSIBS changed that, through a project called Escolis.
The starting point: an app that only ran locally
Escolis is a school information system built as microservices: each business feature (work-study placements, calendars, email and support…) is an independent service, developed and versioned separately, then assembled with the others. Our class had designed it in an earlier project. It worked, but only on our own machines.
Our job wasn’t to add features. It was to industrialize it: build a real CI/CD chain with security and resilience built in, and ship a final release, ICE-2026-v1.0.0, to production without service interruption.
It sounds simple. In practice, that’s where I learned that “it works on my machine” is the very beginning of the work, not the end.
Thirty students, ten groups, one production
This wasn’t an isolated exercise per group. Around ten groups, roughly thirty students, worked at the same time on the same cluster and the same GitLab. Each group industrialized a different microservice, but all of them had to converge on the same environments to rebuild Escolis as a whole.
That changes everything. You share one infrastructure repository, follow strict conventions (namespaces, image names, ports) and accept that a mistake in your service can break everyone else’s environment. The stability of the final product was a collective responsibility.
A real cluster, shared by the whole class
No simulation: we worked on a Kubernetes (K3s) cluster with one master and six workers, shared by every group.
- Each group had its own namespace (
ns-groupe-X), a private sandbox. - Three global namespaces were shared by everyone:
staging,preprodandprod. That’s where the whole class’s microservices came together. - GitLab runners ran inside the cluster, so pipelines could reach pods through Kubernetes’ internal DNS without ever leaving it:
http://<service>.<namespace>.svc.cluster.local:<port>
What struck me most was the two-level RBAC. As a student, I could write to my group’s namespace and only read the global environments. The CI/CD robot could write to staging, preprod and prod… but had no rights at all on group namespaces.
In other words: nobody deploys to production by hand. Everything goes through the pipeline. It’s a constraint, and that’s exactly the point.
Four environments, four safety nets
The pipeline was designed as a progression. Each environment adds a layer of verification.
| Environment | Goal | Tools |
|---|---|---|
| Sandbox | Static security, fast feedback | SAST, secret scanning, Trivy |
| Staging | Dynamic security and integration | OWASP ZAP, Newman/Postman |
| Preprod | Performance and resilience | k6, Locust, chaos engineering |
| Prod | Zero-downtime delivery and monitoring | Blue/Green, Canary, Grafana |
Sandbox: shift left. Before anything is deployed, we analyze the code, the Dockerfile and the YAML manifests. The goal: feedback to the developer in under two minutes. A flaw caught here costs almost nothing to fix.
Staging: the app is actually running. We attack the real internal URLs with DAST (OWASP ZAP, fuzzing) and check that API contracts hold.
Preprod: does it hold up? Load tests validate CPU and memory quotas, and chaos engineering randomly deletes pods to prove the architecture heals itself without losing data.
Prod: ship without anyone noticing. Zero-downtime strategies, Grafana dashboards and runtime protection.
Two ways of working with Git
We used two workflows, each suited to its role.
On application repositories, trunk-based development: a short-lived branch, a merge request to main that triggers static scans, a merge that builds a temporary image and deploys it to the sandbox, then a version tag (such as v2.1.0) that produces the official Docker image.
On the global infrastructure repository, GitFlow, where each branch maps to an environment:
feature/* → develop (staging) → release/ICE-2026-vX (preprod) → main (prod)
The rule I found most interesting: a merge to main had to be approved by someone from another group. Mandatory cross-review, recorded in the history. It forces you to write changes that someone without your context can understand. And that’s a real skill.
The journey of one change, end to end
To make it concrete, here is the path a single change takes:
- A developer pushes code and opens a merge request. CI runs a static scanner (Bandit, for example). After the merge to
main, the image is built and deployed automatically to the group’s sandbox. If tests pass, a version tag produces the official Docker image. - The service’s Kubernetes manifest is updated in the infrastructure repository (
feature/*→develop). It deploys automatically tostaging, followed by an automated OWASP ZAP DAST attack against the shared environment. - Once every group has merged into
develop, a merge request torelease/…triggers preprod: load tests and chaos engineering across the whole system. - A final merge request to
main, reviewed by another group, deploys to production, monitored in real time in Grafana.
My part: the Calendar service
I was in group 8, a team of three, responsible for the Calendar service in Node.js. Because our team was small, preprod wasn’t required for us, so we focused on the rest of the chain.
Blocking a vulnerable image in CI
In the sandbox, we scanned the Docker image with Trivy. If a CRITICAL vulnerability showed up, the pipeline stopped. Full stop:
trivy image --exit-code 1 --severity CRITICAL "$IMAGE"
One line. But it was the first time I saw security go from “recommendation” to “condition for moving forward”.
Checking the API contract in staging
In staging, Newman/Postman tests automatically checked that the event-creation API returned 201 Created and respected the expected data contract. If another group depended on our format, they were protected.
Switching production without downtime
For production, we set up a Blue/Green deployment: two Kubernetes Deployments, calendrier-blue and calendrier-green. The new version starts next to the old one, and once it’s ready, 100% of traffic switches at once with a kubectl patch, triggered manually from a GitLab CI job.
Simplified, the idea looks like this:
kubectl patch service calendrier -n prod \
-p '{"spec":{"selector":{"version":"green"}}}'
If something goes wrong, rolling back is the same command in reverse.
What I took away
Security is a series of small gates, not a final audit. A scan that blocks in two minutes beats a fifty-page report three weeks later.
Constraints shape the architecture. RBAC, isolated namespaces and internal DNS aren’t obstacles: they’re what makes a cluster shared by dozens of people workable.
Deployment is part of the product. I used to see going to production as a step after the “real” work. Now I think about how a service will be shipped, monitored and rolled back from the moment I start writing it.
Cross-review teaches technical empathy. Writing for someone who doesn’t share your context makes you write better.
With thirty people on one infrastructure, conventions are security. One image name or port that breaks the shared rule, and it’s another group’s environment that goes down.
This project took me from “I know what DevSecOps is” to “I want to do this for a living”. That’s why Cloud and DevSecOps are now at the heart of what I’m looking for.