01

Separate content from execution

Course and challenge definitions live in the filesystem. The application presents lessons, tracks stage progress, and accepts code from an in-browser editor. A separate grading path runs each submission against the stage's test cases.

Progress and submissions are stored through Prisma in SQLite. That keeps the initial deployment on one node and makes its operational scope explicit.

02

Give every submission a bounded environment

Each grading run starts a fresh container with networking disabled, a read-only filesystem, CPU and memory limits, a process limit, and an execution timeout. Separate images provide the supported language runtimes.

Starting a container adds overhead and requires managing runtime images. I accepted that cost to separate submissions from the application process and put resource limits around their execution. Containers still share the host kernel.

03

Keep the deployment understandable

The deployment uses Docker Compose for the application and a Caddy reverse proxy. The application launches grading containers through Docker, so access to the daemon is an explicit part of the deployment boundary.