Skip to content

Commit ca96e96

Browse files
committed
Split llama-stack container build into deps and app layers for faster rebuilds
Separate the monolithic container image into a deps base image (test.deps.containerfile) that installs system packages and Python dependencies, and a thin app image (test.containerfile) that only copies source code on top. The Makefile tracks a hash of pyproject.toml and uv.lock to skip rebuilding the deps image when dependencies haven't changed, making source-only iterations significantly faster. Remove existing images by name before building replacements to prevent dangling <none>:<none> images from accumulating and wasting disk space.
1 parent be591ad commit ca96e96

4 files changed

Lines changed: 101 additions & 42 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ requirements.*.backup
195195
# Local run files
196196
local-run.yaml
197197

198+
# Deps image hash (auto-generated by make)
199+
.llama-stack-deps.hash
200+
198201
# Sisyphus planning files
199202
.sisyphus/
200203
# Per-developer feature design overrides (see docs/contributing/feature-design.config)

Makefile

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,17 @@ LLAMA_STACK_CONFIG ?= run.yaml
1313

1414
# Container configuration
1515
LLAMA_STACK_CONTAINER_NAME ?= lightspeed-llama-stack
16+
LLAMA_STACK_DEPS_IMAGE ?= lightspeed-llama-stack-deps:local
1617
LLAMA_STACK_IMAGE ?= lightspeed-llama-stack:local
1718
LLAMA_STACK_PORT ?= 8321
1819
CONTAINER_RUNTIME ?= $(shell command -v podman 2>/dev/null || command -v docker 2>/dev/null)
1920

20-
.PHONY: run run-stack build-llama-stack-image remove-llama-stack-container stop-llama-stack-container start-llama-stack-container wait-for-llama-stack-health clean-llama-stack
21+
# Dependency change detection
22+
DEPS_HASH_FILE := .llama-stack-deps.hash
23+
CURRENT_DEPS_HASH := $(shell cat pyproject.toml uv.lock 2>/dev/null | shasum -a 256 | cut -d' ' -f1)
24+
STORED_DEPS_HASH := $(shell cat $(DEPS_HASH_FILE) 2>/dev/null)
25+
26+
.PHONY: run run-stack build-llama-stack-deps-image ensure-llama-stack-deps-image build-llama-stack-image remove-llama-stack-container stop-llama-stack-container start-llama-stack-container wait-for-llama-stack-health clean-llama-stack
2127

2228
run-stack: ## Run lightspeed-stack directly, without building dependent service/s
2329
uv run src/lightspeed_stack.py -c $(CONFIG)
@@ -27,13 +33,44 @@ run: start-llama-stack-container ## Run the service locally with dependent servi
2733
@trap 'echo ""; echo "Stopping services..."; $(MAKE) stop-llama-stack-container' EXIT INT TERM; \
2834
$(MAKE) run-stack
2935

