feat: Created multi-stage Dockerfile.engine with healthcheck endpoint;…

- "docker/Dockerfile.engine"
- "engine/api/routes.py"
- ".dockerignore"

GSD-Task: S03/T02
This commit is contained in:
jlightner 2026-03-26 04:49:38 +00:00
parent c693f5e1e2
commit 2d8efb15dd
8 changed files with 275 additions and 13 deletions

22
.dockerignore Normal file
View file

@ -0,0 +1,22 @@
# Kerf Engine — Docker build context exclusions
.git
.gsd
.venv
venv
__pycache__
*.pyc
*.pyo
.pytest_cache
*.egg-info
.env
.env.*
node_modules
.next
dist
build
coverage
.cache
tmp
*.log
.DS_Store
Thumbs.db

View file

@ -9,3 +9,4 @@
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S02","taskId":"T03"},"ts":"2026-03-26T04:39:50.468Z","actor":"agent","hash":"00412cfd0b09e3c4","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
{"cmd":"complete-slice","params":{"milestoneId":"M001","sliceId":"S02"},"ts":"2026-03-26T04:41:58.014Z","actor":"agent","hash":"296c12d4a2f536c8","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T01"},"ts":"2026-03-26T04:45:48.732Z","actor":"agent","hash":"a1c0c74b1d7c5d15","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}
{"cmd":"complete-task","params":{"milestoneId":"M001","sliceId":"S03","taskId":"T02"},"ts":"2026-03-26T04:49:33.566Z","actor":"agent","hash":"fc8b517936769f11","session_id":"49f8e0fe-34a0-4608-b519-eca93850ed7c"}

View file

@ -14,7 +14,7 @@
- Estimate: 45min
- Files: engine/presets/sign.json, engine/presets/patch.json, engine/presets/stencil.json, engine/presets/detailed.json, engine/presets/custom.json, engine/presets/loader.py, engine/api/routes.py
- Verify: cd engine && python -m pytest tests/ -v -k preset
- [ ] **T02: Engine Dockerfile + healthcheck** — 1. Create docker/Dockerfile.engine (multi-stage build)
- [x] **T02: Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health** — 1. Create docker/Dockerfile.engine (multi-stage build)
2. Install system deps: libopencv, potrace libs
3. Install Python deps from pyproject.toml
4. Add healthcheck endpoint: GET /engine/health → {status: 'ok'}

View file

@ -0,0 +1,24 @@
{
"schemaVersion": 1,
"taskId": "T01",
"unitId": "M001/S03/T01",
"timestamp": 1774500352182,
"passed": false,
"discoverySource": "task-plan",
"checks": [
{
"command": "cd engine",
"exitCode": 0,
"durationMs": 4,
"verdict": "pass"
},
{
"command": "python -m pytest tests/ -v -k preset",
"exitCode": 127,
"durationMs": 3,
"verdict": "fail"
}
],
"retryAttempt": 1,
"maxRetries": 2
}

View file

