Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
.git
.github
.venv
.pytest_cache
.ruff_cache
.mypy_cache
__pycache__
*.py[cod]
*.egg-info
build
dist
htmlcov
.coverage
test-results.xml
# Fail-closed allowlist for the Planfile fleet-runner build context.
*
!.dockerignore
!Dockerfile
!README.md
!pyproject.toml
!uv.lock
!planfile/
!planfile/**
!scripts/
!scripts/docker-entrypoint.sh
32 changes: 14 additions & 18 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,28 +1,25 @@
# Planfile CI/CD Runner Docker Image
FROM python:3.11-slim
# uv 0.11.28 and Python 3.12.14 are selected by immutable multi-platform
# manifest digests. Keep the human-readable versions in this comment only.
FROM ghcr.io/astral-sh/uv@sha256:0f36cb9361a3346885ca3677e3767016687b5a170c1a6b88465ec14aefec90aa AS uv
FROM python@sha256:782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254 AS runtime

COPY --from=uv /uv /uvx /bin/

# Install system dependencies
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
&& rm -rf /var/lib/apt/lists/*

# Install Ollama for local LLM support
RUN curl -fsSL https://ollama.ai/install.sh | sh

# Set working directory
WORKDIR /app

# Copy requirements
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Install Planfile with all integrations
RUN pip install --no-cache-dir planfile[all]

# Install LLX
RUN pip install --no-cache-dir llx
# Install the local package and all runner integrations from the committed,
# hash-bearing lockfile. Frozen mode refuses to resolve or rewrite anything.
COPY pyproject.toml uv.lock README.md ./
COPY planfile/ ./planfile/
RUN uv sync --frozen --no-dev --extra all --no-editable

# Copy entrypoint script
COPY scripts/docker-entrypoint.sh /usr/local/bin/
Expand All @@ -35,9 +32,8 @@ RUN mkdir -p /workspace /app/results
ENV PYTHONPATH=/app
ENV WORKSPACE=/workspace
ENV RESULTS_DIR=/app/results

# Expose port for Ollama
EXPOSE 11434
ENV VIRTUAL_ENV=/app/.venv
ENV PATH="/app/.venv/bin:${PATH}"

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
Expand Down
7 changes: 0 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ services:
- MAX_ITERATIONS=5
- AUTO_FIX=false

# Ollama settings
- ENABLE_OLLAMA=true
- OLLAMA_MODEL=qwen2.5:3b

# Strategy file (mount or copy)
- STRATEGY_FILE=/app/planfile.yaml

Expand Down Expand Up @@ -56,9 +52,6 @@ services:
# Mount git config if needed
- ~/.gitconfig:/root/.gitconfig:ro

ports:
- "11434:11434" # Ollama API

networks:
- planfile

Expand Down
17 changes: 6 additions & 11 deletions scripts/docker-entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,12 @@ NC='\033[0m' # No Color
echo -e "${GREEN}🚀 Planfile CI/CD Runner${NC}"
echo "=============================="

# Start Ollama in background if enabled
if [ "$ENABLE_OLLAMA" = "true" ]; then
echo -e "${YELLOW}🤖 Starting Ollama...${NC}"
ollama serve &
sleep 5

# Pull default model if specified
if [ -n "$OLLAMA_MODEL" ]; then
echo -e "${YELLOW}📥 Pulling model: $OLLAMA_MODEL${NC}"
ollama pull "$OLLAMA_MODEL"
fi
# The fleet runner no longer downloads or embeds Ollama. Call an external,
# independently managed Ollama endpoint through the normal provider settings.
if [ "${ENABLE_OLLAMA:-false}" = "true" ]; then
echo -e "${RED}❌ Embedded Ollama is not part of the locked runner image.${NC}" >&2
echo "Configure an external Ollama endpoint instead." >&2
exit 2
fi

# Check required environment variables
Expand Down
44 changes: 44 additions & 0 deletions tests/test_docker_supply_chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from __future__ import annotations

import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DOCKERFILE = ROOT / "Dockerfile"
DIGEST_FROM = re.compile(r"^FROM\s+\S+@sha256:[0-9a-f]{64}(?:\s+AS\s+\S+)?$", re.MULTILINE)


def test_every_runner_stage_uses_an_immutable_image_digest() -> None:
text = DOCKERFILE.read_text(encoding="utf-8")
from_lines = [line for line in text.splitlines() if line.startswith("FROM ")]

assert from_lines
assert all(DIGEST_FROM.fullmatch(line) for line in from_lines)


def test_runner_uses_only_frozen_python_resolution() -> None:
text = DOCKERFILE.read_text(encoding="utf-8")

assert "uv sync --frozen --no-dev --extra all --no-editable" in text
assert "COPY pyproject.toml uv.lock README.md ./" in text
assert "pip install" not in text
assert "install.sh" not in text
assert "curl " not in text


def test_docker_context_is_an_explicit_allowlist() -> None:
entries = (ROOT / ".dockerignore").read_text(encoding="utf-8").splitlines()
active = [line for line in entries if line and not line.startswith("#")]

assert active[0] == "*"
assert set(active[1:]) == {
"!.dockerignore",
"!Dockerfile",
"!README.md",
"!pyproject.toml",
"!uv.lock",
"!planfile/",
"!planfile/**",
"!scripts/",
"!scripts/docker-entrypoint.sh",
}
Loading