Skip to content

Commit 64b4494

Browse files
committed
refactor: Replace MinIO with S3-compatible storage
- Updated configuration and environment variables to transition from MinIO to S3 storage, including changes to .env.example and Docker Compose files. - Introduced a new S3Config class for managing S3 settings and removed the MinIO configuration. - Refactored file management and state archival services to utilize the S3 client, ensuring compatibility with S3 operations. - Adjusted health checks and service dependencies to reflect the new S3 storage integration. - Updated documentation and comments throughout the codebase to replace references to MinIO with S3.
1 parent 836a352 commit 64b4494

34 files changed

Lines changed: 512 additions & 535 deletions

.env.example

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ REDIS_PORT=6379
3535
# REDIS_PASSWORD=
3636
# REDIS_URL=redis://localhost:6379/0 # Alternative to individual settings
3737

38-
# ── MinIO / S3 ─────────────────────────────────────────────────
39-
MINIO_ENDPOINT=localhost:9000
40-
MINIO_ACCESS_KEY=minioadmin
41-
MINIO_SECRET_KEY=minioadmin
42-
# MINIO_SECURE=false
43-
# MINIO_BUCKET=code-interpreter-files
38+
# ── S3 Storage (Garage) ────────────────────────────────────────
39+
S3_ENDPOINT=localhost:3900
40+
S3_ACCESS_KEY=minioadmin
41+
S3_SECRET_KEY=minioadmin
42+
# S3_SECURE=false
43+
# S3_BUCKET=code-interpreter-files
44+
# S3_REGION=garage
4445

4546
# ── Execution Limits ───────────────────────────────────────────
4647
# MAX_EXECUTION_TIME=30 # Seconds (default: 30)

docker-compose.prod.yml

Lines changed: 21 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ services:
99
- SYS_ADMIN
1010
# NET_ADMIN required to install iptables egress rules for sandbox uid
1111
# when ENABLE_SANDBOX_NETWORK=true. Restricts sandbox traffic to the
12-
# inline allowlist proxy and prevents SSRF to Redis/MinIO/etc.
12+
# inline allowlist proxy and prevents SSRF to Redis/S3/etc.
1313
- NET_ADMIN
1414
security_opt:
1515
- apparmor:unconfined
@@ -19,7 +19,7 @@ services:
1919
- .env
2020
environment:
2121
- REDIS_HOST=redis
22-
- MINIO_ENDPOINT=minio:9000
22+
- S3_ENDPOINT=garage:3900
2323
volumes:
2424
- sandbox-data:/var/lib/code-interpreter/sandboxes
2525
# Persistent skill-deps cache: pip/npm/go/cargo install here when
@@ -34,8 +34,8 @@ services:
3434
depends_on:
3535
redis:
3636
condition: service_healthy
37-
minio-init:
38-
condition: service_completed_successfully
37+
garage:
38+
condition: service_healthy
3939
healthcheck:
4040
test: ["CMD-SHELL", "curl -fs http://localhost:8000/health || curl -fsk https://localhost:8000/health"]
4141
interval: 30s
@@ -63,43 +63,33 @@ services:
6363
timeout: 5s
6464
retries: 5
6565

66-
minio:
67-
image: minio/minio:latest
68-
container_name: code-interpreter-minio
66+
# Garage S3-compatible object storage (replaces MinIO)
67+
garage:
68+
image: dxflrs/garage:v2.3.0
69+
container_name: code-interpreter-garage
6970
restart: unless-stopped
71+
command: >
72+
server
73+
--single-node
74+
--default-bucket ${S3_BUCKET:-code-interpreter-files}
7075
ports:
71-
- "127.0.0.1:${MINIO_PORT:-9000}:9000"
72-
- "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"
76+
- "127.0.0.1:${S3_PORT:-3900}:3900"
77+
- "127.0.0.1:${GARAGE_ADMIN_PORT:-3903}:3903"
7378
environment:
74-
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
75-
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
76-
command: server /data --console-address ":9001"
79+
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-minioadmin}
80+
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-minioadmin}
7781
volumes:
78-
- minio-data:/data
82+
- garage-data:/var/lib/garage
83+
- ./garage.toml:/etc/garage.toml
7984
healthcheck:
80-
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
85+
test: ["CMD-SHELL", "curl -sf http://localhost:3900 || exit 1"]
8186
interval: 10s
8287
timeout: 5s
8388
retries: 5
84-
85-
minio-init:
86-
image: minio/mc:latest
87-
depends_on:
88-
minio:
89-
condition: service_healthy
90-
entrypoint: >
91-
/bin/sh -c "
92-
mc alias set myminio http://minio:9000 $${MINIO_ACCESS_KEY:-minioadmin} $${MINIO_SECRET_KEY:-minioadmin};
93-
mc mb --ignore-existing myminio/$${MINIO_BUCKET:-code-interpreter-files};
94-
exit 0;
95-
"
96-
environment:
97-
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
98-
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
99-
MINIO_BUCKET: ${MINIO_BUCKET:-code-interpreter-files}
89+
start_period: 10s
10090

