Skip to content

Deployment

The platform runs as a set of containerized services. The spike runs locally; production needs a different shape.

Components

ComponentPurposeSpikeProduction
Crawler CLIRun a crawlLocalContainerized job
SchedulerTrigger crawls on a scheduleNoneCron / k8s CronJob
SQLite storePrimary data storeLocal fileShared volume or managed
MCP serverServe data to agentsStdioHTTP/SSE
Backup jobSnapshot SQLiteNoneDaily to object storage

Container Layout

# Dockerfile
FROM python:3.12-slim

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv

# Copy project
WORKDIR /app
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen

COPY . .

# Default: serve the MCP server
CMD ["uv", "run", "python", "crawler.py", "serve"]

The same image runs as the MCP server, the CLI, and the scheduler. Different CMD per use case.

Volumes

The SQLite database lives on a persistent volume. Backups write to object storage (S3, GCS, etc.).

# docker-compose.yml (development)
services:
  intelligence:
    build: .
    volumes:
      - ./data:/app/data
    command: uv run python crawler.py serve

Scheduler

A k8s CronJob (or equivalent) runs the CLI on each vertical's schedule:

# k8s cronjob
apiVersion: batch/v1
kind: CronJob
metadata:
  name: intelligence-dmc-weekly
spec:
  schedule: "0 3 * * 0"
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: intelligence
            image: intelligence:latest
            command: ["uv", "run", "python", "crawler.py", "crawl", "--source", "all"]
            volumeMounts:
            - name: data
              mountPath: /app/data
          restartPolicy: OnFailure
  volume:
    - name: data
      persistentVolumeClaim:
        claimName: intelligence-data

The same template, one CronJob per vertical.

MCP Server Transport

The spike uses stdio. Production needs HTTP so remote agents can connect.

# Production: run with HTTP transport
if __name__ == "__main__":
    mcp.run(transport="http", host="0.0.0.0", port=8000)

The MCP server runs as a long-lived service behind a load balancer. Agents connect over HTTP/SSE.

Configuration

All config via environment variables:

INTELLIGENCE_DATA_DIR=/app/data
INTELLIGENCE_LOG_LEVEL=info
INTELLIGENCE_MCP_TRANSPORT=http
INTELLIGENCE_MCP_PORT=8000
INTELLIGENCE_SCHEDULE_CONFIG=/app/config/schedule.yaml

No secrets needed (the platform crawls public sources, not authenticated APIs). Future verticals with API access (Experiences, Hotels) will need secrets management per the Integrations Architecture.

Health & Readiness

  • /healthz — process is up
  • /readyz — SQLite is reachable, last crawl wasn't catastrophically broken
  • /metrics — Prometheus

CI/CD

  1. Lint (ruff)
  2. Type check (mypy)
  3. Unit tests
  4. Integration tests (against a sandbox source, where possible)
  5. Build image
  6. Push to registry
  7. Deploy to staging
  8. Smoke test against staging
  9. Manual approval for prod
  10. Deploy to prod

Cloud

Container-agnostic. Any container host works (ECS, Cloud Run, k8s, Fly). The persistent volume is the only stateful piece.

Local Development

git clone ...
cd intelligence
uv sync
uv run python crawler.py crawl --source inside_travel -n 5 --no-deep-crawl
uv run python crawler.py search --destination "Greece"
uv run python crawler.py serve

Local dev uses the same SQLite file. The data/ directory is gitignored.

Marchay Platform Documentation