Skip to work
Josep Mampel
Barcelona

I build things, then try to break them.

Controllers, internal platforms and pipelines, mostly written in Go. An operator that keeps a workload matching its spec, a portal that turns a form into a running service, an admission gate that refuses an image built by the wrong workflow, a vision pipeline that names the birds at a drinking trough. Everything here runs, and every number is measured.

01

webapp-operator

Kubernetes operator · 2026

One resource in. A whole workload out, and kept that way.

apply one resource, get the whole stack
Terminal recording: a WebApp resource is applied and the operator creates a Deployment, Service and HPA

You write a small WebApp resource: an image, a port, how many replicas. The controller reconciles it into a Deployment, a Service, a HorizontalPodAutoscaler and a PodDisruptionBudget, then keeps them matching the spec forever. Edit a child by hand and the next reconcile puts it back.

The interesting part is the parts that refuse. An admission webhook rejects :latest and untagged images, because an operator that forbids mutable tags for its operands should hold itself to the same rule. Status conditions report what is actually true, including Available=False when the image does not exist. Two API versions are served through a conversion webhook that completes inconsistent stored data instead of rejecting it.

  • Go
  • controller-runtime
  • kubebuilder
  • envtest
  • Helm
  • Prometheus
  • Grafana
  • Trivy
  • govulncheck
p95 50ms
reconcile at 250 objects
81 / 100%
controller / webhook coverage
41 MB
RSS under full load
2
API versions, converted
02

idp-backstage

Internal developer platform · 2026

Fill in a form. The repository and the running service are already there.

form to running service, uncut
Screen recording: filling the Backstage template form creates a repository and a running Kubernetes workload

One step produces a GitHub repository with CI, a container build and health endpoints, plus a live workload in Kubernetes. No manifest to copy, no second tool, no ticket to the platform team. The catalog entry gets a tab showing the real state of the cluster.

Underneath, the scaffolder writes a WebApp custom resource and hands it to my own operator, so the two projects are one system: the portal is the front door, the controller does the reconciling. The part of the README I care about most is the one on what happens when it half-fails, when a repository is created but the cluster apply is rejected. That is where the engineering actually is.

  • Backstage
  • Go
  • Argo CD
  • Kubernetes
  • GitHub Actions
  • PostgreSQL
  • TypeScript
  • kind
~75 s
cold bootstrap to ready cluster
1 form
to repo + CI + live pods
3
CI suites, e2e included
03

provenance-gate

Admission control · Supply chain · 2026

A signed image is not enough. The question is who signed it, and whether they were supposed to.

three admissions, one refused despite a real signature
Terminal recording: three admissions, where an unsigned image and a genuinely signed image built by the wrong workflow are both refused

Admission control that ties a Kubernetes workload to the build that produced it. Not this image is signed, but this image was built by the workflow that belongs to this service, checked against a trust root the workload itself cannot write. The demo runs three admissions against images the repository really publishes: its own build is admitted, an unsigned image is refused, and an image built by a different workflow of the same trusted builder is refused as well. The third one is the whole point, because it is not a forgery.

The project began from a claim that turned out to be false, and correcting it is more interesting than the claim was. I argued that Kyverno could not express a per-resource provenance check, then tested it against Kyverno 1.19 before writing a line of the webhook. It can, in nine lines of CEL. So the boundary moved: Kyverno stays the enforcement engine and nothing here reimplements it, but it can only read a trust root, not create one, keep it in step, or control who writes it. That registry is what this builds, with RBAC bound to the platform's ServiceAccount and to nobody else. The design record saying the earlier one was wrong is kept next to it, unedited.

  • Go
  • Kubernetes
  • controller-runtime
  • Admission webhooks
  • Sigstore
  • cosign
  • SLSA
  • Kyverno
  • kind
  • GitHub Actions
0.6 ms
admission p50, cache warm
SLSA L3
the builder signs, not the repo
45.9 MiB
RSS under load
10
design records, one retracting another
04

llm-gateway

v1.0.0 Concurrent Go proxy · 2026

One OpenAI-shaped endpoint, every provider behind it.

a Claude model, in OpenAI's wire format
Terminal recording: tokens from a Claude model arriving one by one in OpenAI's wire format

An application that talks to OpenAI directly is married to OpenAI: its key sits in that application's config, its retries are that application's problem, and nobody can say what it costs. Six applications means six of each. The gateway takes it over. One endpoint, one set of revocable keys, and the messy parts written once: routing by model prefix, streaming, failover, a rate limiter shared across replicas, caching and metrics. Any OpenAI client works unchanged by pointing its base URL at it.

