Article · 30 March 2026 · 5 min read

Duct tape to trust: our DevSecOps lab, rebuilt

Version one was a week of work, one pipeline, and a lot of duct tape. It proved the concept: code goes in, a scanned image comes out, Argo CD puts it on a cluster. Version two is the same little app with almost every layer underneath replaced, restructured, or hardened. This is what changed, and why.

Project DS v2 title card: a helmeted head over a circuit board.
Project DS v2. The lab has a name because it is a real project, with a repo, a backlog, and versions that break.

The app is the excuse

Anchor is a spaced-repetition scheduler we built for ourselves: give it a lesson name and a start date, it hands back the exact review dates (day 0, then 3, 7, 14, 30) with a calendar file. A few hundred lines of Streamlit. It matters here for one reason: it is the application we run through the entire DevSecOps pipeline, end to end, the way a client's service would be.

The Anchor app: a topic field, a start date, and a generated table of review dates.
The whole application. Two inputs, one table, two download buttons. Everything interesting happens underneath it.

What the lab is

A GitLab pipeline that builds, scans, tests, deploys, monitors, and rolls back a containerized app across three Kubernetes environments (dev, staging, production) on a k3s cluster provisioned with Terraform. Nothing lives in one monolithic file: the app repo carries its code and manifests, the pipeline logic lives in the same public components library this website deploys through, and production is GitOps: the pipeline commits a version bump to a manifests repo, and Argo CD reconciles the cluster from it. Git is the source of truth for what runs.

The components directory of the components-ci repository, one YAML file per job.
The pipeline itself: one file per job, none of them in the app repo. A fix to rollback.yaml lands in every project that includes it, this website included.

The pipeline, step by step

A GitLab pipeline graph: seven stages, every job green.
One run, seven stages. The play icons are the deliberate stops: push the image, staging, production. Everything else decided for itself.
Job log of the secret-detection stage: gitleaks scans the repository and finds nothing.
Gitleaks reads the whole repository before a single Docker layer is built. 18.3 ms, no leaks. One hit and the run stops here.
Job log of the Playwright end-to-end test: four steps, one test passed in 1.63 seconds.
Not a curl approximation: Playwright opens the running container in Chromium and checks what a person would look at, down to the submit button.
Job log of the production deploy: the image tag is bumped in the manifests repo and pushed.
Production is a commit, not a kubectl apply. The job moves the tag from 1.5.0 to 1.6.0 in the manifests repo, pushes, and stops. Argo CD does the rest.

What changed between v1 and v2

The instructive part is why each layer moved. GitHub Actions gave way to GitLab for the include mechanism, the built-in registry, and native manual gates. The single pipeline file was split into reusable components, because a fix should land everywhere at once. Trivy gave way to Grype after serious vulnerabilities were found in the scanner itself: a reminder that following the news and adapting is part of the job. A self-hosted Harbor registry on its own server became the GitLab registry that ships with the project: one fewer machine, one fewer attack surface. One environment became three, each with its own health check and rollback. No post-deploy verification became poll-based checks with automatic rollback. And a blind cluster got the Datadog Operator: APM traces, pod health, and a dashboard.

A Datadog dashboard for the Anchor app: pod counts, system load, monitor states, APM latency.
The end of guessing. Pods, load, eight monitors and per-request latency for the same app the pipeline shipped ten minutes earlier.
Version one asked: can I wire this together? Version two asks the only question that matters in production: can I trust it?

Honestly, what is still ahead

Policy engines, secret management, DORA metrics, canary deploys, and tighter Argo CD feedback into the pipeline are still on the list. The goal has not moved: a platform we can present, defend, and hand off. v2 is closer. It is not done, and saying so is part of the method.

The code is public: gitlab.com/project-ds1/project-ds-v2. And if you are debating one big repo versus splitting early: split early. The cost of extraction later is always higher than the cost of a second repo now.

The same shape, on your platform

This lab is the practice behind the retainer: shared components, health checks that decide, rollbacks that need no hero, and everything written down. Tell us what your pipeline looks like today.

Get in touch