Skill Tracer
Skills › Scientific Agent Skills › modal › images.md

images.md

275 lines · skills/modal/references/images.md at 5f2bc03

Open on GitHub
1# Modal Container Images
2
3Reviewed against SDK 1.6.0 and official [Images](https://modal.com/docs/guide/images)
4and [existing Images](https://modal.com/docs/guide/existing-images) guides.
5Image definitions were checked locally; builds and workload dependency compatibility
6were not executed. Package versions below are illustrative pins, not a tested ML stack.
7

Modal Container Images

Reviewed against SDK 1.6.0 and official Images and existing Images guides. Image definitions were checked locally; builds and workload dependency compatibility were not executed. Package versions below are illustrative pins, not a tested ML stack.

Raw lines 1 to 7
8## Table of Contents
9
10- [Overview](#overview)
11- [Base Images](#base-images)
12- [Installing Packages](#installing-packages)
13- [System Packages](#system-packages)
14- [Shell Commands](#shell-commands)
15- [Running Python During Build](#running-python-during-build)
16- [Adding Local Files](#adding-local-files)
17- [Environment Variables](#environment-variables)
18- [Dockerfiles](#dockerfiles)
19- [Alternative Package Managers](#alternative-package-managers)
20- [Image Caching](#image-caching)
21- [Handling Remote-Only Imports](#handling-remote-only-imports)

Table of Contents

  • Overview
  • Base Images
  • Installing Packages
  • System Packages
  • Shell Commands
  • Running Python During Build
  • Adding Local Files
  • Environment Variables
  • Dockerfiles
  • Alternative Package Managers
  • Image Caching
  • Handling Remote-Only Imports
Raw lines 8 to 22
23## Overview
25Every Modal function runs inside a container built from an `Image`. By default, Modal uses a Debian Linux image with the same Python minor version as your local interpreter.
27Defining an Image is lazy and performs no remote build. Modal resolves/builds it when
28running or deploying the App (before inputs execute), or when creating a Sandbox.
29Layers are cached for fast rebuilds.

Overview

Every Modal function runs inside a container built from an Image. By default, Modal uses a Debian Linux image with the same Python minor version as your local interpreter.

Defining an Image is lazy and performs no remote build. Modal resolves/builds it when running or deploying the App (before inputs execute), or when creating a Sandbox. Layers are cached for fast rebuilds.

Raw lines 23 to 30
31## Base Images
33```python
34# Default: Debian slim with your local Python version
35image = modal.Image.debian_slim()
37# Specific Python version
38image = modal.Image.debian_slim(python_version="3.11")
40# From Docker Hub
41image = modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")
43# From a Dockerfile
44image = modal.Image.from_dockerfile("./Dockerfile")
45```

Base Images

# Default: Debian slim with your local Python version
image = modal.Image.debian_slim()

# Specific Python version
image = modal.Image.debian_slim(python_version="3.11")

# From Docker Hub
image = modal.Image.from_registry("nvidia/cuda:12.4.0-devel-ubuntu22.04", add_python="3.11")

# From a Dockerfile
image = modal.Image.from_dockerfile("./Dockerfile")
Raw lines 31 to 46
47## Installing Packages

Installing Packages

Raw lines 47 to 48
49### uv (Recommended)
51`uv_pip_install` uses the uv package manager for fast, reliable installs:
53```python
54image = (
55 modal.Image.debian_slim(python_version="3.11")
56 .uv_pip_install(
57 "torch==2.12.0",
58 "transformers==5.9.0",
59 "accelerate==1.13.0",
60 "scipy==1.17.1",
61 )
63```
65Pin versions for reproducibility. uv resolves dependencies faster than pip.

uv (Recommended)

uv_pip_install uses the uv package manager for fast, reliable installs:

image = (
    modal.Image.debian_slim(python_version="3.11")
    .uv_pip_install(
        "torch==2.12.0",
        "transformers==5.9.0",
        "accelerate==1.13.0",
        "scipy==1.17.1",
    )
)

Pin versions for reproducibility. uv resolves dependencies faster than pip.

Raw lines 49 to 66
67### pip (Fallback)
69```python
70image = modal.Image.debian_slim(python_version="3.11").pip_install(
71 "numpy==1.26.0",
72 "pandas==2.1.0",
74```

pip (Fallback)

image = modal.Image.debian_slim(python_version="3.11").pip_install(
    "numpy==1.26.0",
    "pandas==2.1.0",
)
Raw lines 67 to 75
76### From requirements.txt
78```python
79image = modal.Image.debian_slim().pip_install_from_requirements("requirements.txt")
80```
82For a uv-managed project, `modal.Image.debian_slim().uv_sync(".")` reads its
83`pyproject.toml` and `uv.lock` (`frozen=True` by default). Keep the lockfile in sync.

From requirements.txt

image = modal.Image.debian_slim().pip_install_from_requirements("requirements.txt")

For a uv-managed project, modal.Image.debian_slim().uv_sync(".") reads its pyproject.toml and uv.lock (frozen=True by default). Keep the lockfile in sync.

Raw lines 76 to 84
85### Private Packages
87```python
88image = (
89 modal.Image.debian_slim()
90 .pip_install_private_repos(
91 "github.com/org/private-repo",
92 git_user="username",
93 secrets=[modal.Secret.from_name("github-token")],
94 )
96```
98For GitHub, the named Secret must contain `GITHUB_TOKEN`; do not embed tokens in URLs.

Private Packages

image = (
    modal.Image.debian_slim()
    .pip_install_private_repos(
        "github.com/org/private-repo",
        git_user="username",
        secrets=[modal.Secret.from_name("github-token")],
    )
)

For GitHub, the named Secret must contain GITHUB_TOKEN; do not embed tokens in URLs.

Raw lines 85 to 99
100## System Packages
102Install Linux packages via apt:
104```python
105image = (
106 modal.Image.debian_slim()
107 .apt_install("ffmpeg", "libsndfile1", "git", "curl")
108 .uv_pip_install("librosa", "soundfile")
110```

System Packages

Install Linux packages via apt:

image = (
    modal.Image.debian_slim()
    .apt_install("ffmpeg", "libsndfile1", "git", "curl")
    .uv_pip_install("librosa", "soundfile")
)
Raw lines 100 to 111
112## Shell Commands
114Run arbitrary commands during image build:
116```python
117image = (
118 modal.Image.debian_slim()
119 .run_commands("mkdir -p /opt/data")
121```

Shell Commands

Run arbitrary commands during image build:

image = (
    modal.Image.debian_slim()
    .run_commands("mkdir -p /opt/data")
)
Raw lines 112 to 122
123### With GPU
125Some build steps require GPU access (e.g., compiling CUDA kernels):
127```python
128image = (
129 modal.Image.debian_slim()
130 .uv_pip_install("torch")
131 .run_commands("python -c 'import torch; assert torch.cuda.is_available()'", gpu="A100")
133```

With GPU

Some build steps require GPU access (e.g., compiling CUDA kernels):

image = (
    modal.Image.debian_slim()
    .uv_pip_install("torch")
    .run_commands("python -c 'import torch; assert torch.cuda.is_available()'", gpu="A100")
)
Raw lines 123 to 134
135## Running Python During Build
137Execute Python functions as build steps — useful for downloading model weights:
139```python
140def download_model():
141 from huggingface_hub import snapshot_download
142 snapshot_download("openai-community/gpt2", local_dir="/models/gpt2")
144image = (
145 modal.Image.debian_slim(python_version="3.11")
146 .uv_pip_install("huggingface_hub", "torch", "transformers")
147 .run_function(download_model, secrets=[modal.Secret.from_name("huggingface")])
149```
151The resulting filesystem (including downloaded files) is snapshotted into the image.
152For gated models, accept the model license and pass an `HF_TOKEN` Secret explicitly.
153Pin the model revision for reproducible production builds.

Running Python During Build

Execute Python functions as build steps — useful for downloading model weights:

def download_model():
    from huggingface_hub import snapshot_download
    snapshot_download("openai-community/gpt2", local_dir="/models/gpt2")

image = (
    modal.Image.debian_slim(python_version="3.11")
    .uv_pip_install("huggingface_hub", "torch", "transformers")
    .run_function(download_model, secrets=[modal.Secret.from_name("huggingface")])
)

The resulting filesystem (including downloaded files) is snapshotted into the image. For gated models, accept the model license and pass an HF_TOKEN Secret explicitly. Pin the model revision for reproducible production builds.

Raw lines 135 to 154
155## Adding Local Files

Adding Local Files

Raw lines 155 to 156
157### Local Directories
159```python
160image = modal.Image.debian_slim().add_local_dir(
161 local_path="./config",
162 remote_path="/root/config",
164```
166By default, files are added at container startup (not baked into the image layer). Use `copy=True` to bake them in.

Local Directories

image = modal.Image.debian_slim().add_local_dir(
    local_path="./config",
    remote_path="/root/config",
)

By default, files are added at container startup (not baked into the image layer). Use copy=True to bake them in.

Raw lines 157 to 167
168### Local Python Modules
170```python
171image = modal.Image.debian_slim().add_local_python_source("my_module")
172```
174This uses Python's import system to find and include the module.
176> As of v1.0, Modal no longer "automounts" imported local modules. You must explicitly
177> include local dependencies with `add_local_python_source` (the App's own source is
178> still included automatically; set `include_source=False` on the App/Function to opt
179> out). The deprecated `modal.Mount` object and the `mount=`/`context_mount=` parameters
180> have been replaced by these `Image.add_local_*` methods.

Local Python Modules

image = modal.Image.debian_slim().add_local_python_source("my_module")

This uses Python's import system to find and include the module.

As of v1.0, Modal no longer "automounts" imported local modules. You must explicitly include local dependencies with add_local_python_source (the App's own source is still included automatically; set include_source=False on the App/Function to opt out). The deprecated modal.Mount object and the mount=/context_mount= parameters have been replaced by these Image.add_local_* methods.
Raw lines 168 to 181
182### Individual Files
184```python
185image = modal.Image.debian_slim().add_local_file(
186 local_path="./model_config.json",
187 remote_path="/root/config.json",
189```

Individual Files

image = modal.Image.debian_slim().add_local_file(
    local_path="./model_config.json",
    remote_path="/root/config.json",
)
Raw lines 182 to 190
191## Environment Variables
193```python
194image = (
195 modal.Image.debian_slim()
196 .env({
197 "TOKENIZERS_PARALLELISM": "false",
198 "HF_HOME": "/cache/huggingface",
199 })
201```
203Names and values must be strings.

Environment Variables

image = (
    modal.Image.debian_slim()
    .env({
        "TOKENIZERS_PARALLELISM": "false",
        "HF_HOME": "/cache/huggingface",
    })
)

Names and values must be strings.

Raw lines 191 to 204
205## Dockerfiles
207Build from existing Dockerfiles:
209```python
210image = modal.Image.from_dockerfile("./Dockerfile")
211```
213The build context is now inferred automatically from the Dockerfile's commands. The
214old `context_mount=` parameter — along with the `modal.Mount` object it relied on — is
215deprecated and was enforced as removed in v1.0; do not pass it.

Dockerfiles

Build from existing Dockerfiles:

image = modal.Image.from_dockerfile("./Dockerfile")

The build context is now inferred automatically from the Dockerfile's commands. The old context_mount= parameter — along with the modal.Mount object it relied on — is deprecated and was enforced as removed in v1.0; do not pass it.

Raw lines 205 to 216
217## Alternative Package Managers

Alternative Package Managers

Raw lines 217 to 218
219### Micromamba / Conda
221For packages requiring coordinated system and Python package installs:
223```python
224image = (
225 modal.Image.micromamba(python_version="3.11")
226 .micromamba_install("numpy", "scipy", channels=["conda-forge"])
228```

Micromamba / Conda

For packages requiring coordinated system and Python package installs:

image = (
    modal.Image.micromamba(python_version="3.11")
    .micromamba_install("numpy", "scipy", channels=["conda-forge"])
)
Raw lines 219 to 229
230## Image Caching
232Modal caches images per layer (per method call). Breaking the cache on one layer cascades to all subsequent layers.

Image Caching

Modal caches images per layer (per method call). Breaking the cache on one layer cascades to all subsequent layers.

Raw lines 230 to 233
234### Optimization Tips
2361. **Order layers by change frequency**: Put stable dependencies first, frequently changing code last
2372. **Pin versions**: Unpinned versions may resolve differently and break cache
2383. **Separate large installs**: Put heavy packages (torch, tensorflow) in early layers

Optimization Tips

  1. Order layers by change frequency: Put stable dependencies first, frequently changing code last
  2. Pin versions: Unpinned versions may resolve differently and break cache
  3. Separate large installs: Put heavy packages (torch, tensorflow) in early layers
Raw lines 234 to 239
240### Force Rebuild
242```python
243# Single layer
244image = modal.Image.debian_slim().apt_install("git", force_build=True)
245```
247```bash
248# All images in a run
249MODAL_FORCE_BUILD=1 modal run script.py
251# Rebuild without updating cache
252MODAL_IGNORE_CACHE=1 modal run script.py
253```

Force Rebuild

# Single layer
image = modal.Image.debian_slim().apt_install("git", force_build=True)
# All images in a run
MODAL_FORCE_BUILD=1 modal run script.py

# Rebuild without updating cache
MODAL_IGNORE_CACHE=1 modal run script.py
Raw lines 240 to 254
255## Handling Remote-Only Imports
257When packages are only available in the container (not locally), use conditional imports:
259```python
260@app.function(image=image)
261def process():
262 import torch # Only available in the container
263 return torch.cuda.device_count()
264```
266For module-level imports shared across functions, use the `Image.imports()` context manager:
268```python
269with image.imports():
270 import torch
271 import transformers
272```
274This prevents `ImportError` locally while making the imports available in the container.
275It does not install dependencies or make an unavailable module usable during local execution.

Handling Remote-Only Imports

When packages are only available in the container (not locally), use conditional imports:

@app.function(image=image)
def process():
    import torch  # Only available in the container
    return torch.cuda.device_count()

For module-level imports shared across functions, use the Image.imports() context manager:

with image.imports():
    import torch
    import transformers

This prevents ImportError locally while making the imports available in the container. It does not install dependencies or make an unavailable module usable during local execution.

Raw lines 255 to 275