30-
build-llama-stack-image: remove-llama-stack-container ## Build llama-stack container image
31-
@echo "Building llama-stack container image..."
36+
build-llama-stack-deps-image: ## Force rebuild the deps base image
37+
@echo "Building llama-stack deps image..."
3238
@if [ -z "$(CONTAINER_RUNTIME)" ]; then \
3339
echo "ERROR: No container runtime found. Install podman or docker."; \
3440
exit 1; \
3541
fi
36-
$(CONTAINER_RUNTIME) build -f deploy/llama-stack/test.containerfile -t $(LLAMA_STACK_IMAGE) .
42+
@if $(CONTAINER_RUNTIME) image inspect $(LLAMA_STACK_DEPS_IMAGE) >/dev/null 2>&1; then \
43+
echo "Removing existing deps image to avoid dangling images..."; \
44+
$(CONTAINER_RUNTIME) rmi $(LLAMA_STACK_DEPS_IMAGE); \
45+
fi
46+
$(CONTAINER_RUNTIME) build -f deploy/llama-stack/test.deps.containerfile -t $(LLAMA_STACK_DEPS_IMAGE) .
47+
@echo "$(CURRENT_DEPS_HASH)" > $(DEPS_HASH_FILE)
48+
@echo "✓ Deps image built and hash saved"
49+
50+
ensure-llama-stack-deps-image: ## Build deps image only if missing or dependencies changed
51+
@if [ -z "$(CONTAINER_RUNTIME)" ]; then \
52+
echo "ERROR: No container runtime found. Install podman or docker."; \
53+
exit 1; \
54+
fi
55+
@if ! $(CONTAINER_RUNTIME) image inspect $(LLAMA_STACK_DEPS_IMAGE) >/dev/null 2>&1; then \
56+
echo "Deps image not found, building..."; \
57+
$(MAKE) build-llama-stack-deps-image; \
58+
elif [ "$(CURRENT_DEPS_HASH)" != "$(STORED_DEPS_HASH)" ]; then \
59+
echo "Dependencies changed (pyproject.toml or uv.lock), rebuilding deps image..."; \
60+
$(MAKE) build-llama-stack-deps-image; \
61+
else \
62+
echo "✓ Deps image is up-to-date (skipping rebuild)"; \
63+
fi
64+
65+
build-llama-stack-image: ensure-llama-stack-deps-image ## Build llama-stack app image (source-only layer on top of deps)
66+
@echo "Building llama-stack app image..."
67+
@if $(CONTAINER_RUNTIME) image inspect $(LLAMA_STACK_IMAGE) >/dev/null 2>&1; then \
68+
echo "Removing existing app image to avoid dangling images..."; \
69+
$(CONTAINER_RUNTIME) rmi $(LLAMA_STACK_IMAGE); \
70+
fi
71+
$(CONTAINER_RUNTIME) build -f deploy/llama-stack/test.containerfile \
72+
--build-arg DEPS_IMAGE=$(LLAMA_STACK_DEPS_IMAGE) \
73+
-t $(LLAMA_STACK_IMAGE) .
3774

3875
stop-llama-stack-container: ## Gracefully stop llama-stack container
3976
@if [ -n "$(CONTAINER_RUNTIME)" ] && $(CONTAINER_RUNTIME) inspect $(LLAMA_STACK_CONTAINER_NAME) >/dev/null 2>&1; then \
@@ -57,7 +94,7 @@ remove-llama-stack-container: ## Remove llama-stack container (saves logs first)
5794
echo "✓ Container removed (logs saved to /tmp/llama-stack-last-run.log)"; \
5895
fi
5996

60-
start-llama-stack-container: build-llama-stack-image ## Start llama-stack container
97+
start-llama-stack-container: remove-llama-stack-container build-llama-stack-image ## Start llama-stack container
6198
@echo "Starting llama-stack container..."
6299
$(CONTAINER_RUNTIME) run -d \
63100
--name $(LLAMA_STACK_CONTAINER_NAME) \
@@ -123,11 +160,16 @@ wait-for-llama-stack-health: ## Wait for llama-stack container to be healthy
123160
$(CONTAINER_RUNTIME) logs $(LLAMA_STACK_CONTAINER_NAME); \
124161
exit 1
125162

126-
clean-llama-stack: remove-llama-stack-container ## Remove container and image
163+
clean-llama-stack: remove-llama-stack-container ## Remove containers, images, and deps hash
127164
@if [ -n "$(CONTAINER_RUNTIME)" ] && $(CONTAINER_RUNTIME) images -q $(LLAMA_STACK_IMAGE) | grep -q .; then \
128-
echo "Removing llama-stack image..."; \
165+
echo "Removing llama-stack app image..."; \
129166
$(CONTAINER_RUNTIME) rmi $(LLAMA_STACK_IMAGE); \
130167
fi
168+
@if [ -n "$(CONTAINER_RUNTIME)" ] && $(CONTAINER_RUNTIME) images -q $(LLAMA_STACK_DEPS_IMAGE) | grep -q .; then \
169+
echo "Removing llama-stack deps image..."; \
170+
$(CONTAINER_RUNTIME) rmi $(LLAMA_STACK_DEPS_IMAGE); \
171+
fi
172+
@rm -f $(DEPS_HASH_FILE)
131173

132174
run-llama-stack: ## Start Llama Stack with enriched config (for local service mode)
133175
uv run src/llama_stack_configuration.py -c $(CONFIG) -i $(LLAMA_STACK_CONFIG) -o $(LLAMA_STACK_CONFIG) && \

