This document describes how to build and manage the Docker images used by the code executor.
Dockerfile: docker/runner-c.Dockerfile
Purpose: Compiles C code using GCC with optimization flags Base: Alpine Linux Key packages: gcc, musl-dev Build command:
docker build -f docker/runner-c.Dockerfile -t runner-c .Dockerfile: docker/runner-py.Dockerfile
Purpose: Executes Python 3 code Base: Alpine Linux with Python 3 Key packages: python3 Build command:
docker build -f docker/runner-py.Dockerfile -t runner-py .Dockerfile: docker/runner-runtime.Dockerfile
Purpose: Final execution environment for compiled binaries and Python scripts Base: Alpine Linux with gVisor support Key features: Non-root user, minimal attack surface, seccomp support Build command:
docker build -f docker/runner-runtime.Dockerfile -t runner-runtime .# Build all images at once
docker build -f docker/runner-c.Dockerfile -t runner-c .
docker build -f docker/runner-py.Dockerfile -t runner-py .
docker build -f docker/runner-runtime.Dockerfile -t runner-runtime .The executor uses gVisor (runsc runtime) for enhanced container isolation:
# Install runsc on Linux
curl -fsSL https://gvisor.dev/archive/latest/runsc > /usr/local/bin/runsc
chmod +x /usr/local/bin/runsc
# Verify installation
runsc --versionIf runsc is properly registered with Docker, the code executor will detect it on startup automatically using docker info. If you wish to disable gVisor despite having it installed, you can set DISABLE_GVISOR=true in your .env file.
Ensure Docker daemon can use gVisor runtime by configuring /etc/docker/daemon.json:
{
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}Restart Docker: sudo systemctl restart docker
The executor includes a seccomp profile at seccomp-runtime.json for syscall filtering. Ensure this file exists in the project root.
- Minimal Images: All images use Alpine Linux to minimize attack surface
- Non-root User: Execution runs as
runneruser, not root - Read-only Filesystem:
/is read-only except for/tmp - No Network:
--network=nonefor all containers - Dropped Capabilities: All Linux capabilities are dropped
- Resource Limits:
- Memory: 64MB
- CPU: 0.5 cores
- Processes: 32
- Create new Dockerfile in
docker/runner-<lang>.Dockerfile - Use Alpine Linux base
- Install language runtime/compiler
- Copy to
runner-runtimefor execution - Add handler in
runner/run<Lang>.js - Update
runner/runCode.jsto dispatch to new runner
Example template:
FROM alpine:latest
RUN apk add --no-cache <language-packages>
WORKDIR /appEdit the centralized limits in src/config/index.js under the sandbox section, which applies to all runners automatically:
sandbox: Object.freeze({
memoryLimit: "64m",
cpuLimit: "0.5",
pidsLimit: "32",
// ...
}),# Check Docker logs
docker build --progress=plain -f docker/runner-c.Dockerfile -t runner-c .
# Verify base image availability
docker pull alpine:latest# Verify runsc is available
which runsc
runsc --version
# Check Docker daemon logs
journalctl -u docker -n 50# Test image directly
docker run --rm -it runner-runtime /bin/sh
# Test with explicit runtime
docker run --runtime=runsc --rm alpine echo "test"Regularly rebuild images to include security patches:
# Update base images
docker pull alpine:latest
# Rebuild all images
docker build --no-cache -f docker/runner-c.Dockerfile -t runner-c .
docker build --no-cache -f docker/runner-py.Dockerfile -t runner-py .
docker build --no-cache -f docker/runner-runtime.Dockerfile -t runner-runtime .Current image sizes:
runner-c: ~200MBrunner-py: ~150MBrunner-runtime: ~10MB
Optimize with:
- Multi-stage builds
- Alpine Linux (already used)
- Minimal package installations