Skip to content

Commit 1ed81c5

Browse files
committed
feat: Add Docker Compose configuration and update environment settings
- Introduced a new `docker-compose.ghcr.yml` file for deploying the application with pre-built images from GitHub Container Registry. - Updated `.env.example` to include `DOCKER_IMAGE_REGISTRY` for better configuration management. - Enhanced the `Settings` class to support dynamic Docker image retrieval based on the specified registry. - Modified `languages.py` to use images without the registry prefix for better compatibility with the new setup. - Updated the README to reflect changes in execution environment setup and provide instructions for using local vs. pre-built images.
1 parent 2929962 commit 1ed81c5

6 files changed

Lines changed: 220 additions & 21 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ MINIO_BUCKET=code-interpreter-files
4444
MINIO_REGION=us-east-1
4545

4646
# Docker Configuration
47+
DOCKER_IMAGE_REGISTRY=code-interpreter
4748
# DOCKER_BASE_URL=unix://var/run/docker.sock
4849
DOCKER_TIMEOUT=60
4950
DOCKER_NETWORK_MODE=none

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ Get up and running in minutes by building the execution environment.
2424
# The default settings work out-of-the-box for local development
2525
```
2626

27-
3. **Build execution environment images**
27+
3. **Prepare execution environment images**
2828

29+
You can either build the images locally (recommended) or pull pre-built images from GitHub Container Registry.
30+
31+
**Option A: Build locally (Recommended)**
2932
```bash
3033
# Build Python only (minimal)
3134
./docker/build-images.sh -l python
@@ -34,11 +37,30 @@ Get up and running in minutes by building the execution environment.
3437
./docker/build-images.sh -p
3538
```
3639

40+
**Option B: Pull from GHCR**
41+
```bash
42+
# Pull Python only
43+
docker pull ghcr.io/usnavy13/librecodeinterpreter/python:latest
44+
45+
# Or pull the API and all languages
46+
docker pull ghcr.io/usnavy13/librecodeinterpreter:latest
47+
for lang in python nodejs go java c-cpp php rust r fortran d; do
48+
docker pull ghcr.io/usnavy13/librecodeinterpreter/$lang:latest
49+
done
50+
```
51+
3752
4. **Start the API**
53+
54+
**Option A: Using local images (if you built them)**
3855
```bash
3956
docker compose up -d
4057
```
4158

59+
**Option B: Using pre-built images (if you pulled them)**
60+
```bash
61+
docker compose -f docker-compose.ghcr.yml up -d
62+
```
63+
4264
The API will be available at `http://localhost:8000`.
4365
Visit `http://localhost:8000/docs` for the interactive API documentation.
4466