deploy/llama-stack/test.containerfile

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,20 @@
1-
# Upstream llama-stack built from Red Hat UBI Python 3.12 image
2-
FROM registry.access.redhat.com/ubi9/python-312
1+
# Thin app image — copies source code on top of the pre-built deps image.
2+
# Rebuilds are fast since dependencies are already installed in the base.
3+
ARG DEPS_IMAGE=lightspeed-llama-stack-deps:local
4+
FROM ${DEPS_IMAGE}
35

46
USER root
57

6-
# Install additional build tools
7-
RUN dnf install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs \
8-
git tar gcc gcc-c++ make && \
9-
dnf clean all
10-
11-
# Install uv
12-
ENV PATH="/root/.local/bin:${PATH}"
13-
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
14-
15-
# Copy project files for dependency installation
16-
WORKDIR /opt/app-root
17-
COPY pyproject.toml uv.lock LICENSE README.md ./
8+
# Copy source code
189
COPY src ./src
1910

20-
# Install dependencies using uv sync
21-
RUN uv sync --locked --no-install-project --group llslibdev
22-
23-
# Add virtual environment to PATH for llama command
24-
ENV PATH="/opt/app-root/.venv/bin:$PATH" \
25-
PYTHONPATH="/opt/app-root/src"
26-
27-
# Set HOME directory so llama-stack uses /opt/app-root/src/.llama
28-
ENV HOME="/opt/app-root/src"
29-
30-
# Create python3 symlink for compatibility
31-
RUN ln -sf /usr/bin/python3.12 /usr/bin/python3
32-
33-
# Create required directories
34-
RUN mkdir -p /opt/app-root/src/.llama/storage \
35-
/opt/app-root/src/.llama/providers.d \
36-
/opt/app-root/src/.cache/huggingface && \
37-
chown -R 1001:0 /opt/app-root && \
38-
chmod -R 775 /opt/app-root
39-
4011
# Copy enrichment scripts for runtime config enrichment
4112
COPY src/llama_stack_configuration.py /opt/app-root/llama_stack_configuration.py
4213
COPY scripts/llama-stack-entrypoint.sh /opt/app-root/enrich-entrypoint.sh
4314
RUN chmod +x /opt/app-root/enrich-entrypoint.sh && \
44-
chown 1001:0 /opt/app-root/enrich-entrypoint.sh /opt/app-root/llama_stack_configuration.py
15+
chown 1001:0 /opt/app-root/enrich-entrypoint.sh /opt/app-root/llama_stack_configuration.py && \
16+
chown -R 1001:0 /opt/app-root/src && \
17+
chmod -R 775 /opt/app-root/src
4518

4619
# Switch back to the original user
4720
USER 1001
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Base image with all dependencies pre-installed.
2+
# Rebuild only when pyproject.toml or uv.lock change.
3+
FROM registry.access.redhat.com/ubi9/python-312
4+
5+
USER root
6+
7+
# Install additional build tools
8+
RUN dnf install -y --nodocs --setopt=keepcache=0 --setopt=tsflags=nodocs \
9+
git tar gcc gcc-c++ make && \
10+
dnf clean all
11+
12+
# Install uv
13+
ENV PATH="/root/.local/bin:${PATH}"
14+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
15+
16+
# Copy only dependency-related files
17+
WORKDIR /opt/app-root
18+
COPY pyproject.toml uv.lock LICENSE README.md ./
19+
COPY src/version.py ./src/version.py
20+
21+
# Install dependencies (not the project itself)
22+
RUN uv sync --locked --no-install-project --group llslibdev
23+
24+
# Add virtual environment to PATH for llama command
25+
ENV PATH="/opt/app-root/.venv/bin:$PATH" \
26+
PYTHONPATH="/opt/app-root/src"
27+
28+
# Set HOME directory so llama-stack uses /opt/app-root/src/.llama
29+
ENV HOME="/opt/app-root/src"
30+
31+
# Create python3 symlink for compatibility
32+
RUN ln -sf /usr/bin/python3.12 /usr/bin/python3
33+
34+
# Create required directories
35+
RUN mkdir -p /opt/app-root/src/.llama/storage \
36+
/opt/app-root/src/.llama/providers.d \
37+
/opt/app-root/src/.cache/huggingface && \
38+
chown -R 1001:0 /opt/app-root && \
39+
chmod -R 775 /opt/app-root
40+
41+
USER 1001

0 commit comments

Comments
 (0)