Skip to content
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
**/.pytest_cache
**/.mypy_cache
**/.ruff_cache
**/tests
build

8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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 \
Expand Down
74 changes: 63 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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 <target>` 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 |
52 changes: 23 additions & 29 deletions deployments/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
21 changes: 21 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
@@ -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/*"
13 changes: 7 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ services:
build:
context: .
dockerfile: deployments/api/Dockerfile
volumes:
- ./deployments/api/src:/app/src
env_file:
- .env
environment:
Expand All @@ -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
Expand All @@ -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
Expand All @@ -53,7 +55,6 @@ services:
db:
condition: service_healthy


db-init:
build:
context: .
Expand Down