A secure, production-ready, multi-language code execution platform using Docker isolation, a Node.js API, and a browser-based interactive UI.
This project executes untrusted code in isolated containers with strict resource and security limits. It supports:
- REST execution (
POST /execute) for request/response runs - WebSocket interactive execution (real-time stdin/stdout via Socket.IO)
- Multiple language runtimes (interpreted + compiled)
- Queueing and concurrency control
- Production-oriented safeguards (rate limiting, timeouts, no-network execution containers)
At the time of writing, this repo supports:
- Python
- JavaScript (Node.js)
- Java
- C++
- C
- Ruby
- Go
- Dart
- PHP
- Rust
- C#
- Scala
- Octave
- Assembly (NASM x86_64)
-
Backend API Server
- File:
server/server.js - Stack: Express + Socket.IO + Dockerode
- Responsibilities:
- Accept execution requests
- Validate and queue work
- Orchestrate Docker containers
- Stream output and accept interactive input
- File:
-
Frontend Web UI
- File:
web-ui/index.html - Stack: Bootstrap + CodeMirror + Socket.IO client
- Responsibilities:
- Language selection
- Editor + sample snippets
- Real-time output rendering
- Interactive stdin input during execution
- File:
-
Language Runtime Containers
- Folder:
languages/<language>/Dockerfile - Responsibilities:
- Provide language/compiler toolchain
- Run as non-root user (
coderunner)
- Folder:
-
Docker Compose Orchestration
- File:
docker-compose.yml - Responsibilities:
- Build and run API + language images
- Mount shared work volume for interactive mode
- Wire network and service lifecycle
- File:
-
Shared Execution Volume
- Volume:
code-workdir - Used by interactive runs to avoid per-request image build overhead.
- Volume:
There are two execution paths:
Flow:
- Client sends
{ language, code, input? }. - Server validates request and enqueues execution.
executeCode()creates a unique temp directory under/tmp/<executionId>.- Server writes source file (language-specific naming: e.g.,
Main.java,main.cpp,script.py). - If input exists, writes
input.txt. - Server generates a tiny temporary Dockerfile (
FROM <base-image>,COPYsource/input). - Builds a per-execution image.
- Runs container with strict limits.
- Collects logs, cleans Docker multiplex headers, sanitizes known benign noise.
- Returns JSON response with output and exit code.
- Cleans up temp directory/container/image.
When to use:
- API-based stateless execution
- Non-interactive integrations
Flow:
- Client emits
execute-interactive. executeCodeInteractive()creates/workdir/<executionId>in shared volume.- Source file is written there.
- Server starts container from prebuilt language image with
WorkingDir=/workdir/<executionId>. - Attaches stream before start (avoids race on fast exits).
- Demuxes stdout/stderr and streams chunks to UI in real time.
- Sends
execution-startwithexecutionId. - Client sends input events tagged with
executionId. - Server routes input only to matching execution stream (prevents stale cross-run input writes).
- On completion/timeout, emits
execution-completeand cleans up.
When to use:
- Programs that ask for input during execution
- Educational/live coding use cases
Execution containers are constrained with Docker HostConfig:
- Memory limit (
100MBdefault; higher for heavy languages) MemorySwapfixed to same limit- CPU cap (
NanoCpus) - PID limit (
PidsLimit: 50) NetworkMode: none(no outbound internet)Privileged: falseSecurityOpt: ['no-new-privileges']CapDrop: ['ALL']- Timeouts with forced stop
Additional protections:
- Rate limiting on
/execute - Request validation (language/code required)
- Non-root runtime users in language images
- Cleanup of temporary artifacts
The API server keeps an in-memory queue:
executionQueue[]currentExecutionsMAX_CONCURRENT_EXECUTIONSenv var
Requests are queued and processed FIFO to avoid uncontrolled parallel execution.
For large-scale production, replace this with Redis/RabbitMQ-backed distributed queueing.
code_execution_server/
├─ docker-compose.yml
├─ server/
│ ├─ Dockerfile
│ ├─ package.json
│ └─ server.js
├─ web-ui/
│ └─ index.html
└─ languages/
├─ python/Dockerfile
├─ javascript/Dockerfile
├─ java/Dockerfile
├─ cpp/Dockerfile
├─ c/Dockerfile
├─ ruby/Dockerfile
├─ go/Dockerfile
├─ dart/Dockerfile
├─ php/Dockerfile
├─ rust/Dockerfile
├─ csharp/Dockerfile
├─ scala/Dockerfile
├─ octave/Dockerfile
└─ assembly/Dockerfile
Request:
{
"language": "python",
"code": "print('Hello')",
"input": "optional stdin"
}Response:
{
"executionId": "uuid",
"status": "success",
"output": "Hello\n",
"exitCode": 0
}Returns list of supported language keys.
Returns:
{ "status": "UP" }Client -> Server:
execute-interactive:{ language, code }input:{ data, executionId }
Server -> Client:
execution-start:{ executionId }output:{ data, type? }wheretypemay bestderrexecution-complete:{ status, exitCode, executionId }error:{ message }
- Docker + Docker Compose
- Port
3000available
docker-compose builddocker-compose up -dhttp://localhost:3000
docker-compose logs -f api-serverdocker-compose downThis platform supports real-time interactive programs via Socket.IO.
- Open
http://localhost:3000 - Select a language
- Click Run Code (Interactive)
- Wait for prompts in output
- Type input in the interactive field and press Enter (or Send)
- Use Stop to terminate long-running executions
- Output streams in real time
- Input you send is echoed in the output area
- Stderr is highlighted separately
- Final status shows success/failure with exit code
- Use clear prompts (example:
Enter your name:) - Flush output when runtime requires it
- Keep interaction short to avoid timeout
- Handle invalid input in code for better UX
- Greeting flows (name/age)
- CLI calculators
- Small games (guessing loops)
- Data entry scripts
- Input validation and request size limits
- Rate limiting enabled
- Non-root execution containers
- No-new-privileges + dropped capabilities
- Network-disabled execution containers
- CPU/memory/process limits per execution
- Execution timeout enforcement
- Health endpoint (
/health) - Queue/concurrency controls
NODE_ENV=production
PORT=3000
MAX_CONCURRENT_EXECUTIONS=5
CORS_ORIGIN=https://yourdomain.com# health
curl http://localhost:3000/health
# service logs
docker-compose logs -f api-server
# container status
docker-compose ps
# live resource usage
docker stats- Put API behind Nginx/Apache in production
- Terminate TLS at reverse proxy
- Forward websocket upgrade headers
- Set proxy timeout above execution timeout
# rebuild and redeploy
docker-compose build --no-cache
docker-compose up -d
# restart api server only
docker-compose restart api-server
# cleanup old images/volumes (careful)
docker image prune -a
docker volume prune- Real-time interactive execution over WebSocket
- Execution-scoped input routing via
executionId - Streaming stdout/stderr demux from Docker
- Expanded multi-language support (including compiled runtimes)
- Output noise reduction for better user-visible I/O
Use this checklist for production-ready onboarding.
Create languages/<language>/Dockerfile:
- Install runtime/compiler
- Create non-root
coderunner - Create
/codeand assign ownership USER coderunnerWORKDIR /code
Example shape:
FROM <base-image>
RUN ... install toolchain ...
RUN groupadd -r coderunner && useradd -r -g coderunner coderunner
RUN mkdir /code && chown coderunner:coderunner /code
USER coderunner
WORKDIR /codeIn server/server.js, add entry:
<language>: {
image: 'code-execution-<language>:latest',
fileExtension: '.<ext>',
command: ['<runtime>', '<entry-file>']
}For compiled languages, use compile + run command (prefer /tmp build dir if shared volume permissions are strict).
If language needs special entry name (Main.java, Program.cs, etc.), update file naming logic in:
executeCode()executeCodeInteractive()
Otherwise default script.<ext> is enough.
Update detectInteractiveCode(language, code) with regex for common stdin APIs in that language.
In batch mode command overrides (if (hasInteractiveInput && input)), add branch for your language so piped input works.
In docker-compose.yml, add:
code-execution-<language>:
build:
context: ./languages/<language>
dockerfile: Dockerfile
image: code-execution-<language>:latest
restart: "no"
networks:
- code-execution-networkIn web-ui/index.html:
- Add
<option value="<language>">...</option> - Add sample snippet in
sampleCode - Add CodeMirror mode in
languageModes
Heavy runtimes/compilers may need 256MB+ in memory limit selection logic.
docker-compose build code-execution-<language> api-server
docker-compose restart api-serverRun at least these tests:
- Hello world
- Interactive stdin sample
- Intentional compile/runtime error
- Timeout behavior
Compile in /tmp/<lang>-build and run output from /tmp instead of mounted workdir.
Execution containers have NetworkMode: none; choose offline command path or pre-bake dependencies in image.
Redirect setup/build stdout on success, but preserve stderr/failure logs for diagnostics.
Check executionId routing between client and server, and verify language stdin detection regex.
- API server currently uses in-memory queue; use external queue for multi-instance scaling.
- Consider stricter CORS origins for production.
- Pin image versions and scan images for vulnerabilities regularly.
- Add persistent observability (metrics/tracing) for enterprise deployments.
MIT