Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ MINIO_BUCKET=code-interpreter-files
MINIO_REGION=us-east-1

# Docker Configuration
DOCKER_IMAGE_REGISTRY=code-interpreter
# DOCKER_BASE_URL=unix://var/run/docker.sock
DOCKER_TIMEOUT=60
DOCKER_NETWORK_MODE=none
Expand All @@ -52,7 +53,8 @@ DOCKER_READ_ONLY=true
# Resource Limits - Execution
MAX_EXECUTION_TIME=30
MAX_MEMORY_MB=512
MAX_CPU_QUOTA=50000
MAX_CPUS=1
MAX_CPU_QUOTA=50000 #Deprecated
MAX_PROCESSES=32
MAX_OPEN_FILES=1024

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/execution-env-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ jobs:
context: docker
file: docker/${{ matrix.language }}.Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
Expand Down
28 changes: 23 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A secure, open-source code interpreter API that provides sandboxed code executio

## Quick Start

Get up and running in minutes using prebuilt Docker images.
Get up and running in minutes by building the execution environment.

1. **Clone the repository**

Expand All @@ -24,24 +24,42 @@ Get up and running in minutes using prebuilt Docker images.
# The default settings work out-of-the-box for local development
```

3. **Pull execution environment images**
3. **Prepare execution environment images**

You can either build the images locally (recommended) or pull pre-built images from GitHub Container Registry.

**Option A: Build locally (Recommended)**
```bash
# Build Python only (minimal)
./docker/build-images.sh -l python

# Or build all 12 languages
./docker/build-images.sh -p
```

**Option B: Pull from GHCR**
```bash
# Python only (minimal)
# Pull Python only
docker pull ghcr.io/usnavy13/librecodeinterpreter/python:latest

# Or pull all 12 languages
# Or pull the API and all languages
docker pull ghcr.io/usnavy13/librecodeinterpreter:latest
for lang in python nodejs go java c-cpp php rust r fortran d; do
docker pull ghcr.io/usnavy13/librecodeinterpreter/$lang:latest
done
```

4. **Start the API**

**Option A: Using local images (if you built them)**
```bash
docker compose up -d
```

> **Building from source?** See the [Development Guide](docs/DEVELOPMENT.md) for instructions on building images locally.
**Option B: Using pre-built images (if you pulled them)**
```bash
docker compose -f docker-compose.ghcr.yml up -d
```

The API will be available at `http://localhost:8000`.
Visit `http://localhost:8000/docs` for the interactive API documentation.
Expand Down
156 changes: 156 additions & 0 deletions docker-compose.ghcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
services:
# Code Interpreter API
api:
image: ghcr.io/usnavy13/librecodeinterpreter:latest
container_name: code-interpreter-api
user: "1000:988" # Run as user with docker group access
cap_add:
- NET_ADMIN # Required for iptables management when WAN access is enabled
ports:
- "${API_PORT:-8000}:8000"
- "${HTTPS_PORT:-443}:443"
env_file:
- .env
environment:
# Container-specific overrides (these override .env values)
- API_HOST=0.0.0.0
- API_PORT=8000
- DOCKER_IMAGE_REGISTRY=ghcr.io/usnavy13/librecodeinterpreter

# Service discovery (container names)
- REDIS_HOST=redis
- REDIS_PORT=6379
- MINIO_ENDPOINT=minio:9000

# Docker socket path inside container
- DOCKER_BASE_URL=unix://var/run/docker.sock

# SSL paths inside container
- SSL_CERT_FILE=/app/ssl/cert.pem
- SSL_KEY_FILE=/app/ssl/key.pem
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./logs:/app/logs
- ./data:/app/data
- ./ssl:/app/ssl
depends_on:
- redis
- minio
networks:
- code-interpreter-network
restart: unless-stopped
stop_grace_period: 30s # Allow time to cleanup pooled containers
healthcheck:
test:
[
"CMD",
"sh",
"-c",
"curl -f -k https://localhost:443/health 2>/dev/null || curl -f http://localhost:8000/health 2>/dev/null || curl -f http://localhost:80/health || exit 1",
]
interval: 30s
timeout: 15s
retries: 3
start_period: 15s

# Redis for session management
redis:
image: redis:7-alpine
container_name: code-interpreter-redis
ports:
# Expose to localhost for CLI tools
- "127.0.0.1:6379:6379"
environment:
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
command: >
sh -c "
if [ -n \"$$REDIS_PASSWORD\" ]; then
redis-server --requirepass $$REDIS_PASSWORD --appendonly yes --appendfsync everysec
else
redis-server --appendonly yes --appendfsync everysec
fi
"
volumes:
- redis-data:/data
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
networks:
- code-interpreter-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 30s
timeout: 10s
retries: 3

# MinIO for file storage
minio:
image: minio/minio:latest
container_name: code-interpreter-minio
ports:
# API port for local testing
- "127.0.0.1:9000:9000"
# Console only, bound to localhost (access via SSH tunnel)
- "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"
environment:
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minioadmin}
- MINIO_BROWSER_REDIRECT_URL=http://localhost:9001
command: server /data --console-address ":9001"
volumes:
- minio-data:/data
networks:
- code-interpreter-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 10s
retries: 3