10191
volumes:
10292
sandbox-data:
10393
skill-deps:
10494
redis-data:
105-
minio-data:
95+
garage-data:

docker-compose.yml

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ services:
2323
environment:
2424
# Container-specific overrides (service discovery within compose network)
2525
- REDIS_HOST=redis
26-
- MINIO_ENDPOINT=minio:9000
26+
- S3_ENDPOINT=garage:3900
2727
volumes:
2828
- sandbox-data:/var/lib/code-interpreter/sandboxes
2929
# SSL_CERTS_PATH is a host path; SSL_CERT_FILE and SSL_KEY_FILE must point
@@ -34,8 +34,8 @@ services:
3434
depends_on:
3535
redis:
3636
condition: service_healthy
37-
minio-init:
38-
condition: service_completed_successfully
37+
garage:
38+
condition: service_healthy
3939
healthcheck:
4040
test: ["CMD-SHELL", "curl -fs http://localhost:8000/health || curl -fsk https://localhost:8000/health"]
4141
interval: 30s
@@ -65,44 +65,32 @@ services:
6565
timeout: 5s
6666
retries: 5
6767

68-
# MinIO for file storage
69-
minio:
70-
image: minio/minio:latest
71-
container_name: code-interpreter-minio
68+
# Garage S3-compatible object storage (replaces MinIO)
69+
garage:
70+
image: dxflrs/garage:v2.3.0
71+
container_name: code-interpreter-garage
7272
restart: unless-stopped
73+
command: >
74+
server
75+
--single-node
76+
--default-bucket ${S3_BUCKET:-code-interpreter-files}
7377
ports:
74-
- "127.0.0.1:${MINIO_PORT:-9000}:9000"
75-
- "127.0.0.1:${MINIO_CONSOLE_PORT:-9001}:9001"
78+
- "127.0.0.1:${S3_PORT:-3900}:3900"
79+
- "127.0.0.1:${GARAGE_ADMIN_PORT:-3903}:3903"
7680
environment:
77-
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
78-
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
79-
command: server /data --console-address ":9001"
81+
GARAGE_DEFAULT_ACCESS_KEY: ${S3_ACCESS_KEY:-minioadmin}
82+
GARAGE_DEFAULT_SECRET_KEY: ${S3_SECRET_KEY:-minioadmin}
8083
volumes:
81-
- minio-data:/data
84+
- garage-data:/var/lib/garage
85+
- ./garage.toml:/etc/garage.toml
8286
healthcheck:
83-
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
87+
test: ["CMD-SHELL", "curl -sf http://localhost:3900 || exit 1"]
8488
interval: 10s
8589
timeout: 5s
8690
retries: 5
87-
88-
# MinIO bucket initialization
89-
minio-init:
90-
image: minio/mc:latest
91-
depends_on:
92-
minio:
93-
condition: service_healthy
94-
entrypoint: >
95-
/bin/sh -c "
96-
mc alias set myminio http://minio:9000 $${MINIO_ACCESS_KEY:-minioadmin} $${MINIO_SECRET_KEY:-minioadmin};
97-
mc mb --ignore-existing myminio/$${MINIO_BUCKET:-code-interpreter-files};
98-
exit 0;
99-
"
100-
environment:
101-
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
102-
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
103-
MINIO_BUCKET: ${MINIO_BUCKET:-code-interpreter-files}
91+
start_period: 10s
10492

10593
volumes:
10694
sandbox-data:
10795
redis-data:
108-
minio-data:
96+
garage-data:

garage.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
metadata_dir = "/var/lib/garage/meta"
2+
data_dir = "/var/lib/garage/data"
3+
db_engine = "sqlite"
4+
replication_factor = 1
5+
6+
[s3_api]
7+
s3_region = "garage"
8+
api_bind_addr = "[::]:3900"
9+
10+
[admin]
11+
api_bind_addr = "[::]:3903"

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ redis==7.2.0
1616
# SQLite async support for metrics
1717
aiosqlite>=0.19.0
1818