docker-compose.ghcr.yml

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
services:
2+
# Code Interpreter API
3+
api:
4+
image: ghcr.io/usnavy13/librecodeinterpreter:latest
5+
container_name: code-interpreter-api
6+
user: "1000:988" # Run as user with docker group access
7+
cap_add:
8+
- NET_ADMIN # Required for iptables management when WAN access is enabled
9+
ports:
10+
- "${API_PORT:-8000}:8000"
11+
- "${HTTPS_PORT:-443}:443"
12+
env_file:
13+
- .env
14+
environment:
15+
# Container-specific overrides (these override .env values)
16+
- API_HOST=0.0.0.0
17+
- API_PORT=8000
18+
- DOCKER_IMAGE_REGISTRY=ghcr.io/usnavy13/librecodeinterpreter
19+
20+
# Service discovery (container names)
21+
- REDIS_HOST=redis
22+
- REDIS_PORT=6379
23+
- MINIO_ENDPOINT=minio:9000
24+
25+
# Docker socket path inside container
26+
- DOCKER_BASE_URL=unix://var/run/docker.sock
27+
28+
# SSL paths inside container
29+
- SSL_CERT_FILE=/app/ssl/cert.pem
30+
- SSL_KEY_FILE=/app/ssl/key.pem
31+
volumes:
32+
- /var/run/docker.sock:/var/run/docker.sock
33+
- ./logs:/app/logs
34+
- ./data:/app/data
35+
- ./ssl:/app/ssl
36+
depends_on:
37+
- redis
38+
- minio
39+
networks:
40+
- code-interpreter-network
41+
restart: unless-stopped
42+
stop_grace_period: 30s # Allow time to cleanup pooled containers
43+
healthcheck:
44+
test:
45+
[
46+
"CMD",
47+
"sh",
48+
"-c",
49+
"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",
50+
]
51+
interval: 30s
52+
timeout: 15s
53+
retries: 3
54+
start_period: 15s
55+
56+
# Redis for session management
57+
redis:
58+
image: redis:7-alpine
59+
container_name: code-interpreter-redis
60+
ports:
61+
# Expose to localhost for CLI tools
62+
- "127.0.0.1:6379:6379"
63+
environment:
64+
- REDIS_PASSWORD=${REDIS_PASSWORD:-}
65+
command: >
66+
sh -c "
67+
if [ -n \"$$REDIS_PASSWORD\" ]; then
68+
redis-server --requirepass $$REDIS_PASSWORD --appendonly yes --appendfsync everysec
69+
else
70+
redis-server --appendonly yes --appendfsync everysec
71+
fi
72+
"
73+
volumes:
74+
- redis-data:/data
75+
- ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
76+
networks:
77+
- code-interpreter-network
78+
restart: unless-stopped
79+
healthcheck:
80+
test: ["CMD", "redis-cli", "ping"]
81+
interval: 30s
82+
timeout: 10s
83+
retries: 3
84+
85+
# MinIO for file storage
86+
minio:
87+
image: minio/minio:latest
88+
container_name: code-interpreter-minio
89+
ports:
90+
# API port for local testing
91+
- "127.0.0.1:9000:9000"
92+
# Console only, bound to localhost (access via SSH tunnel)
93+
- "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"
94+
environment:
95+
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minioadmin}
96+
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minioadmin}
97+
- MINIO_BROWSER_REDIRECT_URL=http://localhost:9001
98+
command: server /data --console-address ":9001"
99+
volumes:
100+
- minio-data:/data
101+
networks:
102+
- code-interpreter-network
103+
restart: unless-stopped
104+
healthcheck:
105+
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
106+
interval: 30s
107+
timeout: 10s
108+
retries: 3
109+
110+
# MinIO bucket initialization
111+
minio-init:
112+
image: minio/mc:latest
113+
container_name: code-interpreter-minio-init
114+
depends_on:
115+
- minio
116+
environment:
117+
- MINIO_ENDPOINT=minio:9000
118+
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
119+
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
120+
- MINIO_BUCKET=${MINIO_BUCKET:-code-interpreter-files}
121+
entrypoint: >
122+
/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
123+
echo 'MinIO not ready, waiting...';
124+
sleep 2;
125+
done; echo 'MinIO is ready. Creating bucket if it does not exist...'; mc mb minio/$$MINIO_BUCKET --ignore-existing; echo 'Bucket setup complete.'; "
126+
networks:
127+
- code-interpreter-network
128+
129+
volumes:
130+
redis-data:
131+
driver: local
132+
minio-data:
133+
driver: local
134+
135+
networks:
136+
code-interpreter-network:
137+
driver: bridge
138+
ipam:
139+
config:
140+
- subnet: 172.20.0.0/16
141+
142+
# WAN-only network for execution containers that need internet access
143+
# This network is only created when ENABLE_WAN_ACCESS=true
144+
code-interpreter-wan:
145+
driver: bridge
146+
ipam:
147+
config:
148+
- subnet: 172.30.0.0/16
149+
driver_opts:
150+
# Enable NAT for outbound internet access
151+
com.docker.network.bridge.enable_ip_masquerade: "true"
152+
# Disable inter-container communication
153+
com.docker.network.bridge.enable_icc: "false"
154+
labels:
155+
com.code-interpreter.managed: "true"
156+
com.code-interpreter.type: "wan-access"

src/config/__init__.py

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class Settings(BaseSettings):
111111

