Real cases, real errors

Each project covers the challenge, the architecture, my role and the problems I had to solve along the way. Errors teach more than pretty diagrams.

Freelance In development · phase 1 Oracle Cloud Infrastructure

From paper notebooks to the cloud: a platform to track ~3,500 slot machines

A casino recorded every machine collection by hand, on notebook pages: machine ID, location, who performed the collection, the amount, the date and the winnings. Consolidating that data was slow, capture errors were inevitable and there was no easy way to audit who did what. With my team, we're building a platform on Oracle Cloud that captures this information digitally and traceably.

My role

Infrastructure and systems administration. The rest of the team builds the backend and frontend.

  • Provisioned the VM and network (VCN, subnets, gateways) on OCI
  • Installed and upgraded the runtime: Java 21 and Tomcat 11
  • Linux users and permissions; host firewall with iptables
  • Database: least-privilege users, mTLS wallet and IP allowlist
  • WAR deployments and failure diagnosis

What the platform captures

  • Each machine's SK-ID
  • Location on the floor
  • Person who performed the collection
  • Amount, date and winnings

Goal: real-time capture, less human error and full traceability.

Architecture

← swipe to see the diagram →
Generic architecture of the slot-machine control platform on Oracle Cloud Infrastructure Collection staff & management ~3,500 machines Oracle Cloud Infrastructure Always Free Tier VCN Security Lists · IGW · NAT IGW Public subnet VM · Ubuntu 22.04 Tomcat 11 · Spring Boot (WAR) · Java 21 iptables · least-privilege users Dev DB · Oracle XE 21 in Docker localhost only · persistent volume Private subnet workers websocket designed to scale out PROD Autonomous DB Transaction Processing mTLS · wallet IP allowlist managed backups mTLS ojdbc11
Generic view: region, resource names and ports are intentionally omitted.

Design decisions

Deliberately simple

One VM plus a managed database, inside the Always Free Tier. The design allows for three VMs (app, workers and websocket); one runs today and the others get added as the project scales.

Production separated from development

We weighed running the DB inside the VM against Autonomous Database. We chose Autonomous for production, for managed backups and fault isolation, and an Oracle XE container bound to localhost for development, with no internet exposure.

Layered security

Public and private subnets, Security Lists with minimal ingress, a persistent host firewall, mTLS database connections with a wallet, IP-restricted access and least-privilege DB users.

Problems I solved

ORA-12263 · "file does not exist" (but it did)

The app couldn't open the database wallet. The certificates were owned by a different user than the one running the service (tomcat), and Oracle misleadingly reports that permissions problem as a missing file. Fix: correct the wallet's ownership and permissions for the service user.

A "successful" 2-second deploy… that never started

The WAR deployed with no visible error, but the app never responded. Two stacked incompatibilities: Spring Framework 7 requires Servlet 6.1 (Tomcat 11) while the server ran Tomcat 10.1 (Servlet 6.0), and bytecode compiled for Java 21 couldn't be read by the installed JVM 17. The clue: a real Spring startup takes ~10 s, not 2. Fix: upgrade to Tomcat 11 and Java 21.

Stack

OCI ComputeVCN · IGW · NATAutonomous DBUbuntu 22.04iptablesDockerOracle XE 21Tomcat 11Java 21Spring Boot 4.1Hibernate 7.4HikariCPojdbc11

Impact

Digital capture

Collections move off paper and into a central system.

Traceability

Every collection is tied to a machine, a location and a person.

Less human error

Real-time capture removes the step of copying notebooks into spreadsheets.

Client 100% satisfied with the work delivered so far. We're measuring before vs. after (time per collection, capture errors and consolidation time) to publish real figures.

Live Self-hosting DevSecOps

hack-core.com: secure self-hosting on a Raspberry Pi 4

I wanted to publish my site from home, with end-to-end HTTPS and without exposing my network. The problem: my residential ISP blocks inbound ports 80 and 443. The fix was routing traffic through an outbound tunnel and encrypting every hop.

← swipe to see the diagram →
hack-core.com architecture: visitor to Cloudflare edge, outbound tunnel to cloudflared on a Raspberry Pi, then HTTPS to Nginx Visitor browser Cloudflare Edge DDoS · TLS 1.3 · CDN Zero Trust Tunnel HTTPS No port forwarding traffic enters via the tunnel Raspberry Pi 4 · Docker Compose internal bridge 172.20.0.0/24 · no published ports Tunnel outbound · QUIC cloudflared 4 connections nginx:443 Let's Encrypt · HTTP/2 CSP · HSTS · headers cap_drop: ALL HTTPS certbot 12 h check · DNS-01 certs Cloudflare API · DNS TXT
The browser speaks TLS to Cloudflare; Cloudflare reaches the Pi through an outbound tunnel; cloudflared speaks HTTPS to Nginx.

Three containers

Docker Compose with Nginx (Alpine, cap_drop: ALL, read-only volumes), Certbot with the Cloudflare DNS plugin, and cloudflared. No ports published on the host.

Certificates without port 80

Let's Encrypt's HTTP challenge failed because of the ISP block. I switched to DNS-01: Certbot creates a TXT record through the Cloudflare API and checks for renewal every 12 hours.

Security headers

HSTS, a strict CSP, X-Frame-Options, Referrer-Policy and Permissions-Policy, plus blocking of scanner user agents and sensitive files.

Problems I solved

nginx: [emerg] "ssl_prefer_server_ciphers" directive is duplicate

Certbot's options-ssl-nginx.conf already defines protocols, ciphers and the session cache. My config repeated them, so Nginx wouldn't start. Fix: keep those directives only in the include.

502 · x509: certificate is valid for hack-core.com, not nginx

cloudflared connects to the container by its internal name, which doesn't match the certificate. I enabled No TLS Verify only on that internal hop: traffic stays encrypted inside the Docker network.

403 Forbidden · drwx------

The site folder had 700 permissions, and Nginx, running as a different user in the container, couldn't read it. Fix: chmod 755 on the directory.

Error 1033 · lookup on 127.0.0.11:53: server misbehaving

Months later, Docker's embedded DNS stopped resolving Cloudflare's hosts and the tunnel went into a crash loop. Fix: explicit resolvers (1.1.1.1 and 8.8.8.8) on the service.

Stack

Raspberry Pi 4Docker ComposeNginx 1.25 AlpineLet's EncryptCertbot DNS-01Cloudflare TunnelWeb3FormsHTML · CSS · JS
Open source · MIT Ethical hacking

hack-core/home: ethical hacking resources for learning at home

A beginner-friendly repository: notes, Docker tooling and Python scripts for practicing on local labs, training ranges and systems you own.

Kali lab in Docker

A Kali Linux-based container ready for fuzzing local labs with ffuf and SecLists.

Python helper scripts

Lightweight, readable tools for practicing web security, with setup instructions.

Foundations

Python exercises (loops, functions, classes and conditionals) to review the basics.

⚠️ All material is for your own labs or systems you're authorized to test.

View on GitHub ↗