CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.
The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.
- Email/password signup and login with role-based JWT authentication
- Public problem catalog with generated starter code
- Admin-only problem and test-case management
- Monaco-based browser code editor
- Python 3.12, Java 21, and C++17 judging
- Synchronous runs against sample or custom inputs
- Asynchronous submissions against all test cases
- Per-user submission history and verdict polling
- Docker Compose development environment
- Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow
| Layer | Technologies |
|---|---|
| Frontend | React 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS |
| Backend | Java 21, Spring Boot 3.5, Spring Web, Spring Data JPA |
| Data and messaging | PostgreSQL 16, Kafka 3.7 |
| Authentication | JWT (HS256), BCrypt password hashing |
| Judge sandbox | Docker containers with network, memory, CPU, and time limits |
| Delivery | Docker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD |
flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.
Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:
| Service | Responsibility | State |
|---|---|---|
auth-service | Signup, login, admin seeding, JWT issuance, and /auth/me | users |
problem-service | Problems, function signatures, starter code, sample cases, hidden cases, and admin APIs | problems, test_cases |
submission-service | User-facing run/submit APIs, submission lifecycle, history, and verdict persistence | submissions |
judge-service | Harness generation, sandboxed code execution, output parsing, and grading | Stateless |
frontend-service | React SPA and path-based API reverse proxy | Stateless |
auth-serviceverifies credentials and issues an HS256 access token containing the user ID, email, and role.- The frontend stores the token in local storage and sends it as
Authorization: Bearer <token>. auth-service,problem-service, andsubmission-serviceindependently validate the token using the same secret, issuer, and audience.- Public problem reads expose sample cases only. Admin problem APIs require the
ADMINrole. - The judge fetches hidden cases from
problem-servicethrough/internal/problems/{id}/judge-bundle, guarded by a separate internal service token.
The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.
sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.
sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.
Each execution runs in a temporary Docker container with:
- No network access
- Configurable CPU and memory limits
- A configurable execution timeout
- Separate stdout and stderr capture
- Cleanup after process exit with
docker run --rm
Runtime images:
| Language | Image | Command |
|---|---|---|
| Python | python:3.12-alpine | python main.py |
| Java | eclipse-temurin:21-alpine | javac Main.java && java -cp . Main |
| C++ | gcc:13 | g++ -O2 -std=c++17 ... |
Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.
- A
Userhas an email, BCrypt password hash, andUSERorADMINrole. - A
Problemstores its description, difficulty, tags, function signature, supported languages, and generated starter code. - A
TestCasestores positional input arguments and expected output as JSONB, plus sample visibility and ordering. - A
Submissionstores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.
Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.
- Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
- The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
- Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
- Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
- Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.
.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent
- Docker with Docker Compose
- JDK 21 and Maven for host-based backend development
- Node.js 20+ and npm for host-based frontend development
- Python 3 for the end-to-end test
From the repository root:
docker compose up --buildOpen http://localhost:8080.
The default local admin account is:
Email: admin@codebox.dev
Password: admin12345
Stop the stack with:
docker compose downThe first judge request can be slower while Docker downloads the language runtime image.
This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:
scripts/run-local.shOpen http://localhost:5173. Logs are written to .local-run/logs/.
scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and KafkaUse SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.
| Component | Port |
|---|---|
| Frontend, Docker Compose | 8080 |
| Frontend, Vite | 5173 |
| Auth service | 8081 |
| Submission service | 8082 |
| Judge service | 8084 |
| Problem service | 8085 |
| PostgreSQL | 5432 |
| Kafka host listener | 29092 |
All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.
| Method and path | Purpose | Access |
|---|---|---|
POST /auth/signup | Create a user | Public |
POST /auth/login | Issue an access token | Public |
GET /auth/me | Return the authenticated user | Authenticated |
GET /problems | List problems | Public |
GET /problems/{id} | Get problem details and sample cases | Public |
/admin/problems/** | Manage problems and test cases | Admin |
POST /submissions/run | Run against sample or custom inputs | Authenticated |
POST /submissions | Queue a judged submission | Authenticated |
GET /submissions/{id} | Get an owned submission | Authenticated |
GET /submissions | List the current user's submissions | Authenticated |
Important environment variables:
| Variable | Used by | Purpose |
|---|---|---|
DB_URL, DB_USERNAME, DB_PASSWORD | Stateful backend services | PostgreSQL connection |
JWT_SECRET | Auth, problem, submission | Shared HS256 secret; must be at least 32 bytes |
JWT_ISSUER, JWT_AUDIENCE | Auth, problem, submission | JWT validation boundary |
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORD | Auth | Bootstrap administrator |
INTERNAL_TOKEN | Problem | Protect hidden-case endpoint |
PROBLEM_SERVICE_INTERNAL_TOKEN | Judge | Authenticate to hidden-case endpoint |
KAFKA_BOOTSTRAP_SERVERS | Submission, judge | Kafka connection |
JUDGE_EXECUTION_TIMEOUT | Judge | Per-execution timeout, for example PT30S |
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUS | Judge | Sandbox resource limits |
The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.
Build all backend services:
mvn clean packageBuild the frontend:
cd frontend
npm install
npm run buildWith the full stack running, execute the end-to-end test:
python3 scripts/e2e_test.pyThe E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.
The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.
The delivery flow is:
Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster
Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.