# MinIO bucket initialization
minio-init:
image: minio/mc:latest
container_name: code-interpreter-minio-init
depends_on:
- minio
environment:
- MINIO_ENDPOINT=minio:9000
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
- MINIO_BUCKET=${MINIO_BUCKET:-code-interpreter-files}
entrypoint: >
/bin/sh -c " echo 'Waiting for MinIO to be ready...'; until mc alias set minio http://$$MINIO_ENDPOINT $$MINIO_ACCESS_KEY $$MINIO_SECRET_KEY; do
echo 'MinIO not ready, waiting...';
sleep 2;
done; echo 'MinIO is ready. Creating bucket if it does not exist...'; mc mb minio/$$MINIO_BUCKET --ignore-existing; echo 'Bucket setup complete.'; "
networks:
- code-interpreter-network

volumes:
redis-data:
driver: local
minio-data:
driver: local

networks:
code-interpreter-network:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16

# WAN-only network for execution containers that need internet access
# This network is only created when ENABLE_WAN_ACCESS=true
code-interpreter-wan:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/16
driver_opts:
# Enable NAT for outbound internet access
com.docker.network.bridge.enable_ip_masquerade: "true"
# Disable inter-container communication
com.docker.network.bridge.enable_icc: "false"
labels:
com.code-interpreter.managed: "true"
com.code-interpreter.type: "wan-access"
2 changes: 1 addition & 1 deletion src/api/dashboard_metrics.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Dashboard metrics API endpoints for advanced analytics."""

from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Literal, Optional
from typing import Dict, List, Literal, Optional

from fastapi import APIRouter, Depends, Header, HTTPException, Query
from pydantic import BaseModel
Expand Down
5 changes: 0 additions & 5 deletions src/api/exec.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@

from ..models import ExecRequest, ExecResponse
from ..services.orchestrator import ExecutionOrchestrator
from ..services.interfaces import (
SessionServiceInterface,
ExecutionServiceInterface,
FileServiceInterface,
)
from ..dependencies.services import (
SessionServiceDep,
FileServiceDep,
Expand Down
13 changes: 2 additions & 11 deletions src/api/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,18 @@
# Standard library imports
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Union
from typing import List, Optional
from urllib.parse import quote

# Third-party imports
import structlog
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, Form, Query
from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Query
from fastapi.responses import Response, StreamingResponse
from unidecode import unidecode

# Local application imports
from ..config import settings
from ..dependencies import FileServiceDep
from ..models import (
FileUploadRequest,
FileUploadResponse,
FileInfo,
FileListResponse,
FileDownloadResponse,
FileDeleteResponse,
)
from ..services.interfaces import FileServiceInterface
from ..utils.id_generator import generate_session_id


Expand Down
11 changes: 5 additions & 6 deletions src/api/health.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""Health check and monitoring endpoints."""

from typing import Dict, Any
from fastapi import APIRouter, HTTPException, Depends, Query
from fastapi.responses import JSONResponse
import structlog
Expand Down Expand Up @@ -42,11 +41,11 @@ async def detailed_health_check(
# Prepare response
response_data = {
"status": overall_status.value,
"timestamp": service_results[
list(service_results.keys())[0]
].timestamp.isoformat()
if service_results
else None,
"timestamp": (
service_results[list(service_results.keys())[0]].timestamp.isoformat()
if service_results
else None
),
"services": {
name: result.to_dict() for name, result in service_results.items()
},
Expand Down
29 changes: 25 additions & 4 deletions src/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ class Settings(BaseSettings):

# Docker Configuration
docker_base_url: Optional[str] = Field(default=None)
docker_image_registry: str = Field(
default="code-interpreter",
description="Registry/namespace prefix for execution environment images",
)
docker_timeout: int = Field(default=60, ge=10)
docker_network_mode: str = Field(default="none")
docker_security_opt: List[str] = Field(
Expand Down Expand Up @@ -384,16 +388,23 @@ class Settings(BaseSettings):
enable_filesystem_isolation: bool = Field(default=True)

# Language Configuration - now uses LANGUAGES from languages.py
supported_languages: Dict[str, Dict[str, Any]] = Field(
default_factory=lambda: {
supported_languages: Dict[str, Dict[str, Any]] = Field(default_factory=dict)

@validator("supported_languages", pre=True, always=True)
def _set_supported_languages(cls, v, values):
"""Initialize supported_languages with registry-prefixed images."""
if v:
return v

registry = values.get("docker_image_registry", "code-interpreter")
return {
code: {
"image": lang.image,
"image": f"{registry}/{lang.image}" if registry else lang.image,
"timeout_multiplier": lang.timeout_multiplier,
"memory_multiplier": lang.memory_multiplier,
}
for code, lang in LANGUAGES.items()
}
)

# Logging Configuration
log_level: str = Field(default="INFO")
Expand Down Expand Up @@ -580,6 +591,16 @@ def get_language_config(self, language: str) -> Dict[str, Any]:
"""Get configuration for a specific language."""
return self.supported_languages.get(language, {})

def get_image_for_language(self, code: str) -> str:
"""Get Docker image for a language."""
config = self.get_language_config(code)
if config and "image" in config:
return config["image"]

# Fallback to languages.py logic if not in settings
from .languages import get_image_for_language as get_img
return get_img(code, registry=self.docker_image_registry)

def get_execution_timeout(self, language: str) -> int:
"""Get execution timeout for a specific language."""
multiplier = self.get_language_config(language).get("timeout_multiplier", 1.0)
Expand Down
Loading
Loading