Each vendor speaks its own dialect, so every provider package owns its translation and none of that vocabulary escapes it, a rule Go turns into a compile error rather than a convention. The parts worth arguing about are the failure paths: only retryable errors fail over, backoff is jittered so a thousand clients do not retry in unison, a circuit breaker pulls a bad provider out and lets exactly one probe decide when it is back, and identical concurrent requests collapse into a single upstream call. It builds and tests itself offline against a fake upstream that speaks both dialects and misbehaves on request.

  • Go
  • Concurrency
  • SSE
  • Redis
  • Prometheus
  • Grafana
  • Helm
  • Docker
  • GitHub Actions
v1.0.0
released, with SBOM and provenance
49
smoke checks, no API key needed
8
Prometheus metrics exported
scratch
base image: no shell, no libc
05

BirdVision

Computer vision platform · Final degree project · 2025–2026

Point a camera at a drinking trough. Get species, place and time.

a live camera, boxes drawn on the stream
A live camera stream of a drinking trough with detection boxes and species names drawn over the playing video

My final degree project at EPSEVG · UPC, built with the environmental association Alytes for outreach and education. It is a whole platform rather than a model: dataset preparation, two-stage YOLO12 training, a GPU-aware FastAPI inference service, asynchronous video jobs, and an RTMP→HLS pipeline that draws boxes on a live stream.

Four modes share one backend: image upload, async video analysis, the browser webcam, and published RTMP cameras. Video jobs are content-addressed (SHA-256(file : conf : stride)), so re-uploading the same clip returns instantly, even for a different user, and any job still queued survives a backend restart. Every stage is bounded: worker pools, semaphores, a sliding-window rate limiter and hard timeouts on both inference and FFmpeg.

  • Python
  • PyTorch
  • YOLO12
  • FastAPI
  • OpenCV
  • FFmpeg
  • React
  • PostgreSQL
  • Docker
  • AWS
  • NGINX-RTMP
101
bird species detected
0.910
mAP@0.5, own validation split
4
operating modes, one backend
20 min
max async video, cached by hash

Team work

Shared repositories, described by what I actually merged rather than by what the team shipped.

Neby

Contributor
Community services platform · Team project · 2024

Neighbours trade services with each other, paid in a currency that only exists inside the app.

What I wrote
  • The user endpoints: fetch a user, delete a user by id, and delete your own account, each with its own authorisation rule.
  • Registration validation, so a duplicate account is rejected in the auth controller instead of blowing up on a database constraint.
  • A shared error helper the other controllers reused, so the API stopped inventing a new error shape per route.
  • The GoogleTest suite for user deletion: seven cases over three fixtures, covering admin versus neighbour, invalid and missing ids, and a delete attempted across community boundaries.
  • The claim-achievement endpoint in the gamification system.
  • Reviewing and merging teammates' pull requests, including the ratings and notification work.

Built by a team of eight during the degree. Neighbours publish the services they offer or need, pay each other in the platform's own currency, rate the result, and collect achievements along the way. Three repositories make one product: a C++20 REST API on Crow, a TypeScript frontend, and a devops repository holding the Compose files, the NGINX configuration and the database seeds for every environment.

It is the only C++ backend I have worked on, and the one place where I learned what a code review actually costs when six people are touching the same controllers.

  • C++20
  • Crow
  • libpqxx
  • PostgreSQL
  • JWT
  • GoogleTest
  • CMake
  • Docker
  • NGINX
10
pull requests merged
8
people on the team
7
test cases on one endpoint
3
repos, one product

Toolbox

Sorted by how often I actually reach for it.

Every day, happily

  • Go
  • Kubernetes
  • Docker
  • Linux
  • Git
  • Bash

Also comfortable with

  • Python
  • C++
  • TypeScript
  • React
  • PostgreSQL
  • Redis
  • Kafka
  • FastAPI
  • NGINX
  • AWS
  • Prometheus
  • Grafana
  • GitHub Actions
  • Helm
  • Kyverno
  • Sigstore

Currently learning

  • Terraform

About

I got into this because I liked taking things apart to see why they broke, and that never really stopped. Almost everything I build now sits underneath something else: a controller, a portal, a pipeline. The parts nobody notices until they fail.

I studied Computer Engineering at EPSEVG, UPC, and finished with BirdVision, a detection platform built with a local environmental association. Since then most of my time has gone into Go and Kubernetes, learned the only way I trust: by building the kind of thing Kubernetes is meant to run, and then trying to break it.

What I care about is the habit underneath. Assume it will fail, decide in advance what it does then, and measure instead of guessing. That is why the operator publishes its own metrics, why the platform documents its half-failure case, and why every figure on this page came out of a benchmark or a coverage report.

Archive

Older and smaller repositories, kept public because deleting your history is a strange thing to do.

Everything else on GitHub
A game, on a portfolio

Reconcile

Snake, except the snake is a control loop and the food is pods. It gets faster as it grows and it dies the moment it touches itself, which is roughly how a controller feels at three in the morning.

Play it
Contact

Ask me anything about any of it.

If something here is useful to you, or you want the detail behind one of the numbers on this page, write to me. I am also happy to argue about why latest is a terrible image tag.

josepmampel20@gmail.com