| VM | Role |
|---|---|
| CTFd VM | CTFd platform, MySQL, Redis, Caddy, behind cf so no public ip |
| Runner 1 | Docker Swarm leader, frps/frpc, registry, static challenges |
| Runner 2 | Docker Swarm worker, additional instancer |
mainctfd-1)Stock infra from CTFd opensource a bit modified(some hashing funcs n all, mentioned at the end)
| Container | what for? |
|---|---|
| Elastic Agent | SIEM/monitoring |
| Cloudflare Tunnel | Public access without exposed ports |
ctf-instance)| Container | Purpose |
|---|---|
themctf-runner-frps-1 | FRP server for routing player traffic to challenge containers |
themctf-runner-frpc-1 | FRP client for registering challenge proxies |
themctf-runner-registry-1 | Private Docker image registry |
themctf-runner-chall-manager-1 | IAC challenge manager |
themctf-runner-chall-manager-janitor-1 | Cleanup expired IAC instances |
instancer2)| Container | Purpose |
|---|---|
| Docker Swarm worker | Receives whale-scheduled challenge instances |
Players │ ├── ctf.themctf.com (Cloudflare Tunnel → mainVM) │ └── runner1(frps HTTP — web challenges) Other chals for players were mostly single instanced WireGuard VPN mainctfd-1 ←→ ctf-instance ←→ instancer2
There was other shit as well not important.
| Plugin | Purpose |
|---|---|
ctfd-whale | Per-team Docker instance spawning |
ctfd-chall-manager | IAC-based on-demand infra |
┌────────────────────────────────────────────────────────────────────┐ │ THEM?!CTF 2026 │ │ │ │ Players ──► Cloudflare ──► Caddy ──► CTFd (mainctfd-1) │ │ │ │ │ WireGuard VPN │ │ 10.8.0.0/24 │ │ │ │ │ ┌──────────────────────────┴──────────────────┐ │ │ │ │ │ │ ctf-instance instancer2 │ │ │ Runner 1 — Swarm Leader Runner 2 — Swarm Worker │ │ │ │ │ │ ┌────┴────┐ ┌────────┘ │ │ frps Registry Whale instances │ │ frpc (random placement) │ │ chall-mgr │ │ │ └────────────────────────────────────────────────────────────────────┘
Occurrence: This shit was detected during live CTF
Root Cause: The iptable rules for DOCKER wasnt configured properly, I tried fixing but it was casuing a pain so i left it open~
Impact: Challenge containers could potentially accessfrpc admin API and pull/push images from the registry.( in theory but pushing actual img to reg was not possible from any chal)
Fix: NAH didnt fix shit, do i look like the builder from COC always fixin stuff
FIX: unfortunately a rollback
Based on ELK log analysis
| Time (UTC) | Host | Event |
|---|---|---|
| May 30 @ 12:58 | instancer2 | Soft lockups on all CPUs — runc, elastic-otel-co, gunicorn, event_engine |
| May 30 @ 12:59 | instancer2 | systemd-journald.service: Watchdog timeout → reboot |
| May 30 @ 12:49–12:51 | ctf-instance | Soft lockups — elastic-otel-co, runc, migration threads |
| May 30 @ 22:07 | instancer2 | rcu_preempt soft lockup |
| May 30 @ 22:14–22:15 | instancer2 | Mass soft lockups — socat stuck 245s, migration stuck 273s, elastic-otel-co stuck 115s |
| May 30 @ 22:15 | instancer2 | systemd-journald.service: Watchdog timeout → reboot |
| May 30 @ 22:15 | ctf-instance | NMI watchdog: Perf NMI watchdog permanently disabled |
| May 31 @ 12:48–12:52 | instancer2 | Soft lockups — docker, runc, elastic-otel-co, khugepaged |
| May 31 @ 12:52 | instancer2 | systemd-journald.service: Watchdog timeout → reboot |
| May 31 @ 12:49–12:51 | ctf-instance | Soft lockups — elastic-otel-co, runc |
| May 31 @ 20:48–20:51 | ctf-instance | Soft lockups — gunicorn, runc, khugepaged, elastic-endpoin |
| May 31 @ 20:51 | instancer2 | Mass soft lockups — gunicorn, socat, dockerd, runc, khugepaged |
| May 31 @ 20:51 | instancer2 | systemd-journald.service: Watchdog timeout → reboot |
| May 31 @ 23:25 | Both | Simultaneous soft lockups on all CPUs of both hosts |
| May 31 @ 23:25 | instancer2 | systemd-journald.service: Watchdog timeout → reboot |
| May 31 @ 23:25 | ctf-instance | systemd-journald.service: Watchdog timeout (limit 3min)! |
Primary cause: Elastic Agent (elastic-otel-co, elastic-endpoin) CPU starvation
The kernel soft lockup messages consistently show elastic-otel-co and elastic-endpoin processes stuck for 21–115 seconds across multiple CPUs simultaneously.
This might be due to too much data ingestion not really sure. There were several ghost containers and all of its data was bein ingested so might be(not really sure)
Elastic Agent was consuming excessive CPU, starving the kernel scheduler. When CPU migration threads (migration/N) themselves got stuck, the kernel could no longer schedule work causing a kinda domino where journald's watchdog timer couldnt be serviced, triggering the reboot.
Contributing factors observed in logs:
gunicorn workers stuck 33–82 seconds (whale challenge containers under load)runc stuck 28–48 seconds (container spawning/destroying under load)khugepaged stuck 23–45 seconds (transparent huge page management under memory pressure)socat stuck 245 seconds on instancer2 (challenge1 static container)dockerd stuck 22–36 secondskworker threads stuck (kernel work queue backlog)The simultaneous crash at May 31 23:25 affected both runners and well the instancers never came online again. yea im bein fr they js loop crashed idk how, since this was the end of the CTF i didnt care huh.
Also yes, they both crashed on 2 seperate ocassions and "idk how" basically means i dont know how or either there was too much workload to be like investigatin why shit happened.
FIX: Manually updating DB
Another minor incident was that the FRPC was constantly gettin fucked up, so we had to kill ghost containers manually and smtimes configs were overwritten so we had to fix that everytime.
| Control |
|---|
| SSH key-only auth on private network only |
| X11 forwarding disabled |
| UFW firewall |
| No public ports |
| Elastic Agent SIEM |
| Unattended upgrades |
| Control |
|---|
| SSH key-only auth |
| Docker TLS on 2376 |
| UFW firewall |
| DOCKER-USER iptables |
| raw table blocks |
| Container isolation |
| Whale containers: non-root |
| Resource limits |
| Presisting IPtable rules |
| Control |
|---|
| SSH key-only auth |
| UFW firewall |
| Docker icc disabled |
| DOCKER-USER isolation |
| docker-restrict.service |
| Infra unreachable from containers |
Elk stack was sestup on all 3 vms with proper data ingestion. Rules were in place too for alerts and was integrated with discord for overlooking. Thank God no security incident took place.
| Container | CPU | RAM | Notes |
|---|---|---|---|
| CTFd | 112% | 1.88GB / 13GB | High due to frpc retry storm |
| MySQL | 36% | 4.7GB / 11GB | Connection leak 100+ idle connections |
other containers abd vms were pretty normal throughout
For the final question? TECH STACK
So yea guys thats it for this time ig. If you ask me what i found the hardest? prolly debugging in prod. this is smthg i'd like to not do again(especially with so many tickets being created and constant discord notifs.)
some of you might be wondering did i slop this too? nah. The log part was summarised by slop but i verified it manually and removed the non-important ones.