~ anton@antonsatt
~ / projects / mallo
type: group project · chas-challenge-2026 when: march – june 2026 my-role: devops

Mallo

Community, Conversation & Connection

A Swedish-language community platform built by a 9-person cross-functional team for Chas Challenge 2026 — the annual competition at Chas Academy. Members share posts, find local activities on a map, and chat in real time. I owned DevOps: the Kubernetes deployment, the GitLab CI/CD pipeline, and the observability stack.

Mallo · the marshmallow mascot — click to enlarge

What is Mallo?

Mallo is a Swedish-language web community. Members write posts, react with "hugs", comment, save favourites, and find real-world activities pinned to a map of Sweden. A built-in chat lets them message each other directly. It is built mobile-first, with a friendly marshmallow mascot tying the brand together.

"Chas Challenge 2026's theme was Tech for Good — build a product that creatively uses technology to generate clear and meaningful benefit for people, society, or the environment. We answered with a place for community, conversation and connection."

Eighteen cross-functional teams competed. Each team mirrors a real workplace — frontend, fullstack, DevOps, systems development, and UX/UI — and is judged by an external industry jury on how well it executes the theme. Our team, GR8, shipped Mallo end-to-end: a React 19 SPA, a .NET 10 API, MS SQL Server, and real-time chat over SignalR, all running on the school's Kubernetes cluster.

The Product

Mobile-first views. Tap any screen to enlarge.

Welcome & login
Forum feed
Activity map
Real-time chat

What it does

Forum

posts · hugs · comments

Posts with titles, 13 categories and 20 tags, threaded comments, "hugs" reactions, bookmarks, and user-driven reports for moderation.

Activity map

Mapbox · Turf.js

Geo-tagged events with images, date ranges, and addresses on an interactive Mapbox map. Distance helpers power proximity filtering.

Real-time chat

SignalR WebSocket

One-to-one direct messages over a SignalR hub. Typing indicators, read-receipts, per-user soft-delete, and auto-reconnect.

Accounts & auth

JWT · GDPR

Register with Swedish personnummer + 18+ check, JWT bearer tokens, email-based password reset, profile editing, and GDPR account deletion.

Push notifications

Firebase FCM

A Firebase Cloud Messaging service worker in the browser delivers activity and chat alerts.

My Role — DevOps

Two of us owned DevOps. My focus: getting the whole stack running reliably on the school's k3s Kubernetes cluster, wiring up a GitLab CI/CD pipeline that builds, deploys and smoke-tests every change, and standing up Prometheus + Grafana observability so we could see what the API was doing in production.

Architecture Overview

The SPA and the API deploy separately so they can scale on their own. Everything lives in one Kubernetes namespace (doe25-group-8), behind a single Traefik ingress.

1

Browser → Traefik ingress

The React SPA talks to the cluster over HTTPS. Traefik terminates TLS and routes by path: / to the frontend, /api and /chat/hub to the API.

2

Frontend pod (nginx)

The production React build is served by nginx with an SPA fallback (try_files $uri /index.html).

3

API pod (.NET 10 + SignalR)

REST endpoints plus the SignalR hub for chat. Auth is a stateless JWT, so the API can scale to more than one replica. SignalR connections are pinned to a single pod via a sticky cookie at the ingress, which keeps chat from dropping.

4

Durable state: MS SQL + Redis

MSSQL is the only thing with durable state worth worrying about — it runs as a StatefulSet on a Longhorn PVC. Redis (a Bitnami Helm sub-chart) handles caching with a small PVC of its own.

5

Observability: Prometheus → Grafana

The API exposes /metrics. Prometheus scrapes it; Grafana visualises it; PrometheusRule fires alerts when something looks wrong.

Running on Kubernetes

Everything ships to the school's k3s cluster (same APIs as upstream Kubernetes, smaller install). We split the deploy in two so it stays approachable for the whole team:

Plain YAML for the app

Frontend, API and MSSQL are plain Kubernetes manifests — anyone on the team can read a Deployment without learning Helm templating first. The manifests bake in a gr8-plain placeholder; CI rewrites it per environment (sed -i 's|gr8-plain|gr8-<env>|g').

Helm for the versioned bits

Redis (an upstream Bitnami sub-chart) and the observability glue — ServiceMonitor, PrometheusRule, and the Grafana dashboard ConfigMap — ride in a Helm chart, where templating earns its keep for release naming.

Secrets stay out of git

Two plain Kubernetes Secret objects — one for app secrets, one for the image-pull credentials — are created once with kubectl create secret when a namespace is bootstrapped. CI never templates them and they never land in the repo.

$ kubectl get pods — api, db and frontend running · click to enlarge

CI/CD Pipeline (GitLab)

Every push runs one pipeline; when a merge request is open, the MR pipeline gates the merge button so deploy and smoke-test have to pass before code reaches an environment.

GitLab pipeline: test → build → deploy → smoke-test · click to enlarge
test

check-merge-source enforces that only develop can merge into main, alongside the frontend/backend test jobs.

secret-detection

GitLab's built-in secret scanning runs on every pipeline.

build

Docker buildx with registry cache; images pushed to the school registry tagged per branch.

deploy

Applies the rewritten manifests, then kubectl rollout restart to force a fresh pull even when the tag is unchanged; helm upgrade --install for Redis + observability.

smoke-test

Polls /api/openapi/v1.json every 5s for 5 minutes — long enough to cover a cold MSSQL pod re-applying 28 EF migrations (~3–4 min) after a fresh deploy. Only gateway errors and connection failures count as down.

Branches → environments

Branch Tag Deploy
main :latest Manual approval gate · persistent DB
develop :develop Auto-deploy on push · persistent DB
feature/* :<slug> Image built, not auto-deployed

Observability

The school cluster already runs Prometheus + Grafana. Our Helm chart layers Mallo-specific pieces on top so the API isn't a black box in production.

Grafana — request rate, 5xx, p95 latency, requests by status code · click to enlarge

ServiceMonitor

Scrapes the API's /metrics endpoint every 15 seconds.

PrometheusRule

Two alerts: Gr8ApiHighErrorRate (5xx above 5% for 5 min) and Gr8ApiNoTraffic (0 req/s for 15 min).

Grafana dashboard

Auto-provisioned by a ConfigMap. Four panels: request rate by endpoint, 5xx rate, p95 latency by endpoint, and request count by HTTP status code.

Tech Stack

Kubernetes (k3s) GitLab CI/CD Helm Prometheus Grafana Traefik Docker .NET 10 React 19 MS SQL Server SignalR Redis Mapbox Firebase FCM Longhorn

Team GR8

Nine people across five competencies. I handled DevOps alongside Reza.