112112
# Docker Configuration
113113
docker_base_url: Optional[str] = Field(default=None)
114+
docker_image_registry: str = Field(
115+
default="code-interpreter",
116+
description="Registry/namespace prefix for execution environment images",
117+
)
114118
docker_timeout: int = Field(default=60, ge=10)
115119
docker_network_mode: str = Field(default="none")
116120
docker_security_opt: List[str] = Field(
@@ -384,16 +388,23 @@ class Settings(BaseSettings):
384388
enable_filesystem_isolation: bool = Field(default=True)
385389

386390
# Language Configuration - now uses LANGUAGES from languages.py
387-
supported_languages: Dict[str, Dict[str, Any]] = Field(
388-
default_factory=lambda: {
391+
supported_languages: Dict[str, Dict[str, Any]] = Field(default_factory=dict)
392+
393+
@validator("supported_languages", pre=True, always=True)
394+
def _set_supported_languages(cls, v, values):
395+
"""Initialize supported_languages with registry-prefixed images."""
396+
if v:
397+
return v
398+
399+
registry = values.get("docker_image_registry", "code-interpreter")
400+
return {
389401
code: {
390-
"image": lang.image,
402+
"image": f"{registry}/{lang.image}" if registry else lang.image,
391403
"timeout_multiplier": lang.timeout_multiplier,
392404
"memory_multiplier": lang.memory_multiplier,
393405
}
394406
for code, lang in LANGUAGES.items()
395407
}
396-
)
397408

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

594+
def get_image_for_language(self, code: str) -> str:
595+
"""Get Docker image for a language."""
596+
config = self.get_language_config(code)
597+
if config and "image" in config:
598+
return config["image"]
599+
600+
# Fallback to languages.py logic if not in settings
601+
from .languages import get_image_for_language as get_img
602+
return get_img(code, registry=self.docker_image_registry)
603+
583604
def get_execution_timeout(self, language: str) -> int:
584605
"""Get execution timeout for a specific language."""
585606
multiplier = self.get_language_config(language).get("timeout_multiplier", 1.0)

src/config/languages.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class LanguageConfig:
3434
"py": LanguageConfig(
3535
code="py",
3636
name="Python",
37-
image="code-interpreter/python:latest",
37+
image="python:latest",
3838
user_id=999,
3939
file_extension="py",
4040
execution_command="python3 -",
@@ -45,7 +45,7 @@ class LanguageConfig:
4545
"js": LanguageConfig(
4646
code="js",
4747
name="JavaScript",
48-
image="code-interpreter/nodejs:latest",
48+
image="nodejs:latest",
4949
user_id=1001,
5050
file_extension="js",
5151
execution_command="node",
@@ -56,7 +56,7 @@ class LanguageConfig:
5656
"ts": LanguageConfig(
5757
code="ts",
5858
name="TypeScript",
59-
image="code-interpreter/nodejs:latest",
59+
image="nodejs:latest",
6060
user_id=1001,
6161
file_extension="ts",
6262
execution_command="tsc /mnt/data/code.ts --outDir /mnt/data --module commonjs "
@@ -68,7 +68,7 @@ class LanguageConfig:
6868
"go": LanguageConfig(
6969
code="go",
7070
name="Go",
71-
image="code-interpreter/go:latest",
71+
image="go:latest",
7272
user_id=1001,
7373
file_extension="go",
7474
execution_command="go build -o code code.go && ./code",
@@ -79,7 +79,7 @@ class LanguageConfig:
7979
"java": LanguageConfig(
8080
code="java",
8181
name="Java",
82-
image="code-interpreter/java:latest",
82+
image="java:latest",
8383
user_id=999,
8484
file_extension="java",
8585
execution_command="javac Code.java && java Code",
@@ -90,7 +90,7 @@ class LanguageConfig:
9090
"c": LanguageConfig(
9191
code="c",
9292
name="C",
93-
image="code-interpreter/c-cpp:latest",
93+
image="c-cpp:latest",
9494
user_id=1001,
9595
file_extension="c",
9696
execution_command="gcc -o code code.c && ./code",
@@ -101,7 +101,7 @@ class LanguageConfig:
101101
"cpp": LanguageConfig(
102102
code="cpp",
103103
name="C++",
104-
image="code-interpreter/c-cpp:latest",
104+
image="c-cpp:latest",
105105
user_id=1001,
106106
file_extension="cpp",
107107
execution_command="g++ -o code code.cpp && ./code",
@@ -112,7 +112,7 @@ class LanguageConfig:
112112
"php": LanguageConfig(
113113
code="php",
114114
name="PHP",
115-
image="code-interpreter/php:latest",
115+
image="php:latest",
116116
user_id=1001,
117117
file_extension="php",
118118
execution_command="php",
@@ -123,7 +123,7 @@ class LanguageConfig:
123123
"rs": LanguageConfig(
124124
code="rs",
125125
name="Rust",
126-
image="code-interpreter/rust:latest",
126+
image="rust:latest",
127127
user_id=1001,
128128
file_extension="rs",
129129
execution_command="rustc code.rs -o code && ./code",
@@ -134,7 +134,7 @@ class LanguageConfig:
134134
"r": LanguageConfig(
135135
code="r",
136136
name="R",
137-
image="code-interpreter/r:latest",
137+
image="r:latest",
138138
user_id=1001,
139139
file_extension="r",
140140
execution_command="Rscript /dev/stdin",
@@ -145,7 +145,7 @@ class LanguageConfig:
145145
"f90": LanguageConfig(
146146
code="f90",
147147
name="Fortran",
148-
image="code-interpreter/fortran:latest",
148+
image="fortran:latest",
149149
user_id=1001,
150150
file_extension="f90",
151151
execution_command="gfortran -o code code.f90 && ./code",
@@ -156,7 +156,7 @@ class LanguageConfig:
156156
"d": LanguageConfig(
157157
code="d",
158158
name="D",
159-
image="code-interpreter/d:latest",
159+
image="d:latest",
160160
user_id=0,
161161
file_extension="d",
162162
execution_command="ldc2 code.d -of=code && ./code",
@@ -183,11 +183,11 @@ def is_supported_language(code: str) -> bool:
183183

184184

185185
# Convenience lookups for backward compatibility during transition
186-
def get_image_for_language(code: str) -> str:
186+
def get_image_for_language(code: str, registry: Optional[str] = None) -> str:
187187
"""Get Docker image for a language."""
188188
lang = get_language(code)
189189
if lang:
190-
return lang.image
190+
return f"{registry}/{lang.image}" if registry else lang.image
191191
raise ValueError(f"Unsupported language: {code}")
192192

193193

0 commit comments

Comments
 (0)