I picked roulette for my Cloud Services midterm mostly because it looked fun to build. I was curious about the idea of beating the house edge, and I wanted something I could play around with without betting real money. In a way, ZeroTrace is also a little fidget toy. You choose a color, set a virtual stake, spin the wheel and see what happens.

But there is a point behind it. In European roulette, Red and Black each cover 18 of the 37 pockets, so the chance of winning either bet is about 48.65%. The green zero is what gives the house its roughly 2.70% edge on those bets. A previous spin doesn't change the next one. The ball has no memory, even when a streak makes it feel otherwise.

What I wanted to build, and what I actually finished

I had roulette in mind from the beginning. My original plan was bigger than the current app. I wanted to experiment with betting systems like Martingale, Fibonacci, D'Alembert and Labouchere, and see how they behave over many spins. Those strategies haven't been implemented yet. With the time available, I decided to keep the working scope smaller instead of adding half-finished features.

The current version supports Red and Black bets with virtual credits, a European wheel numbered 0 to 36, animated spins and a saved experiment history. The betting table is visible, but it isn't a fully interactive table for different types of bets. That's probably the first thing I'd improve if I continued this project.

The hardest part for me was the animation, especially making the ball end up where the actual result says it should. Connecting the interface to the API also took some trial and error. Docker and PostgreSQL were more manageable for me, although I still had to learn the configuration step by step. I used AI assistance and existing library code during development, but I also spent time understanding the setup, testing it and deciding which features to keep.

How the app runs

ZeroTrace uses Node.js with Express and EJS, plus browser-side JavaScript for the wheel animation. PostgreSQL stores the spin history. Docker Compose runs two main services, app and db, on a user-defined bridge network. The browser opens the app at http://localhost:3000, while the app reaches PostgreSQL using db:5432, not localhost inside the container.

[Insert architecture diagram here]

I used a multi-stage Dockerfile. Dependencies are installed after copying the package files, which helps Docker reuse the cached dependency layer when only application code changes. The runtime stage then copies the files needed to run the app.

FROM node:22.23.4-bookworm-slim AS builder WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund 

In Compose, the database has a healthcheck, and the app waits for it before starting. This was more useful than simply hoping PostgreSQL would be ready in time.

depends_on: db: condition: service_healthy 

To run it locally, I copy .env.example to .env, set local credentials and use:

docker compose up -d --build docker compose ps 

[Insert app screenshot and Docker Compose screenshot here]

Testing the part that matters

I checked that both containers were healthy, then tested the HTTP endpoints. GET /health returned 200, GET /api/experiments read the saved history, and POST /api/experiments created a spin record with status 201. There are also automated tests for the roulette rules, API and database.

The persistence test was the most useful Docker lesson. I saved a spin, noted its ID, ran docker compose down, then started the services again. The same record was still there. That's because PostgreSQL uses a named volume, so removing the container doesn't remove its stored data. Running docker compose down -v would be different, since it removes the Compose-managed volume too.

What I'd do differently

I liked learning Docker this way because I was building something I actually wanted to try, rather than just copying a fixed tutorial. I got more practice connecting services and checking that the full request flow worked.

ZeroTrace is still quite far from what I first imagined. The table needs more interaction, and I'd like to compare betting strategies using proper statistics rather than assuming any pattern can beat the house edge. Still, I'm glad I got a smaller version running, with its API, database and containers working together. For this midterm, getting that foundation right mattered more than squeezing in every idea.

Source code: https://github.com/Oikpe/zerotrace