19-
# MinIO/S3 client
20-
minio==7.2.20
19+
# S3 storage client (Garage/any S3-compatible backend)
20+
boto3>=1.35.0
2121

2222
# Date/time parsing utilities
2323
python-dateutil==2.9.0.post0

src/api/exec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ async def execute_code(
6464
within the same session, whether the caller supplies `session_id` directly
6565
or the orchestrator reuses a session through same-user file references or
6666
`entity_id` continuity. State is stored in Redis (2 hour TTL) with
67-
automatic archival to MinIO for long-term storage (7 day TTL).
67+
automatic archival to S3 for long-term storage (configurable TTL).
6868
6969
Returns a streaming response that sends keepalive whitespace before the
7070
JSON body to prevent client socket timeouts during long operations.

src/api/files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ async def upload_file(
135135
# Sanitize filename to match what will be used in container
136136
sanitized_name = OutputProcessor.sanitize_filename(file.filename)
137137

138-
# Store with sanitized name so MinIO, sandbox, and cleanup all use the same name
138+
# Store with sanitized name so S3, sandbox, and cleanup all use the same name
139139
file_id = await file_service.store_uploaded_file(
140140
session_id=session_id,
141141
filename=sanitized_name,

src/api/health.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,25 +116,25 @@ async def redis_health_check(_: str = Depends(verify_api_key)):
116116
)
117117

118118

119-
@router.get("/health/minio", summary="MinIO health check")
120-
async def minio_health_check(_: str = Depends(verify_api_key)):
121-
"""Check MinIO/S3 connectivity and performance."""
119+
@router.get("/health/s3", summary="S3 storage health check")
120+
async def s3_health_check(_: str = Depends(verify_api_key)):
121+
"""Check S3 storage connectivity and performance."""
122122
try:
123-
result = await health_service.check_minio()
123+
result = await health_service.check_s3()
124124

125125
if result.status == HealthStatus.UNHEALTHY:
126126
return JSONResponse(status_code=503, content=result.to_dict())
127127
else:
128128
return JSONResponse(status_code=200, content=result.to_dict())
129129

130130
except Exception as e:
131-
logger.error("MinIO health check failed", error=str(e))
131+
logger.error("S3 health check failed", error=str(e))
132132
return JSONResponse(
133133
status_code=503,
134134
content={
135-
"service": "minio",
135+
"service": "s3",
136136
"status": "unhealthy",
137-
"error": str(e) if settings.api_debug else "MinIO check failed",
137+
"error": str(e) if settings.api_debug else "S3 check failed",
138138
},
139139
)
140140

src/config/__init__.py

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
# Import grouped configurations
3030
from .api import APIConfig
3131
from .redis import RedisConfig
32-
from .minio import MinIOConfig
32+
from .s3 import S3Config
3333
from .security import SecurityConfig
3434
from .resources import ResourcesConfig
3535
from .logging import LoggingConfig
@@ -143,12 +143,13 @@ class Settings(BaseSettings):
143143
redis_socket_timeout: int = Field(default=5, ge=1)
144144
redis_socket_connect_timeout: int = Field(default=5, ge=1)
145145

146-
# MinIO/S3 Configuration
147-
minio_endpoint: str = Field(default="localhost:9000")
148-
minio_access_key: str = Field(default="test-access-key", min_length=3)
149-
minio_secret_key: str = Field(default="test-secret-key", min_length=8)
150-
minio_secure: bool = Field(default=False)
151-
minio_bucket: str = Field(default="code-interpreter-files")
146+
# S3 Storage Configuration
147+
s3_endpoint: str = Field(default="localhost:3900")
148+
s3_access_key: str = Field(default="test-access-key", min_length=3)
149+
s3_secret_key: str = Field(default="test-secret-key", min_length=8)
150+
s3_secure: bool = Field(default=False)
151+
s3_bucket: str = Field(default="code-interpreter-files")
152+
s3_region: str = Field(default="garage")
152153

153154
# Sandbox (nsjail) Configuration
154155
nsjail_binary: str = Field(
@@ -196,7 +197,7 @@ class Settings(BaseSettings):
196197
# Session Configuration
197198
session_ttl_hours: int = Field(default=24, ge=1, le=168)
198199
session_cleanup_interval_minutes: int = Field(default=60, ge=1, le=1440)
199-
enable_orphan_minio_cleanup: bool = Field(default=True)
200+
enable_orphan_s3_cleanup: bool = Field(default=True)
200201

201202
# Sandbox Pool Configuration
202203
sandbox_pool_enabled: bool = Field(default=True)
@@ -250,24 +251,24 @@ class Settings(BaseSettings):
250251
default=100,
251252
ge=1,
252253
le=500,
253-
description="Max state size (MB, raw bytes) for Redis storage. Larger states go directly to MinIO",
254+
description="Max state size (MB, raw bytes) for Redis storage. Larger states go directly to S3 cold storage",
254255
)
255256

256-
# State Archival Configuration - Hybrid Redis + MinIO storage
257+
# State Archival Configuration - Hybrid Redis + S3 storage
257258
state_archive_enabled: bool = Field(
258-
default=True, description="Enable archiving inactive states from Redis to MinIO"
259+
default=True, description="Enable archiving inactive states from Redis to S3"
259260
)
260261
state_archive_after_seconds: int = Field(
261262
default=3600,
262263
ge=300,
263264
le=86400,
264-
description="Archive state to MinIO after this many seconds of inactivity. Default: 1 hour",
265+
description="Archive state to S3 after this many seconds of inactivity. Default: 1 hour",
265266
)
266267
state_archive_ttl_days: int = Field(
267268
default=1,
268269
ge=1,
269270
le=30,
270-
description="Keep archived states in MinIO for N days. Default: 1 (24 hours)",
271+
description="Keep archived states in S3 for N days. Default: 1 (24 hours)",
271272
)
272273
state_archive_check_interval_seconds: int = Field(
273274
default=300,
@@ -449,12 +450,12 @@ def parse_api_keys(cls, v):
449450
"""Parse comma-separated API keys into a list."""
450451
return [key.strip() for key in v.split(",") if key.strip()] if v else None
451452

452-
@validator("minio_endpoint")
453-
def validate_minio_endpoint(cls, v):
454-
"""Ensure MinIO endpoint doesn't include protocol."""
453+
@validator("s3_endpoint")
454+
def validate_s3_endpoint(cls, v):
455+
"""Ensure S3 endpoint doesn't include protocol."""
455456
if v.startswith(("http://", "https://")):
456457
raise ValueError(
457-
"MinIO endpoint should not include protocol (use minio_secure instead)"
458+
"S3 endpoint should not include protocol (use s3_secure instead)"
458459
)
459460
return v
460461

@@ -505,14 +506,15 @@ def redis(self) -> RedisConfig:
505506
)
506507

507508
@property
508-
def minio(self) -> MinIOConfig:
509-
"""Access MinIO configuration group."""
510-
return MinIOConfig(
511-
minio_endpoint=self.minio_endpoint,
512-
minio_access_key=self.minio_access_key,
513-
minio_secret_key=self.minio_secret_key,
514-
minio_secure=self.minio_secure,
515-
minio_bucket=self.minio_bucket,
509+
def s3(self) -> S3Config:
510+
"""Access S3 storage configuration group."""
511+
return S3Config(
512+
s3_endpoint=self.s3_endpoint,
513+
s3_access_key=self.s3_access_key,
514+
s3_secret_key=self.s3_secret_key,
515+
s3_secure=self.s3_secure,
516+
s3_bucket=self.s3_bucket,
517+
s3_region=self.s3_region,
516518
)
517519

518520
@property
@@ -539,7 +541,7 @@ def resources(self) -> ResourcesConfig:
539541
max_filename_length=self.max_filename_length,
540542
session_ttl_hours=self.session_ttl_hours,
541543
session_cleanup_interval_minutes=self.session_cleanup_interval_minutes,
542-
enable_orphan_minio_cleanup=self.enable_orphan_minio_cleanup,
544+
enable_orphan_s3_cleanup=self.enable_orphan_s3_cleanup,
543545
)
544546

545547
@property
@@ -612,7 +614,7 @@ def is_file_allowed(self, filename: str) -> bool:
612614
# Grouped configs
613615
"APIConfig",
614616
"RedisConfig",
615-
"MinIOConfig",
617+
"S3Config",
616618
"SecurityConfig",
617619
"ResourcesConfig",
618620
"LoggingConfig",

0 commit comments

Comments
 (0)