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/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..8d9c356 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,15 +34,14 @@ Edit `.env` as needed (passwords, seed settings, etc.). Start (and build) the stack: ```bash -docker compose up --build - -# or, if you have make installed: -make dev-docker +docker compose -f docker-compose.yml -f docker-compose.local.yml up --build ``` -or, if already built: +Or use `make dev-docker` (see [Make Targets](#make-targets)). + +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,13 +58,61 @@ 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 - -# or, if you have make installed: -make clean-docker +docker compose -f docker-compose.yml -f docker-compose.local.yml down -v ``` 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 ``` + +## 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 | diff --git a/deployments/api/Dockerfile b/deployments/api/Dockerfile index 2526754..ef14924 100644 --- a/deployments/api/Dockerfile +++ b/deployments/api/Dockerfile @@ -1,44 +1,38 @@ -FROM python:3.12.12-slim-trixie AS builder +# ── base: Python + uv + third-party deps ───────────────────────────── +FROM python:3.12-slim-trixie AS base -# better console streaming for docker logs 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 - -# 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 workspace metadata -COPY pyproject.toml uv.lock ./ - -# 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 - -COPY packages/stitch-core/pyproject.toml packages/stitch-core/pyproject.toml -COPY packages/stitch-core/src packages/stitch-core/src +WORKDIR /app -# Install deps for the api project (important: target the subproject) -# Create venv and sync using the lock for the API project +# 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 \ - 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 --frozen --no-install-workspace --package stitch-api -# ------------------------------------------------------- +# ── runtime target ──────────────────────────────────────────────────── +FROM base AS runtime -FROM python:3.12.12-slim-trixie AS runtime +RUN groupadd --system --gid 999 stitch-app \ + && useradd --system --gid 999 --uid 999 --create-home stitch-app -ENV PYTHONUNBUFFERED=1 -WORKDIR /app +COPY ./deployments/api /app/deployments/api +COPY ./packages /app/packages -# Copy the ready-to-run virtualenv -COPY --from=builder /app/.venv /app/.venv -ENV PATH="/app/.venv/bin:$PATH" +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 -# Copy only the API source (runtime code) -COPY deployments/api/src /app/src -ENV PYTHONPATH=/app/src +ENV PATH="/app/.venv/bin:$PATH" +USER stitch-app CMD ["uvicorn", "stitch.api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.local.yml b/docker-compose.local.yml new file mode 100644 index 0000000..1808182 --- /dev/null +++ b/docker-compose.local.yml @@ -0,0 +1,21 @@ +services: + api: + environment: + LOG_LEVEL: debug + volumes: + - ./deployments/api/src:/app/deployments/api/src + - ./packages:/app/packages + command: + - 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/*" diff --git a/docker-compose.yml b/docker-compose.yml index 4adf7d2..4635c83 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,8 +4,6 @@ services: build: context: . dockerfile: deployments/api/Dockerfile - volumes: - - ./deployments/api/src:/app/src env_file: - .env environment: @@ -15,10 +13,10 @@ 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" + - "8000:8000" depends_on: db: condition: service_healthy @@ -28,7 +26,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 @@ -53,7 +55,6 @@ services: db: condition: service_healthy - db-init: build: context: .