Skip to content

Commit ceb6deb

Browse files
authored
fix: ensure Ubuntu-based base images work as expected; change default to vanilla Debian (#115)
#### Relevant issue or PR Fixes #112 #### Description of changes <!-- Add a high-level description of changes, focusing on the *what* and *why*. --> #### Testing done <!-- Describe how the changes were tested; e.g., "CI passes", "Tested manually in stagingrepo#123", screenshots of a terminal session that verify the changes, or any other evidence of testing the changes. --> #### License - [x] By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://pasteurlabs.github.io/tesseract/LICENSE). - [x] I sign the Developer Certificate of Origin below by adding my name and email address to the `Signed-off-by` line. <details> <summary><b>Developer Certificate of Origin</b></summary> ```text Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. ``` </details> Signed-off-by: Dion Häfner <[email protected]>
1 parent a61f361 commit ceb6deb

File tree

15 files changed

+160
-26
lines changed

15 files changed

+160
-26
lines changed

docs/content/creating-tesseracts/advanced.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ various circumstances:
111111
There are several steps in the process of building a Tesseract image
112112
which can be configured via the `tesseract_config.yaml` file, in particular the `build_config` section.
113113
For example:
114-
- By default the base image is `python:3.12-slim-bookworm`.
114+
- By default the base image is `debian:bookworm-slim`.
115115
Depending on your specific needs (different python version,
116116
preinstalled dependencies, ...), it might be beneficial to
117117
specify a different one in `base_image`.

examples/cuda/tesseract_api.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Copyright 2025 Pasteur Labs. All Rights Reserved.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
# Tesseract API module for cuda-image
5+
# Generated by tesseract 0.8.2.dev14+g494ed54.d20250401 on 2025-04-04T18:55:51.909676
6+
7+
8+
from pydantic import BaseModel
9+
10+
#
11+
# Schemas
12+
#
13+
14+
15+
class InputSchema(BaseModel):
16+
pass
17+
18+
19+
class OutputSchema(BaseModel):
20+
pass
21+
22+
23+
#
24+
# Required endpoints
25+
#
26+
27+
28+
def apply(inputs: InputSchema) -> OutputSchema:
29+
"""Assert that CUDA is available."""
30+
from ctypes.util import find_library
31+
32+
cudart = find_library("cudart")
33+
assert cudart is not None, "CUDA runtime library not found"
34+
return OutputSchema()
35+
36+
37+
#
38+
# Optional endpoints
39+
#
40+
41+
# import numpy as np
42+
43+
# def jacobian(inputs: InputSchema, jac_inputs: set[str], jac_outputs: set[str]):
44+
# return {}
45+
46+
# def jacobian_vector_product(
47+
# inputs: InputSchema,
48+
# jvp_inputs: set[str],
49+
# jvp_outputs: set[str],
50+
# tangent_vector: dict[str, np.typing.ArrayLike]
51+
# ) -> dict[str, np.typing.ArrayLike]:
52+
# return {}
53+
54+
# def vector_jacobian_product(
55+
# inputs: InputSchema,
56+
# vjp_inputs: set[str],
57+
# vjp_outputs: set[str],
58+
# cotangent_vector: dict[str, np.typing.ArrayLike]
59+
# ) -> dict[str, np.typing.ArrayLike]:
60+
# return {}
61+
62+
# def abstract_eval(abstract_inputs):
63+
# return {}

examples/cuda/tesseract_config.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Tesseract configuration file
2+
# Generated by tesseract 0.8.2.dev14+g494ed54.d20250401 on 2025-04-04T18:55:51.909676
3+
4+
name: "cuda-image"
5+
version: "0+unknown"
6+
description: ""
7+
8+
build_config:
9+
# Base image to use for the container, must be Ubuntu or Debian-based
10+
base_image: "nvidia/cuda:12.8.1-runtime-ubuntu24.04"
11+
12+
# Platform to build the container for. In general, images can only be executed
13+
# on the platform they were built for.
14+
target_platform: "native"
15+
16+
# Additional packages to install in the container (via apt-get)
17+
# extra_packages:
18+
# - package_name
19+
20+
# Data to copy into the container, relative to the project root
21+
# package_data:
22+
# - [path/to/source, path/to/destination]
23+
24+
# Additional Dockerfile commands to run during the build process
25+
# custom_build_steps:
26+
# - |
27+
# RUN echo "Hello, World!"
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Tesseract requirements file
2+
# Generated by tesseract 0.8.2.dev14+g494ed54.d20250401 on 2025-04-04T18:55:51.909676
3+
4+
# Add Python requirements like this:
5+
# numpy==1.18.1
6+
7+
# This may contain private dependencies via SSH URLs:
8+
# git+ssh://[email protected]/username/repo.git@branch
9+
# (use `tesseract build --forward-ssh-agent` to grant the builder access to your SSH keys)

examples/helloworld/tesseract_config.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ description: "A sample Python app"
44

55
build_config:
66
# Base image to use for the container, must be Ubuntu or Debian-based
7-
# with Python 3.9 or later
8-
# base_image: "python:3.12-slim-bookworm"
7+
# base_image: "debian:bookworm-slim"
98

109
# Platform to build the container for. In general, images can only be executed
1110
# on the platform they were built for.

examples/meshstats/tesseract_config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ name: "meshstats"
22
version: "0.0.1"
33

44
build_config:
5-
base_image: "python:3.12-slim-bookworm"
5+
base_image: "debian:bookworm-slim"

tesseract_core/sdk/api_parse.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ class TesseractBuildConfig(BaseModel):
9494
"""Configuration options for building a Tesseract."""
9595

9696
base_image: StrictStr = Field(
97-
"python:3.12-slim-bookworm",
97+
"debian:bookworm-slim",
9898
description="Base Docker image for the build. Must be Debian-based.",
9999
)
100100
target_platform: StrictStr = Field(

tesseract_core/sdk/engine.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,9 @@ def prepare_build_context(
317317
# When building from a requirements.txt we support local dependencies.
318318
# We separate local dep. lines from the requirements.txt and copy the
319319
# corresponding files into the build directory.
320+
local_requirements_path = context_dir / "local_requirements"
321+
Path.mkdir(local_requirements_path, parents=True, exist_ok=True)
322+
320323
if requirement_config.provider == "python-pip":
321324
reqstxt = src_dir / requirement_config._filename
322325
if reqstxt.exists():
@@ -325,8 +328,6 @@ def prepare_build_context(
325328
local_dependencies, remote_dependencies = [], []
326329

327330
if local_dependencies:
328-
local_requirements_path = context_dir / "local_requirements"
329-
Path.mkdir(local_requirements_path)
330331
for dependency in local_dependencies:
331332
src = src_dir / dependency
332333
dest = context_dir / "local_requirements" / src.name

tesseract_core/sdk/templates/Dockerfile.base

+22-8
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ FROM {{ config.build_config.base_image }} AS build_stage
55
FROM --platform={{ config.build_config.target_platform }} {{ config.build_config.base_image }} AS build_stage
66
{% endif %}
77

8+
# Install Python if necessary
9+
RUN if [ ! -x "$(command -v python3)" ]; then \
10+
apt-get update && apt-get install -y --no-install-recommends \
11+
python3-dev \
12+
python3-pip \
13+
python3-venv \
14+
&& rm -rf /var/lib/apt/lists/*; \
15+
fi
16+
817
RUN apt-get update && apt-get install -y --no-install-recommends \
9-
build-essential \
10-
git \
11-
ssh \
18+
git \
19+
ssh \
1220
&& rm -rf /var/lib/apt/lists/*
1321

1422
{% if config.build_config.extra_packages %}
@@ -26,9 +34,9 @@ RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
2634
# Copy all dependencies, for both the specific Tesseract and the runtime
2735
WORKDIR /tmp/build
2836
COPY {{ tesseract_runtime_location }} ./tesseract_runtime/
29-
COPY {{ tesseract_source_directory }}/{{ config.build_config.requirements._filename }}* ./
37+
COPY {{ tesseract_source_directory }}/{{ config.build_config.requirements._filename }} ./
3038
COPY {{ tesseract_source_directory }}/{{ config.build_config.requirements._build_script }} ./
31-
COPY local_requirements* ./local_requirements
39+
COPY local_requirements/ ./local_requirements
3240

3341
# Build a python venv from python provider build scripts.
3442
# The build script has to create a venv at /python-env
@@ -42,6 +50,12 @@ FROM {{ config.build_config.base_image }} AS run_stage
4250
FROM --platform={{ config.build_config.target_platform }} {{ config.build_config.base_image }} AS run_stage
4351
{% endif %}
4452

53+
RUN if [ ! -x "$(command -v python3)" ]; then \
54+
apt-get update && apt-get install -y --no-install-recommends \
55+
python3 \
56+
&& rm -rf /var/lib/apt/lists/*; \
57+
fi
58+
4559
{% if config.build_config.extra_packages %}
4660
# Install extra packages again since they may include runtime dependencies
4761
RUN apt-get update && apt-get install -y --no-install-recommends \
@@ -50,8 +64,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
5064
{% endif %}
5165

5266
# Drop to a non-root user
53-
RUN groupadd -g 1000 tesseractor && \
54-
useradd -u 1000 -g 1000 --create-home -s /bin/bash tesseractor
67+
RUN groupadd -o -g 1000 tesseractor && \
68+
useradd -o -u 1000 -g 1000 --create-home -s /bin/bash tesseractor
5569
WORKDIR /tesseract
5670
RUN chown tesseractor:tesseractor /tesseract
5771
USER tesseractor
@@ -64,7 +78,7 @@ ENV TESSERACT_NAME="{{ config.name }}" \
6478

6579
# Copy only necessary files
6680
COPY --from=build_stage /python-env /python-env
67-
COPY "{{ tesseract_source_directory }}/tesseract_api.py" ${TESSERACT_API_PATH}
81+
COPY --chown=1000:1000 "{{ tesseract_source_directory }}/tesseract_api.py" ${TESSERACT_API_PATH}
6882

6983
ENV PATH="/python-env/bin:$PATH"
7084

tesseract_core/sdk/templates/base/tesseract_config.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ description: ""
77

88
build_config:
99
# Base image to use for the container, must be Ubuntu or Debian-based
10-
# with Python 3.9 or later
11-
# base_image: "python:3.12-slim-bookworm"
10+
# base_image: "debian:bookworm-slim"
1211

1312
# Platform to build the container for. In general, images can only be executed
1413
# on the platform they were built for.

tesseract_core/sdk/templates/jax/tesseract_config.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ description: ""
77

88
build_config:
99
# Base image to use for the container, must be Ubuntu or Debian-based
10-
# with Python 3.9 or later
11-
# base_image: "python:3.12-slim-bookworm"
10+
# base_image: "debian:bookworm-slim"
1211

1312
# Platform to build the container for. In general, images can only be executed
1413
# on the platform they were built for.

tesseract_core/sdk/templates/pytorch/tesseract_config.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ description: ""
77

88
build_config:
99
# Base image to use for the container, must be Ubuntu or Debian-based
10-
# with Python 3.9 or later
11-
# base_image: "python:3.12-slim-bookworm"
10+
# base_image: "debian:bookworm-slim"
1211

1312
# Platform to build the container for. In general, images can only be executed
1413
# on the platform they were built for.

tests/endtoend_tests/common.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ def print_debug_info(result):
2727
traceback.print_exception(*result.exc_info)
2828

2929

30-
def build_tesseract(sourcedir, image_name, tag=None, build_retries=3):
30+
def build_tesseract(
31+
sourcedir, image_name, config_override=None, tag=None, build_retries=3
32+
):
3133
cli_runner = CliRunner(mix_stderr=False)
3234

3335
build_args = [
@@ -39,6 +41,10 @@ def build_tesseract(sourcedir, image_name, tag=None, build_retries=3):
3941
f"name={image_name}",
4042
]
4143

44+
if config_override is not None:
45+
for key, val in config_override.items():
46+
build_args.extend(["--config-override", f"{key}={val}"])
47+
4248
if tag is not None:
4349
build_args.extend(["--tag", tag])
4450
image_name = f"{image_name}:{tag}"

tests/endtoend_tests/test_endtoend.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,18 @@ def built_image_name(docker_client, shared_dummy_image_name, dummy_tesseract_loc
2323
yield image_name
2424

2525

26-
@pytest.mark.parametrize("tag", [True, False])
27-
@pytest.mark.parametrize("recipe", [None, *AVAILABLE_RECIPES])
26+
tested_images = ("ubuntu:24.04",)
27+
28+
build_matrix = [
29+
*[(tag, None, None) for tag in (True, False)],
30+
*[(False, r, None) for r in AVAILABLE_RECIPES],
31+
*[(False, None, img) for img in tested_images],
32+
]
33+
34+
35+
@pytest.mark.parametrize("tag,recipe,base_image", build_matrix)
2836
def test_build_from_init_endtoend(
29-
docker_client, dummy_image_name, tmp_path, tag, recipe
37+
docker_client, dummy_image_name, tmp_path, tag, recipe, base_image
3038
):
3139
"""Test that a trivial (empty) Tesseract image can be built from init."""
3240
cli_runner = CliRunner(mix_stderr=False)
@@ -42,7 +50,14 @@ def test_build_from_init_endtoend(
4250
assert yaml.safe_load(config_yaml)["name"] == dummy_image_name
4351

4452
img_tag = "foo" if tag else None
45-
image_name = build_tesseract(tmp_path, dummy_image_name, tag=img_tag)
53+
54+
config_override = {}
55+
if base_image is not None:
56+
config_override["build_config.base_image"] = base_image
57+
58+
image_name = build_tesseract(
59+
tmp_path, dummy_image_name, config_override=config_override, tag=img_tag
60+
)
4661
assert image_exists(docker_client, image_name)
4762

4863
# Test that the image can be run and that --help is forwarded correctly

tests/endtoend_tests/test_examples.py

+3
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,9 @@ class Config:
485485
"package_data": Config(
486486
test_with_random_inputs=True,
487487
),
488+
"cuda": Config(
489+
test_with_random_inputs=True,
490+
),
488491
"meshstats": Config(
489492
test_with_random_inputs=False,
490493
sample_requests=[

0 commit comments

Comments
 (0)