From f6f83cce634a5c5d6e2ea128f2a6fea9cc1294b2 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Thu, 5 Feb 2026 23:18:07 -0700 Subject: [PATCH 1/9] initial tweaks to Dockerfile & dev compose --- .dockerignore | 1 + deployments/api/Dockerfile | 63 +++++++++++++++++++++------------- deployments/api/Dockerfile.api | 56 ++++++++++++++++++++++++++++++ docker-compose.yml | 6 +++- 4 files changed, 102 insertions(+), 24 deletions(-) create mode 100644 deployments/api/Dockerfile.api diff --git a/.dockerignore b/.dockerignore index 3307546..c2ce185 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,5 +7,6 @@ **/.pytest_cache **/.mypy_cache **/.ruff_cache +**/tests build diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index 2526754..dcb4895 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -1,44 +1,61 @@ -FROM python:3.12.12-slim-trixie AS builder +# ── shared base: Python + uv ────────────────────────────────────────── +FROM python:3.12.12-slim-trixie AS base-uv +COPY --from=ghcr.io/astral-sh/uv:0.9.25 /uv /uvx /bin/ -# better console streaming for docker logs ENV PYTHONUNBUFFERED=1 \ UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy WORKDIR /app -# Install uv -# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv -COPY --from=ghcr.io/astral-sh/uv:0.9.25 /uv /uvx /bin/ +COPY deployments/api/pyproject.toml deployments/api/pyproject.toml -# --- Copy workspace metadata -COPY pyproject.toml uv.lock ./ +# -- base installs 3rd part deps only +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-workspace --package stitch-api -# Copy the member pyprojects (and/or whole packages) that are needed to resolve deps -COPY deployments/api/pyproject.toml deployments/api/pyproject.toml -COPY deployments/api/src /app/src +# ── dev target: sync editable, we'll use `develop` in docker compose to watch/rebuild +FROM base-uv AS dev -COPY packages/stitch-core/pyproject.toml packages/stitch-core/pyproject.toml -COPY packages/stitch-core/src packages/stitch-core/src +COPY ./deployments/api /app/deployments/api +COPY ./packages /app/packages -# Install deps for the api project (important: target the subproject) -# Create venv and sync using the lock for the API project RUN --mount=type=cache,target=/root/.cache/uv \ - uv sync --locked --project deployments/api --no-install-workspace + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --package stitch-api + + +ENV PATH="/app/.venv/bin:$PATH" -# ------------------------------------------------------- +CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] + +# ── builder: full source → wheel + sdist ────────────────────────────── +FROM base-uv AS builder -FROM python:3.12.12-slim-trixie AS runtime +COPY pyproject.toml uv.lock ./ +COPY packages/stitch-core packages/stitch-core +COPY deployments/api deployments/api + +RUN --mount=type=cache,target=/root/.cache/uv \ + uv build --package stitch-api --out-dir /app/dist + +# ── prod target: clean image with only the installed wheel ──────────── +FROM python:3.12.12-slim-trixie AS prod ENV PYTHONUNBUFFERED=1 WORKDIR /app -# Copy the ready-to-run virtualenv -COPY --from=builder /app/.venv /app/.venv -ENV PATH="/app/.venv/bin:$PATH" +RUN useradd -m -u 1000 appuser -# Copy only the API source (runtime code) -COPY deployments/api/src /app/src -ENV PYTHONPATH=/app/src +COPY --from=builder /app/dist /tmp/dist +RUN python -m venv /app/.venv && \ + /app/.venv/bin/pip install --no-cache-dir --find-links /tmp/dist stitch-api && \ + rm -rf /tmp/dist + +ENV PATH="/app/.venv/bin:$PATH" +USER appuser CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/deployments/api/Dockerfile.api b/deployments/api/Dockerfile.api new file mode 100644 index 0000000..e8d6236 --- /dev/null +++ b/deployments/api/Dockerfile.api @@ -0,0 +1,56 @@ +FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder + +ENV PYTHONUNBUFFERED=1 + +ENV UV_COMPILE_BYTECODE=1 + +ENV UV_LINK_MODE=copy + +# Omit development dependencies +ENV UV_NO_DEV=1 + +# Disable Python downloads, because we want to use the system interpreter +# across both images. If using a managed Python version, it needs to be +# copied from the build image into the final image; see `standalone.Dockerfile` +# for an example. +ENV UV_PYTHON_DOWNLOADS=0 + +WORKDIR /app +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --frozen --no-install-workspace --package stitch-api + +COPY ./deployments/api /app/deployments/api +COPY ./packages /app/packages + +RUN --mount=type=cache,target=/root/.cache/uv \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync --locked --package stitch-api + + +# Then, use a final image without uv +FROM python:3.12-slim-bookworm +# It is important to use the image that matches the builder, as the path to the +# Python executable must be the same, e.g., using `python:3.11-slim-bookworm` +# will fail. + +# Setup a non-root user +RUN groupadd --system --gid 999 nonroot \ + && useradd --system --gid 999 --uid 999 --create-home nonroot + +# Copy the application from the builder +COPY --from=builder --chown=nonroot:nonroot /app /app + +# Place executables in the environment at the front of the path +ENV PATH="/app/.venv/bin:$PATH" + +# Use the non-root user to run our application +USER nonroot + +# Use `/app` as the working directory +WORKDIR /app + +# Run the FastAPI application by default +CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml index 4adf7d2..fc0fe6e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,9 @@ services: build: context: . dockerfile: deployments/api/Dockerfile + target: dev volumes: - - ./deployments/api/src:/app/src + - .:/app env_file: - .env environment: @@ -58,6 +59,9 @@ services: build: context: . dockerfile: deployments/api/Dockerfile + target: dev + volumes: + - .:/app env_file: - .env environment: From 412596f16ceac9aa550dc173ddafde17ce43f5a3 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Fri, 6 Feb 2026 11:29:43 -0700 Subject: [PATCH 2/9] feat(docker): reconcile Dockerfile & compose for dev/prod targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite Dockerfile: 3 stages (base→dev→prod), drop builder stage - Switch from trixie to bookworm, add UV_NO_DEV and UV_PYTHON_DOWNLOADS=0 - Prod copies venv from dev instead of building/installing wheels - Replace volume bind mounts with develop.watch in compose - Add --reload via compose command override - Delete Dockerfile.api prototype --- deployments/api/Dockerfile | 43 ++++++++++---------------- deployments/api/Dockerfile.api | 56 ---------------------------------- docker-compose.yml | 36 +++++++++++++++++----- 3 files changed, 44 insertions(+), 91 deletions(-) delete mode 100644 deployments/api/Dockerfile.api diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index dcb4895..872593b 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -1,23 +1,25 @@ -# ── shared base: Python + uv ────────────────────────────────────────── -FROM python:3.12.12-slim-trixie AS base-uv +# ── base: Python + uv + third-party deps ───────────────────────────── +FROM python:3.12-slim-bookworm AS base + COPY --from=ghcr.io/astral-sh/uv:0.9.25 /uv /uvx /bin/ ENV PYTHONUNBUFFERED=1 \ UV_COMPILE_BYTECODE=1 \ - UV_LINK_MODE=copy + UV_LINK_MODE=copy \ + UV_PYTHON_DOWNLOADS=0 \ + UV_NO_DEV=1 WORKDIR /app -COPY deployments/api/pyproject.toml deployments/api/pyproject.toml - -# -- base installs 3rd part deps only +# Third-party deps only — cached until uv.lock changes. +# All metadata via bind mounts (not copied into the layer). RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --no-install-workspace --package stitch-api -# ── dev target: sync editable, we'll use `develop` in docker compose to watch/rebuild -FROM base-uv AS dev +# ── dev target ──────────────────────────────────────────────────────── +FROM base AS dev COPY ./deployments/api /app/deployments/api COPY ./packages /app/packages @@ -27,35 +29,22 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --package stitch-api - ENV PATH="/app/.venv/bin:$PATH" CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] -# ── builder: full source → wheel + sdist ────────────────────────────── -FROM base-uv AS builder - -COPY pyproject.toml uv.lock ./ -COPY packages/stitch-core packages/stitch-core -COPY deployments/api deployments/api - -RUN --mount=type=cache,target=/root/.cache/uv \ - uv build --package stitch-api --out-dir /app/dist - -# ── prod target: clean image with only the installed wheel ──────────── -FROM python:3.12.12-slim-trixie AS prod +# ── prod target ─────────────────────────────────────────────────────── +FROM python:3.12-slim-bookworm AS prod ENV PYTHONUNBUFFERED=1 WORKDIR /app -RUN useradd -m -u 1000 appuser +RUN groupadd --system --gid 999 nonroot \ + && useradd --system --gid 999 --uid 999 --create-home nonroot -COPY --from=builder /app/dist /tmp/dist -RUN python -m venv /app/.venv && \ - /app/.venv/bin/pip install --no-cache-dir --find-links /tmp/dist stitch-api && \ - rm -rf /tmp/dist +COPY --from=dev --chown=nonroot:nonroot /app /app ENV PATH="/app/.venv/bin:$PATH" -USER appuser +USER nonroot CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/deployments/api/Dockerfile.api b/deployments/api/Dockerfile.api deleted file mode 100644 index e8d6236..0000000 --- a/deployments/api/Dockerfile.api +++ /dev/null @@ -1,56 +0,0 @@ -FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder - -ENV PYTHONUNBUFFERED=1 - -ENV UV_COMPILE_BYTECODE=1 - -ENV UV_LINK_MODE=copy - -# Omit development dependencies -ENV UV_NO_DEV=1 - -# Disable Python downloads, because we want to use the system interpreter -# across both images. If using a managed Python version, it needs to be -# copied from the build image into the final image; see `standalone.Dockerfile` -# for an example. -ENV UV_PYTHON_DOWNLOADS=0 - -WORKDIR /app -RUN --mount=type=cache,target=/root/.cache/uv \ - --mount=type=bind,source=uv.lock,target=uv.lock \ - --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --frozen --no-install-workspace --package stitch-api - -COPY ./deployments/api /app/deployments/api -COPY ./packages /app/packages - -RUN --mount=type=cache,target=/root/.cache/uv \ - --mount=type=bind,source=uv.lock,target=uv.lock \ - --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ - uv sync --locked --package stitch-api - - -# Then, use a final image without uv -FROM python:3.12-slim-bookworm -# It is important to use the image that matches the builder, as the path to the -# Python executable must be the same, e.g., using `python:3.11-slim-bookworm` -# will fail. - -# Setup a non-root user -RUN groupadd --system --gid 999 nonroot \ - && useradd --system --gid 999 --uid 999 --create-home nonroot - -# Copy the application from the builder -COPY --from=builder --chown=nonroot:nonroot /app /app - -# Place executables in the environment at the front of the path -ENV PATH="/app/.venv/bin:$PATH" - -# Use the non-root user to run our application -USER nonroot - -# Use `/app` as the working directory -WORKDIR /app - -# Run the FastAPI application by default -CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml index fc0fe6e..24a9a52 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,31 @@ services: - api: build: context: . dockerfile: deployments/api/Dockerfile target: dev - volumes: - - .:/app + command: + - uvicorn + - stitch.api.main:app + - --host + - "0.0.0.0" + - --port + - "8000" + - --reload + develop: + watch: + - path: ./deployments/api/src + action: sync + target: /app/deployments/api/src + - path: ./packages/stitch-core/src + action: sync + target: /app/packages/stitch-core/src + - path: ./deployments/api/pyproject.toml + action: rebuild + - path: ./packages/stitch-core/pyproject.toml + action: rebuild + - path: ./uv.lock + action: rebuild env_file: - .env environment: @@ -19,7 +38,7 @@ services: STITCH_DB_PASSWORD: ${STITCH_APP_PASSWORD} ports: - - "8000:8000" + - "8000:8000" depends_on: db: condition: service_healthy @@ -29,7 +48,11 @@ services: db: image: postgres:17-alpine healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-stitch} || exit 1"] + test: + [ + "CMD-SHELL", + "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-stitch} || exit 1", + ] interval: 10s timeout: 5s retries: 10 @@ -54,14 +77,11 @@ services: db: condition: service_healthy - db-init: build: context: . dockerfile: deployments/api/Dockerfile target: dev - volumes: - - .:/app env_file: - .env environment: From d61aaeaa0b190a9229c6772eaca3447077a1a78b Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 15:53:22 -0700 Subject: [PATCH 3/9] fix(docker): tighten Dockerfile and compose config --- deployments/api/Dockerfile | 20 +++++++++++--------- docker-compose.yml | 10 +--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index 872593b..a85e90d 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -1,7 +1,5 @@ # ── base: Python + uv + third-party deps ───────────────────────────── -FROM python:3.12-slim-bookworm AS base - -COPY --from=ghcr.io/astral-sh/uv:0.9.25 /uv /uvx /bin/ +FROM python:3.12-slim-trixie AS base ENV PYTHONUNBUFFERED=1 \ UV_COMPILE_BYTECODE=1 \ @@ -9,6 +7,8 @@ ENV PYTHONUNBUFFERED=1 \ UV_PYTHON_DOWNLOADS=0 \ UV_NO_DEV=1 +COPY --from=ghcr.io/astral-sh/uv:0.9.25 /uv /uvx /bin/ + WORKDIR /app # Third-party deps only — cached until uv.lock changes. @@ -31,20 +31,22 @@ RUN --mount=type=cache,target=/root/.cache/uv \ ENV PATH="/app/.venv/bin:$PATH" -CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] # ── prod target ─────────────────────────────────────────────────────── -FROM python:3.12-slim-bookworm AS prod +FROM python:3.12-slim-trixie AS prod ENV PYTHONUNBUFFERED=1 WORKDIR /app -RUN groupadd --system --gid 999 nonroot \ - && useradd --system --gid 999 --uid 999 --create-home nonroot +RUN groupadd --system --gid 999 stitch-app \ + && useradd --system --gid 999 --uid 999 --create-home stitch-app -COPY --from=dev --chown=nonroot:nonroot /app /app +COPY --from=dev --chown=stitch-app:stitch-app /app/.venv /app/.venv +COPY --from=dev --chown=stitch-app:stitch-app /app/deployments/api /app/deployments/api +COPY --from=dev --chown=stitch-app:stitch-app /app/packages/stitch-core /app/packages/stitch-core ENV PATH="/app/.venv/bin:$PATH" -USER nonroot +USER stitch-app CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml index 24a9a52..6b1186a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,14 +4,6 @@ services: context: . dockerfile: deployments/api/Dockerfile target: dev - command: - - uvicorn - - stitch.api.main:app - - --host - - "0.0.0.0" - - --port - - "8000" - - --reload develop: watch: - path: ./deployments/api/src @@ -35,7 +27,7 @@ services: POSTGRES_PORT: 5432 # API connects as the app role (no DDL) STITCH_DB_USER: stitch_app - STITCH_DB_PASSWORD: ${STITCH_APP_PASSWORD} + STITCH_DB_PASSWORD: ${STITCH_APP_PASSWORD:?STITCH_APP_PASSWORD must be set in .env} ports: - "8000:8000" From 8d5b19ac5255fffe1d4ce2d83a33f3d2a3b0d6df Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 16:04:44 -0700 Subject: [PATCH 4/9] refactor(docker): split compose into base and local dev override --- Makefile | 8 ++++++-- README.md | 13 +++++++++---- docker-compose.local.yml | 23 +++++++++++++++++++++++ docker-compose.yml | 15 --------------- 4 files changed, 38 insertions(+), 21 deletions(-) create mode 100644 docker-compose.local.yml diff --git a/Makefile b/Makefile index 30404db..06f1035 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ UV ?= uv -DOCKER_COMPOSE := docker compose +DOCKER_COMPOSE := docker compose -f docker-compose.yml +DOCKER_COMPOSE_DEV := $(DOCKER_COMPOSE) -f docker-compose.local.yml PYTEST := $(UV) run pytest RUFF := $(UV) run ruff @@ -127,9 +128,12 @@ frontend-clean: # docker clean-docker: - $(DOCKER_COMPOSE) down --volumes --remove-orphans + $(DOCKER_COMPOSE_DEV) down --volumes --remove-orphans dev-docker: + $(DOCKER_COMPOSE_DEV) up + +prod-docker: $(DOCKER_COMPOSE) up .PHONY: all build clean \ diff --git a/README.md b/README.md index 007c0b9..83f124e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ Stitch is a platform that integrates diverse oil & gas asset datasets, applies A Local development is run via Docker Compose (DB + API + Frontend) with optional DB initialization/seeding. +The stack uses two compose files: + +- **`docker-compose.yml`** — base services (API, DB, frontend, etc.) +- **`docker-compose.local.yml`** — local dev overrides (dev build target, debug logging, file-watch sync) + ### Prerequisites - Docker Desktop (includes Docker Engine + Docker Compose) @@ -29,7 +34,7 @@ Edit `.env` as needed (passwords, seed settings, etc.). Start (and build) the stack: ```bash -docker compose up --build +docker compose -f docker-compose.yml -f docker-compose.local.yml up --build # or, if you have make installed: make dev-docker @@ -37,7 +42,7 @@ make dev-docker or, if already built: ```bash -docker compose up db api frontend +docker compose -f docker-compose.yml -f docker-compose.local.yml up db api frontend ``` Useful URLs: @@ -54,7 +59,7 @@ Note: The `db-init` service runs automatically (via `depends_on`) to apply schem Stop containers and delete the Postgres volume (this removes all local DB data): ```bash -docker compose down -v +docker compose -f docker-compose.yml -f docker-compose.local.yml down -v # or, if you have make installed: make clean-docker @@ -62,5 +67,5 @@ make clean-docker Then start fresh: ```bash -docker compose up db api frontend +docker compose -f docker-compose.yml -f docker-compose.local.yml up db api frontend ``` diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..c25bfad --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,23 @@ +services: + api: + build: + target: dev + environment: + LOG_LEVEL: debug + develop: + watch: + # Source directories — synced into the running container. + # NOTE: these must stay in sync with workspace deps of stitch-api. + - path: ./deployments/api/src + action: sync + target: /app/deployments/api/src + - path: ./packages/stitch-core/src + action: sync + target: /app/packages/stitch-core/src + # Dependency changes — full rebuild. + - path: ./deployments/api/pyproject.toml + action: rebuild + - path: ./packages/stitch-core/pyproject.toml + action: rebuild + - path: ./uv.lock + action: rebuild diff --git a/docker-compose.yml b/docker-compose.yml index 6b1186a..38bc365 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,21 +3,6 @@ services: build: context: . dockerfile: deployments/api/Dockerfile - target: dev - develop: - watch: - - path: ./deployments/api/src - action: sync - target: /app/deployments/api/src - - path: ./packages/stitch-core/src - action: sync - target: /app/packages/stitch-core/src - - path: ./deployments/api/pyproject.toml - action: rebuild - - path: ./packages/stitch-core/pyproject.toml - action: rebuild - - path: ./uv.lock - action: rebuild env_file: - .env environment: From d985079c04432478eba98c7864afbe53c0ab4028 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 16:31:11 -0700 Subject: [PATCH 5/9] docs(readme): add Make Targets section --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 83f124e..8d9c356 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,11 @@ Edit `.env` as needed (passwords, seed settings, etc.). Start (and build) the stack: ```bash docker compose -f docker-compose.yml -f docker-compose.local.yml up --build - -# or, if you have make installed: -make dev-docker ``` -or, if already built: +Or use `make dev-docker` (see [Make Targets](#make-targets)). + +Or, if already built: ```bash docker compose -f docker-compose.yml -f docker-compose.local.yml up db api frontend ``` @@ -60,12 +59,60 @@ Note: The `db-init` service runs automatically (via `depends_on`) to apply schem Stop containers and delete the Postgres volume (this removes all local DB data): ```bash docker compose -f docker-compose.yml -f docker-compose.local.yml down -v - -# or, if you have make installed: -make clean-docker ``` Then start fresh: ```bash docker compose -f docker-compose.yml -f docker-compose.local.yml up db api frontend ``` + +## Make Targets + +Most common operations have `make` shortcuts. Run `make ` from the repo root. + +### Build + +| Target | Description | +|---|---| +| `make all` | Build all Python packages and the frontend | +| `make build-python` | Build all discovered Python packages (under `packages/`) | +| `make build-python PKG=stitch-core` | Build a single package by name | +| `make frontend` | Build the frontend | + +Python package discovery is automatic — any subdirectory of `packages/` with a `pyproject.toml` is included. Builds are incremental via stamp files under `build/`. + +### Check / Lint / Test + +| Target | Description | +|---|---| +| `make check` | Run all checks (lint, test, format-check, lock-check) | +| `make lint` | Run Python and frontend linters | +| `make test` | Run Python and frontend tests | +| `make format` | Auto-format Python and frontend code | +| `make format-check` | Check formatting without modifying files | +| `make lock-check` | Verify `uv.lock` is up to date | + +### Docker + +| Target | Description | +|---|---| +| `make dev-docker` | Start the full local-dev stack | +| `make prod-docker` | Start without local-dev overrides | +| `make docker-exec SVC=api` | Open a shell in a running container | +| `make docker-run SVC=api` | Spin up a one-off container with a shell | +| `make docker-logs SVC=api` | Tail logs for a service | +| `make docker-ps` | List running containers | +| `make stop-docker` | Stop containers (keep volumes) | +| `make clean-docker` | Stop containers and delete volumes | + +`SVC` defaults to `api` if omitted. + +### Clean + +| Target | Description | +|---|---| +| `make clean` | Run all clean targets | +| `make clean-build` | Remove `build/` and `dist/` | +| `make clean-cache` | Remove `.ruff_cache` and `.pytest_cache` | +| `make clean-docker` | Stop containers and delete volumes | +| `make frontend-clean` | Remove frontend `dist/`, `node_modules`, and stamps | From 4bb97ee4cac4fe52b398121b6444bbf21da8f85d Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 16:58:04 -0700 Subject: [PATCH 6/9] refactor(docker): use volume mounts instead of develop.watch --- deployments/api/Dockerfile | 2 +- docker-compose.local.yml | 20 +++----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index a85e90d..d587cfd 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -44,7 +44,7 @@ RUN groupadd --system --gid 999 stitch-app \ COPY --from=dev --chown=stitch-app:stitch-app /app/.venv /app/.venv COPY --from=dev --chown=stitch-app:stitch-app /app/deployments/api /app/deployments/api -COPY --from=dev --chown=stitch-app:stitch-app /app/packages/stitch-core /app/packages/stitch-core +COPY --from=dev --chown=stitch-app:stitch-app /app/packages /app/packages ENV PATH="/app/.venv/bin:$PATH" USER stitch-app diff --git a/docker-compose.local.yml b/docker-compose.local.yml index c25bfad..787d01f 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -4,20 +4,6 @@ services: target: dev environment: LOG_LEVEL: debug - develop: - watch: - # Source directories — synced into the running container. - # NOTE: these must stay in sync with workspace deps of stitch-api. - - path: ./deployments/api/src - action: sync - target: /app/deployments/api/src - - path: ./packages/stitch-core/src - action: sync - target: /app/packages/stitch-core/src - # Dependency changes — full rebuild. - - path: ./deployments/api/pyproject.toml - action: rebuild - - path: ./packages/stitch-core/pyproject.toml - action: rebuild - - path: ./uv.lock - action: rebuild + volumes: + - ./deployments/api/src:/app/deployments/api/src + - ./packages:/app/packages From 689b916f2e22ac4ea732a9ecf8f29163a807f5ef Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 17:26:32 -0700 Subject: [PATCH 7/9] refactor(docker): collapse to 2-stage build (base + runtime) --- deployments/api/Dockerfile | 24 +++++------------------- docker-compose.local.yml | 13 ++++++++++--- docker-compose.yml | 1 - 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index d587cfd..ef14924 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -18,8 +18,11 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --frozen --no-install-workspace --package stitch-api -# ── dev target ──────────────────────────────────────────────────────── -FROM base AS dev +# ── runtime target ──────────────────────────────────────────────────── +FROM base AS runtime + +RUN groupadd --system --gid 999 stitch-app \ + && useradd --system --gid 999 --uid 999 --create-home stitch-app COPY ./deployments/api /app/deployments/api COPY ./packages /app/packages @@ -29,23 +32,6 @@ RUN --mount=type=cache,target=/root/.cache/uv \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ uv sync --locked --package stitch-api -ENV PATH="/app/.venv/bin:$PATH" - -CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] - -# ── prod target ─────────────────────────────────────────────────────── -FROM python:3.12-slim-trixie AS prod - -ENV PYTHONUNBUFFERED=1 -WORKDIR /app - -RUN groupadd --system --gid 999 stitch-app \ - && useradd --system --gid 999 --uid 999 --create-home stitch-app - -COPY --from=dev --chown=stitch-app:stitch-app /app/.venv /app/.venv -COPY --from=dev --chown=stitch-app:stitch-app /app/deployments/api /app/deployments/api -COPY --from=dev --chown=stitch-app:stitch-app /app/packages /app/packages - ENV PATH="/app/.venv/bin:$PATH" USER stitch-app diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 787d01f..d5aad61 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -1,9 +1,16 @@ services: api: - build: - target: dev environment: LOG_LEVEL: debug volumes: - ./deployments/api/src:/app/deployments/api/src - - ./packages:/app/packages + - ./packages/stitch-core/src:/app/packages/stitch-core/src + develop: + watch: + - path: ./uv.lock + action: rebuild + - path: ./deployments/api/pyproject.toml + action: rebuild + + command: + ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/docker-compose.yml b/docker-compose.yml index 38bc365..99622a0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,7 +58,6 @@ services: build: context: . dockerfile: deployments/api/Dockerfile - target: dev env_file: - .env environment: From 25d3a62dbe622d17d73118ed1760e7faf6102364 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Mon, 9 Feb 2026 20:50:21 -0700 Subject: [PATCH 8/9] refactor(compose): widen volume mounts with scoped reload dirs --- docker-compose.local.yml | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/docker-compose.local.yml b/docker-compose.local.yml index d5aad61..1808182 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -4,13 +4,18 @@ services: LOG_LEVEL: debug volumes: - ./deployments/api/src:/app/deployments/api/src - - ./packages/stitch-core/src:/app/packages/stitch-core/src - develop: - watch: - - path: ./uv.lock - action: rebuild - - path: ./deployments/api/pyproject.toml - action: rebuild - + - ./packages:/app/packages command: - ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] + - uvicorn + - stitch.api.main:app + - --host + - "0.0.0.0" + - --port + - "8000" + - --reload + - --reload-dir + - /app/deployments/api/src + - --reload-dir + - /app/packages + - --reload-exclude + - "*/tests/*" From 0b4bb6789f0473610c4d2c2c7c63e9e8eff480c6 Mon Sep 17 00:00:00 2001 From: Michael Barlow Date: Wed, 11 Feb 2026 13:39:38 -0700 Subject: [PATCH 9/9] chore: whitespace --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 99622a0..4635c83 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,5 @@ services: + api: build: context: .