CodeArena is a self-hosted code execution sandbox that runs user-submitted code inside isolated Docker containers.
The system demonstrates secure execution handling, job queuing, and real-time output streaming.
- 🔐 GitHub OAuth login with secure HttpOnly JWT cookie session
- 🐳 Docker-isolated sandboxed execution
- ⚡ Multi-languages: Python 3, C# (.NET 8), JavaScript (Node.js), C (GCC), C++ 17
- 📝 Snippet CRUD with tags and unique slugs per user
- ✅ Test cases — run against multiple stdin/stdout pairs
- 🔴 Live output streaming via SignalR WebSockets
- 🐙 Push to GitHub — GitHub repository push integration
- 📊 Swagger - API documentatione
| Layer | Technology |
|---|---|
| API | ASP.NET Core 8, Entity Framework Core, Npgsql(PostgreSQL), SignalR, Serilog logging, FluentValidation |
| Runner | .NET 8 Worker, StackExchange.Redis, Docker container execution, Resource-limited sandbox configuration |
| Frontend | React 18, TypeScript, Vite, Monaco Editor, Zustand, Axios, SignalR JS |
| Database | PostgreSQL 16 |
| Cache / Queue | Redis 7 |
| Proxy | Caddy (prod) / Nginx (alt) (production) |
| **Infrastructure | PostgreSQL 16, Redis 7, Docker Compose |
| Tool | Min Version | Notes |
|---|---|---|
| Docker Desktop | 24+ | Must be running. Enables Docker-in-Docker execution. |
| Docker Compose v2 | bundled with Docker Desktop | Used to start all services |
| WSL 2 | — | Required on Windows for Linux containers |
| Git | any | To clone the repo |
| .NET 8 SDK | 8.0 | Only needed to run tests or develop locally without Docker |
| Node.js | 20 LTS | Only needed for local frontend dev without Docker |
git clone https://github.com/<your-org>/codearena.git
cd codearena- Go to GitHub → Settings → Developer settings → OAuth Apps → New OAuth App
- Set:
- Homepage URL:
http://localhost:5000 - Authorization callback URL:
http://localhost:5000/api/auth/callback
- Homepage URL:
- Note the Client ID and generate a Client Secret
cd infra
cp .env.example .envEdit .env and fill in all values:
POSTGRES_PASSWORD=<strong-password>
REDIS_PASSWORD=<strong-redis-password>
JWT_KEY=<at-least-32-character-random-string>
GITHUB_CLIENT_ID=<your-oauth-app-client-id>
GITHUB_CLIENT_SECRET=<your-oauth-app-client-secret>
RUNNER_TOKEN=<random-shared-secret-between-runner-and-api>Tip: Generate secrets with
openssl rand -base64 32
On Windows (PowerShell):
cd scripts
.\build-runner-images.ps1On Linux / macOS / WSL:
cd scripts
bash build-runner-images.shOn Windows (PowerShell):
cd scripts
.\build-runner-images.ps1This builds 4 Docker images:
codearena-runner-python:latestcodearena-runner-node:latestcodearena-runner-csharp:latestcodearena-runner-c-cpp:latest
cd infra
docker compose up --build| Service | URL |
|---|---|
| Web UI | http://localhost:5173 |
| API | http://localhost:5000 |
| Swagger | http://localhost:5000/swagger |
| Health | http://localhost:5000/health |
cd apps/api
# Fill in appsettings.Development.json with a local Postgres + Redis
dotnet restore
dotnet runThe API runs on http://localhost:5000.
cd apps/web
npm install
npm run devThe dev server runs on http://localhost:5173 and proxies /api and /hubs to http://localhost:5000 (configured in vite.config.ts).
The runner requires Docker access and a running Redis. In development, the worker won't start until RUNNER_TOKEN and ConnectionStrings__Redis are set. Set them in appsettings.Development.json or as environment variables:
cd apps/runner
ConnectionStrings__Redis="localhost:6379,password=<pw>" \
Api__HubUrl="http://localhost:5000/hubs/execution" \
Api__RunnerToken="<RUNNER_TOKEN>" \
dotnet runcd apps/api/Tests
dotnet testTests use xunit + Moq + EF InMemory — no live database or Redis required.
codearena/
├── apps/
│ ├── api/ # ASP.NET Core 8 Web API
│ │ ├── Controllers/ # Auth, Snippets, Execution, GitHub endpoints
│ │ ├── Data/ # AppDbContext + EF migrations (SQL)
│ │ ├── DTOs/ # Request/response records + FluentValidation
│ │ ├── Hubs/ # SignalR ExecutionHub for live output
│ │ ├── Middleware/ # Global exception handler
│ │ ├── Models/ # EF entity classes
│ │ ├── Services/ # Business logic (Auth, Snippet, Execution, GitHub)
│ │ └── Tests/ # xunit unit tests
│ ├── runner/ # .NET Worker Service — dequeues jobs, runs Docker containers
│ │ ├── DockerExecutor.cs # Spawns sandbox containers with strict resource limits
│ │ ├── LanguageRegistry.cs # Maps language names → Docker images + commands
│ │ ├── Worker.cs # Redis queue consumer, SignalR result reporting
│ │ └── SandboxOptions.cs # Configurable timeout, memory, output limits
│ └── web/ # React + TypeScript SPA
│ └── src/
│ ├── api/ # Typed Axios wrappers (client, snippets, execution, github)
│ ├── components/ # Monaco editor, output panel, test results
│ ├── hooks/ # useExecution (poll/run), useSignalR (live stream)
│ ├── pages/ # LoginPage, SnippetsPage, EditorPage
│ ├── store/ # Zustand editor state
│ └── types/ # Shared TypeScript interfaces
├── infra/
│ ├── docker-compose.yml # Development stack
│ ├── docker-compose.prod.yml # Production (with Caddy reverse proxy)
│ ├── .env.example # Template — copy to .env and fill secrets
│ └── runner-images/ # Dockerfiles for per-language sandbox images
└── scripts/
├── build-runner-images.sh # Linux / macOS builder
└── build-runner-images.ps1 # Windows PowerShell builder
- ASP.NET Core 8
- Entity Framework Core
- Npgsql (PostgreSQL)
- FluentValidation
- SignalR
- Serilog logging
- .NET Worker Service
- Redis queue consumption
- Docker container execution
- Resource-limited sandbox configuration
- React 18
- TypeScript
- Vite
- Monaco Editor
- Zustand state management
- PostgreSQL 16
- Redis 7
- Docker Compose
- Optional Caddy reverse proxy (production)
- JWT is stored in an HttpOnly, Secure, SameSite=Strict cookie (
ca_jwt) — never in localStorage - GitHub OAuth tokens are encrypted at rest using ASP.NET Core Data Protection
- Docker containers run with:
--network=none,--read-only,--cap-drop=ALL,--security-opt no-new-privileges,--pids-limit=100, memory and CPU quotas - CSRF is mitigated by SameSite=Strict cookies + CORS allowlist
- Open-redirect protection in OAuth state parameter
cd infra
docker compose up --build
Web UI: http://localhost:5173