@ -0,0 +1,84 @@
---
id: T02
parent: S03
milestone: M001
provides: []
requires: []
affects: []
key_files: ["docker/Dockerfile.engine", "engine/api/routes.py", ".dockerignore"]
key_decisions: ["Multi-stage build separates build deps (build-essential, potrace-dev, agg-dev) from runtime (libpotrace0, libagg2, curl)", "Health endpoint at /engine/health for namespace consistency with other /engine/* routes", "Engine image contains only engine source (api, pipeline, output, presets, main.py) — no App dependencies"]
patterns_established: []
drill_down_paths: []
observability_surfaces: []
duration: ""
verification_result: "Built image successfully (exit 0), ran container on port 8100, confirmed /engine/health returns {"status":"ok"}, Docker health status shows "healthy" with 0 failing streak, presets endpoint works from container, no App files in image, full test suite passes (196/196), preset tests pass (28/28)."
completed_at: 2026-03-26T04:49:33.510Z
blocker_discovered: false
---
# T02: Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health
> Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health
## What Happened
---
id: T02
parent: S03
milestone: M001
key_files:
- docker/Dockerfile.engine
- engine/api/routes.py
- .dockerignore
key_decisions:
- Multi-stage build separates build deps (build-essential, potrace-dev, agg-dev) from runtime (libpotrace0, libagg2, curl)
- Health endpoint at /engine/health for namespace consistency with other /engine/* routes
- Engine image contains only engine source (api, pipeline, output, presets, main.py) — no App dependencies
duration: ""
verification_result: passed
completed_at: 2026-03-26T04:49:33.527Z
blocker_discovered: false
---
# T02: Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health
**Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health**
## What Happened
Created docker/Dockerfile.engine as a two-stage build: builder stage installs build-essential, libagg-dev, and libpotrace-dev to compile pypotrace, then installs all Python deps into /opt/venv. Runtime stage uses python:3.11-slim with only runtime libs (libpotrace0, libagg2, curl), copies the venv, and copies only engine source code. Added GET /engine/health endpoint returning {"status": "ok"} to engine/api/routes.py. Configured Docker HEALTHCHECK instruction (15s interval, 5s timeout, 10s start-period, 3 retries). Added .dockerignore for clean builds. Verified container builds, starts, responds healthy, and contains no App/frontend code.
## Verification
Built image successfully (exit 0), ran container on port 8100, confirmed /engine/health returns {"status":"ok"}, Docker health status shows "healthy" with 0 failing streak, presets endpoint works from container, no App files in image, full test suite passes (196/196), preset tests pass (28/28).
## Verification Evidence
| # | Command | Exit Code | Verdict | Duration |
|---|---------|-----------|---------|----------|
| 1 | `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .` | 0 | ✅ pass | 78700ms |
| 2 | `curl -sf http://localhost:8100/engine/health` | 0 | ✅ pass | 5100ms |
| 3 | `docker inspect kerf-engine-test --format='{{json .State.Health.Status}}'` | 0 | ✅ pass | 200ms |
| 4 | `cd engine && .venv/bin/python -m pytest tests/ -v -k preset` | 0 | ✅ pass | 600ms |
| 5 | `cd engine && .venv/bin/python -m pytest tests/ -v` | 0 | ✅ pass | 940ms |
## Deviations
Added /engine/health on the router for namespace consistency (root /health already existed in main.py). Both endpoints now coexist.
## Known Issues
None.
## Files Created/Modified
- `docker/Dockerfile.engine`
- `engine/api/routes.py`
- `.dockerignore`
## Deviations
Added /engine/health on the router for namespace consistency (root /health already existed in main.py). Both endpoints now coexist.
## Known Issues
None.

View file

@ -1,6 +1,6 @@
{
"version": 1,
"exported_at": "2026-03-26T04:45:48.731Z",
"exported_at": "2026-03-26T04:49:33.565Z",
"milestones": [
{
"id": "M001",
@ -774,19 +774,27 @@
"milestone_id": "M001",
"slice_id": "S03",
"id": "T02",
"title": "Engine Dockerfile + healthcheck",
"status": "pending",
"one_liner": "",
"narrative": "",
"verification_result": "",
"title": "Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health",
"status": "complete",
"one_liner": "Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health",
"narrative": "Created docker/Dockerfile.engine as a two-stage build: builder stage installs build-essential, libagg-dev, and libpotrace-dev to compile pypotrace, then installs all Python deps into /opt/venv. Runtime stage uses python:3.11-slim with only runtime libs (libpotrace0, libagg2, curl), copies the venv, and copies only engine source code. Added GET /engine/health endpoint returning {\"status\": \"ok\"} to engine/api/routes.py. Configured Docker HEALTHCHECK instruction (15s interval, 5s timeout, 10s start-period, 3 retries). Added .dockerignore for clean builds. Verified container builds, starts, responds healthy, and contains no App/frontend code.",
"verification_result": "Built image successfully (exit 0), ran container on port 8100, confirmed /engine/health returns {\"status\":\"ok\"}, Docker health status shows \"healthy\" with 0 failing streak, presets endpoint works from container, no App files in image, full test suite passes (196/196), preset tests pass (28/28).",
"duration": "",
"completed_at": null,
"completed_at": "2026-03-26T04:49:33.510Z",
"blocker_discovered": false,
"deviations": "",
"known_issues": "",
"key_files": [],
"key_decisions": [],
"full_summary_md": "",
"deviations": "Added /engine/health on the router for namespace consistency (root /health already existed in main.py). Both endpoints now coexist.",
"known_issues": "None.",
"key_files": [
"docker/Dockerfile.engine",
"engine/api/routes.py",
".dockerignore"
],
"key_decisions": [
"Multi-stage build separates build deps (build-essential, potrace-dev, agg-dev) from runtime (libpotrace0, libagg2, curl)",
"Health endpoint at /engine/health for namespace consistency with other /engine/* routes",
"Engine image contains only engine source (api, pipeline, output, presets, main.py) — no App dependencies"
],
"full_summary_md": "---\nid: T02\nparent: S03\nmilestone: M001\nkey_files:\n - docker/Dockerfile.engine\n - engine/api/routes.py\n - .dockerignore\nkey_decisions:\n - Multi-stage build separates build deps (build-essential, potrace-dev, agg-dev) from runtime (libpotrace0, libagg2, curl)\n - Health endpoint at /engine/health for namespace consistency with other /engine/* routes\n - Engine image contains only engine source (api, pipeline, output, presets, main.py) — no App dependencies\nduration: \"\"\nverification_result: passed\ncompleted_at: 2026-03-26T04:49:33.527Z\nblocker_discovered: false\n---\n\n# T02: Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health\n\n**Created multi-stage Dockerfile.engine with healthcheck endpoint; container builds, starts, and responds healthy on /engine/health**\n\n## What Happened\n\nCreated docker/Dockerfile.engine as a two-stage build: builder stage installs build-essential, libagg-dev, and libpotrace-dev to compile pypotrace, then installs all Python deps into /opt/venv. Runtime stage uses python:3.11-slim with only runtime libs (libpotrace0, libagg2, curl), copies the venv, and copies only engine source code. Added GET /engine/health endpoint returning {\"status\": \"ok\"} to engine/api/routes.py. Configured Docker HEALTHCHECK instruction (15s interval, 5s timeout, 10s start-period, 3 retries). Added .dockerignore for clean builds. Verified container builds, starts, responds healthy, and contains no App/frontend code.\n\n## Verification\n\nBuilt image successfully (exit 0), ran container on port 8100, confirmed /engine/health returns {\"status\":\"ok\"}, Docker health status shows \"healthy\" with 0 failing streak, presets endpoint works from container, no App files in image, full test suite passes (196/196), preset tests pass (28/28).\n\n## Verification Evidence\n\n| # | Command | Exit Code | Verdict | Duration |\n|---|---------|-----------|---------|----------|\n| 1 | `docker build -f docker/Dockerfile.engine -t kerf-engine:dev .` | 0 | ✅ pass | 78700ms |\n| 2 | `curl -sf http://localhost:8100/engine/health` | 0 | ✅ pass | 5100ms |\n| 3 | `docker inspect kerf-engine-test --format='{{json .State.Health.Status}}'` | 0 | ✅ pass | 200ms |\n| 4 | `cd engine && .venv/bin/python -m pytest tests/ -v -k preset` | 0 | ✅ pass | 600ms |\n| 5 | `cd engine && .venv/bin/python -m pytest tests/ -v` | 0 | ✅ pass | 940ms |\n\n\n## Deviations\n\nAdded /engine/health on the router for namespace consistency (root /health already existed in main.py). Both endpoints now coexist.\n\n## Known Issues\n\nNone.\n\n## Files Created/Modified\n\n- `docker/Dockerfile.engine`\n- `engine/api/routes.py`\n- `.dockerignore`\n",
"description": "1. Create docker/Dockerfile.engine (multi-stage build)\n2. Install system deps: libopencv, potrace libs\n3. Install Python deps from pyproject.toml\n4. Add healthcheck endpoint: GET /engine/health → {status: 'ok'}\n5. Configure Dockerfile HEALTHCHECK instruction\n6. Build and test container starts\n7. Verify API responds from inside container\n8. Ensure no App dependencies in engine image",
"estimate": "30min",
"files": [
@ -942,6 +950,61 @@
"verdict": "✅ pass",
"duration_ms": 960,
"created_at": "2026-03-26T04:45:48.689Z"
},
{
"id": 10,
"task_id": "T02",
"slice_id": "S03",
"milestone_id": "M001",
"command": "docker build -f docker/Dockerfile.engine -t kerf-engine:dev .",
"exit_code": 0,
"verdict": "✅ pass",
"duration_ms": 78700,
"created_at": "2026-03-26T04:49:33.510Z"
},
{
"id": 11,
"task_id": "T02",
"slice_id": "S03",
"milestone_id": "M001",
"command": "curl -sf http://localhost:8100/engine/health",
"exit_code": 0,
"verdict": "✅ pass",
"duration_ms": 5100,
"created_at": "2026-03-26T04:49:33.510Z"
},
{
"id": 12,
"task_id": "T02",
"slice_id": "S03",
"milestone_id": "M001",
"command": "docker inspect kerf-engine-test --format='{{json .State.Health.Status}}'",
"exit_code": 0,
"verdict": "✅ pass",
"duration_ms": 200,
"created_at": "2026-03-26T04:49:33.510Z"
},
{
"id": 13,
"task_id": "T02",
"slice_id": "S03",
"milestone_id": "M001",
"command": "cd engine && .venv/bin/python -m pytest tests/ -v -k preset",
"exit_code": 0,
"verdict": "✅ pass",
"duration_ms": 600,
"created_at": "2026-03-26T04:49:33.510Z"
},
{
"id": 14,
"task_id": "T02",
"slice_id": "S03",
"milestone_id": "M001",
"command": "cd engine && .venv/bin/python -m pytest tests/ -v",
"exit_code": 0,
"verdict": "✅ pass",
"duration_ms": 940,
"created_at": "2026-03-26T04:49:33.510Z"
}
]
}

61
docker/Dockerfile.engine Normal file
View file

@ -0,0 +1,61 @@
# ── Kerf Engine — multi-stage Docker build ──
# Standalone raster-to-vector conversion API (no App dependencies)
# ── Stage 1: Build dependencies ──
FROM python:3.11-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
pkg-config \
libagg-dev \
libpotrace-dev \
libpotrace0 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Install Python deps into a virtual-env we can copy later
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
COPY engine/pyproject.toml .
RUN pip install --no-cache-dir . 2>/dev/null || true
# The above may fail because package source isn't present yet — install deps directly
RUN pip install --no-cache-dir \
"fastapi>=0.110" \
"uvicorn[standard]>=0.29" \
"opencv-python-headless>=4.9" \
"pypotrace>=0.3" \
"vtracer>=0.6" \
"python-multipart>=0.0.9" \
"Pillow>=10.2" \
"ezdxf>=1.0"
# ── Stage 2: Runtime image ──
FROM python:3.11-slim AS runtime
# Runtime-only system libs for pypotrace
RUN apt-get update && apt-get install -y --no-install-recommends \
libpotrace0 \
libagg2 \
curl \
&& rm -rf /var/lib/apt/lists/*
# Copy the virtual-env from the builder
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Copy engine source only — no App code
WORKDIR /app
COPY engine/main.py .
COPY engine/api/ ./api/
COPY engine/pipeline/ ./pipeline/
COPY engine/output/ ./output/
COPY engine/presets/ ./presets/
EXPOSE 8000
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \
CMD curl -sf http://localhost:8000/engine/health || exit 1
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

View file

@ -14,6 +14,13 @@ from presets.loader import all_presets, preset_names, resolve_params
router = APIRouter()
@router.get("/engine/health")
async def health():
"""Healthcheck endpoint for container orchestration."""
return {"status": "ok"}
VALID_MODES = {"potrace", "vtracer"}
VALID_OUTPUT_FORMATS = {"svg", "dxf", "json"}