0
# Container Images
1
2
Container image definitions that specify the runtime environment for functions and classes. Images define the operating system, Python version, packages, and system dependencies needed for your code to run.
3
4
## Capabilities
5
6
### Image Class
7
8
Defines container environments with pre-installed dependencies, custom configurations, and optimized builds for serverless execution.
9
10
```python { .api }
11
class Image:
12
@classmethod
13
def debian_slim(
14
cls,
15
python_version: str = None,
16
builder_version: str = None
17
):
18
"""
19
Create a Debian slim base image with Python.
20
21
Parameters:
22
- python_version: Python version (e.g., "3.11", "3.12"). Defaults to local version.
23
- builder_version: Image builder version for reproducible builds
24
25
Returns:
26
Image instance with Debian slim base
27
"""
28
29
@classmethod
30
def from_registry(
31
cls,
32
tag: str,
33
secret: Secret = None,
34
setup_dockerfile_commands: list[str] = None
35
):
36
"""
37
Create an image from a container registry (Docker Hub, ECR, etc.).
38
39
Parameters:
40
- tag: Full image tag (e.g., "python:3.11-slim", "ubuntu:22.04")
41
- secret: Secret for private registry authentication
42
- setup_dockerfile_commands: Additional setup commands to run
43
44
Returns:
45
Image instance based on registry image
46
"""
47
48
def pip_install(
49
self,
50
*packages: str,
51
find_links: str = None,
52
index_url: str = None,
53
extra_index_url: str = None,
54
pre: bool = False,
55
force_build: bool = False
56
):
57
"""
58
Install Python packages using pip.
59
60
Parameters:
61
- *packages: Package names, optionally with version specifiers
62
- find_links: Additional URLs to search for packages
63
- index_url: Base URL of package index
64
- extra_index_url: Extra URLs of package indexes
65
- pre: Include pre-release versions
66
- force_build: Force rebuild even if cached
67
68
Returns:
69
New Image instance with packages installed
70
"""
71
72
def poetry_install_from_file(
73
self,
74
poetry_pyproject_toml: str,
75
poetry_lock_file: str = None,
76
ignore_lockfile: bool = False
77
):
78
"""
79
Install dependencies from Poetry configuration files.
80
81
Parameters:
82
- poetry_pyproject_toml: Path to pyproject.toml file
83
- poetry_lock_file: Path to poetry.lock file
84
- ignore_lockfile: Install without using lockfile
85
86
Returns:
87
New Image instance with Poetry dependencies
88
"""
89
90
def run_commands(
91
self,
92
*commands: str,
93
secrets: list[Secret] = None,
94
gpu: str = None
95
):
96
"""
97
Run shell commands during image build.
98
99
Parameters:
100
- *commands: Shell commands to execute
101
- secrets: Secrets available during build
102
- gpu: GPU type for build environment
103
104
Returns:
105
New Image instance with commands executed
106
"""
107
108
def apt_install(
109
self,
110
*packages: str,
111
force_build: bool = False
112
):
113
"""
114
Install system packages using apt (Debian/Ubuntu).
115
116
Parameters:
117
- *packages: Package names to install
118
- force_build: Force rebuild even if cached
119
120
Returns:
121
New Image instance with system packages
122
"""
123
124
def copy_local_file(
125
self,
126
local_path: str,
127
remote_path: str = None
128
):
129
"""
130
Copy a local file into the image.
131
132
Parameters:
133
- local_path: Path to local file or directory
134
- remote_path: Destination path in image. Defaults to same path.
135
136
Returns:
137
New Image instance with file copied
138
"""
139
140
def copy_local_dir(
141
self,
142
local_path: str,
143
remote_path: str = None,
144
condition: callable = None
145
):
146
"""
147
Copy a local directory into the image.
148
149
Parameters:
150
- local_path: Path to local directory
151
- remote_path: Destination path in image
152
- condition: Function to filter which files to copy
153
154
Returns:
155
New Image instance with directory copied
156
"""
157
158
def add_python_packages(
159
self,
160
*python_packages: str,
161
force_build: bool = False
162
):
163
"""
164
Add Python packages from local wheel files or directories.
165
166
Parameters:
167
- *python_packages: Paths to wheel files or package directories
168
- force_build: Force rebuild even if cached
169
170
Returns:
171
New Image instance with packages added
172
"""
173
174
def dockerfile_commands(
175
self,
176
dockerfile_commands: list[str],
177
secrets: list[Secret] = None,
178
gpu: str = None,
179
context_files: dict = None
180
):
181
"""
182
Execute Dockerfile commands during build.
183
184
Parameters:
185
- dockerfile_commands: List of Dockerfile commands (RUN, COPY, etc.)
186
- secrets: Build secrets
187
- gpu: GPU type for build
188
- context_files: Files to include in build context
189
190
Returns:
191
New Image instance with Dockerfile commands applied
192
"""
193
194
def env(self, **env_vars: str):
195
"""
196
Set environment variables in the image.
197
198
Parameters:
199
- **env_vars: Environment variables as keyword arguments
200
201
Returns:
202
New Image instance with environment variables set
203
"""
204
205
def workdir(self, path: str):
206
"""
207
Set the working directory for the image.
208
209
Parameters:
210
- path: Working directory path
211
212
Returns:
213
New Image instance with working directory set
214
"""
215
```
216
217
## Usage Examples
218
219
### Basic Python Environment
220
221
```python
222
import modal
223
224
# Simple Python environment
225
image = modal.Image.debian_slim(python_version="3.11").pip_install(
226
"requests",
227
"beautifulsoup4",
228
"pandas==2.0.0"
229
)
230
231
app = modal.App("web-scraper")
232
233
@app.function(image=image)
234
def scrape_and_analyze(url: str):
235
import requests
236
import pandas as pd
237
from bs4 import BeautifulSoup
238
239
response = requests.get(url)
240
soup = BeautifulSoup(response.content, 'html.parser')
241
242
# Extract and analyze data
243
links = [a.get('href') for a in soup.find_all('a', href=True)]
244
df = pd.DataFrame({'links': links})
245
246
return {
247
'total_links': len(links),
248
'unique_domains': len(df['links'].apply(lambda x: x.split('/')[2] if x.startswith('http') else None).dropna().unique())
249
}
250
```
251
252
### Data Science Environment
253
254
```python
255
import modal
256
257
# Comprehensive data science environment
258
ds_image = (
259
modal.Image.debian_slim(python_version="3.11")
260
.apt_install("git", "curl", "build-essential")
261
.pip_install(
262
"numpy",
263
"pandas",
264
"scikit-learn",
265
"matplotlib",
266
"seaborn",
267
"jupyter",
268
"plotly",
269
"scipy"
270
)
271
.run_commands(
272
"pip install --no-deps lightgbm", # Custom installation
273
"mkdir -p /opt/data"
274
)
275
.env(
276
DATA_PATH="/opt/data",
277
PYTHONPATH="/opt/models"
278
)
279
)
280
281
app = modal.App("ml-pipeline")
282
283
@app.function(image=ds_image)
284
def train_model(dataset_path: str):
285
import pandas as pd
286
from sklearn.ensemble import RandomForestClassifier
287
from sklearn.model_selection import train_test_split
288
from sklearn.metrics import accuracy_score
289
290
# Load and prepare data
291
df = pd.read_csv(dataset_path)
292
X = df.drop('target', axis=1)
293
y = df['target']
294
295
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
296
297
# Train model
298
model = RandomForestClassifier(n_estimators=100)
299
model.fit(X_train, y_train)
300
301
# Evaluate
302
predictions = model.predict(X_test)
303
accuracy = accuracy_score(y_test, predictions)
304
305
return {"accuracy": accuracy, "model_type": "RandomForest"}
306
```
307
308
### Custom Base Image
309
310
```python
311
import modal
312
313
# Using a custom base image from Docker Hub
314
custom_image = (
315
modal.Image.from_registry("nvidia/cuda:11.8-devel-ubuntu22.04")
316
.apt_install("python3", "python3-pip", "python3-dev")
317
.run_commands(
318
"ln -s /usr/bin/python3 /usr/bin/python",
319
"pip install --upgrade pip"
320
)
321
.pip_install("torch", "torchvision", "transformers")
322
.env(CUDA_VISIBLE_DEVICES="0")
323
)
324
325
app = modal.App("gpu-ml")
326
327
@app.function(
328
image=custom_image,
329
gpu="T4" # GPU required for this function
330
)
331
def run_inference(model_name: str, input_text: str):
332
import torch
333
from transformers import AutoTokenizer, AutoModel
334
335
# Load model and tokenizer
336
tokenizer = AutoTokenizer.from_pretrained(model_name)
337
model = AutoModel.from_pretrained(model_name)
338
339
# Tokenize and run inference
340
inputs = tokenizer(input_text, return_tensors="pt")
341
with torch.no_grad():
342
outputs = model(**inputs)
343
344
return {
345
"embeddings_shape": outputs.last_hidden_state.shape,
346
"mean_embedding": outputs.last_hidden_state.mean().item()
347
}
348
```
349
350
### Development Environment with Local Code
351
352
```python
353
import modal
354
355
# Development image with local code and dependencies
356
dev_image = (
357
modal.Image.debian_slim()
358
.pip_install("fastapi", "uvicorn", "sqlalchemy", "pytest")
359
.copy_local_dir("./src", "/app/src") # Copy local source code
360
.copy_local_file("requirements.txt", "/app/requirements.txt")
361
.run_commands("pip install -r /app/requirements.txt")
362
.workdir("/app")
363
)
364
365
app = modal.App("development")
366
367
@app.function(image=dev_image)
368
def run_tests():
369
import subprocess
370
371
# Run tests using the copied code
372
result = subprocess.run(
373
["python", "-m", "pytest", "src/tests/", "-v"],
374
capture_output=True,
375
text=True,
376
cwd="/app"
377
)
378
379
return {
380
"exit_code": result.returncode,
381
"stdout": result.stdout,
382
"stderr": result.stderr
383
}
384
```
385
386
### Poetry-based Project
387
388
```python
389
import modal
390
391
# Using Poetry for dependency management
392
poetry_image = (
393
modal.Image.debian_slim()
394
.apt_install("curl")
395
.run_commands(
396
"curl -sSL https://install.python-poetry.org | python3 -",
397
"export PATH=\"/root/.local/bin:$PATH\""
398
)
399
.poetry_install_from_file(
400
"pyproject.toml",
401
"poetry.lock"
402
)
403
.copy_local_dir("./my_package", "/app/my_package")
404
.workdir("/app")
405
)
406
407
app = modal.App("poetry-app")
408
409
@app.function(image=poetry_image)
410
def process_with_poetry_deps():
411
# Use dependencies installed via Poetry
412
from my_package import main_module
413
return main_module.process_data()
414
```
415
416
### Multi-stage Build
417
418
```python
419
import modal
420
421
# Complex multi-stage build with custom compilation
422
build_image = (
423
modal.Image.debian_slim()
424
.apt_install(
425
"build-essential",
426
"cmake",
427
"git",
428
"pkg-config",
429
"libssl-dev"
430
)
431
.run_commands(
432
# Clone and build custom library
433
"git clone https://github.com/example/custom-lib.git /tmp/custom-lib",
434
"cd /tmp/custom-lib && mkdir build && cd build",
435
"cmake .. && make -j$(nproc) && make install"
436
)
437
.pip_install("numpy", "cython")
438
.copy_local_file("setup.py", "/app/setup.py")
439
.copy_local_dir("./src", "/app/src")
440
.run_commands(
441
"cd /app && python setup.py build_ext --inplace",
442
"pip install -e ."
443
)
444
.apt_install("--purge", "build-essential", "cmake") # Clean up build tools
445
.run_commands("apt-get autoremove -y && apt-get clean")
446
)
447
448
app = modal.App("optimized-build")
449
450
@app.function(image=build_image)
451
def run_optimized_computation(data):
452
# Use the custom-built optimized library
453
import my_optimized_package
454
return my_optimized_package.fast_compute(data)
455
```
456
457
## Common Patterns
458
459
### Environment Variables and Configuration
460
461
```python
462
config_image = (
463
modal.Image.debian_slim()
464
.pip_install("python-dotenv", "pydantic")
465
.env(
466
ENVIRONMENT="production",
467
LOG_LEVEL="INFO",
468
API_TIMEOUT="30"
469
)
470
.copy_local_file(".env.production", "/app/.env")
471
)
472
```
473
474
### Caching for Faster Builds
475
476
```python
477
# Use force_build=False (default) to enable caching
478
cached_image = (
479
modal.Image.debian_slim()
480
.pip_install("numpy", "pandas", force_build=False) # Will be cached
481
.copy_local_file("requirements.txt") # This will trigger rebuild if file changes
482
.pip_install("-r", "requirements.txt")
483
)
484
```
485
486
### Secrets in Build Process
487
488
```python
489
build_secret = modal.Secret.from_dict({
490
"GITHUB_TOKEN": "ghp_...",
491
"PRIVATE_REGISTRY_TOKEN": "..."
492
})
493
494
secure_image = (
495
modal.Image.debian_slim()
496
.run_commands(
497
"git clone https://${GITHUB_TOKEN}@github.com/private/repo.git /app",
498
secrets=[build_secret]
499
)
500
.pip_install("--extra-index-url", "https://token:${PRIVATE_REGISTRY_TOKEN}@pypi.private.com/simple/", "private-package", secrets=[build_secret])
501
)
502
```