> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emergence.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Local Development

> Iterate on your solution locally with hot reload, mocked platform services, and fast feedback before deploying to the cluster.

# Local Development

This page covers three local-dev modes — choose based on what you're iterating on. The [Quickstart](/guides/solution-dev/quickstart) uses Kind; this page also covers the docker-compose loop (faster) and port-forward to a shared dev cluster (slowest, but real platform).

## Pick a mode

```mermaid theme={null}
%%{init: {'theme': 'base', 'themeVariables': {'lineColor': '#555555', 'fontFamily': 'sans-serif', 'edgeLabelBackground': '#ffffff'}}}%%
flowchart TD
    Q{"What are you<br/>iterating on?"}
    Q -->|"Service code only"| Q2{"Need real K8s<br/>semantics?"}
    Q -->|"Platform integration<br/>(identity + authz services)"| C["Port-forward to<br/>shared dev cluster<br/>(~30s per redeploy)"]
    Q2 -->|"No (probes mocked OK)"| A["docker-compose<br/>+ uvicorn --reload<br/>(&lt;5s hot reload)"]
    Q2 -->|"Yes (probes, ingress,<br/>configmaps)"| B["Kind cluster<br/>+ kind-load.sh<br/>(~30s per redeploy)"]

    classDef q fill:#F0E442,stroke:#555555,color:#000
    classDef mode fill:#0072B2,stroke:#555555,color:#fff
    class Q,Q2 q
    class A,B,C mode
```

| Mode                                   | Use when                                                                                 | Setup time | Iteration time     |
| -------------------------------------- | ---------------------------------------------------------------------------------------- | ---------- | ------------------ |
| **docker-compose**                     | Iterating on your service code only; platform services can be mocked or stubbed          | \~2 min    | \<5s (hot reload)  |
| **Kind**                               | You need a real Kubernetes context (probes, configmaps, ingress)                         | \~5 min    | \~30s per redeploy |
| **Port-forward to shared dev cluster** | You need real platform services (real identity/authorization services, real Connections) | \<1 min    | \~30s per redeploy |

Most loops are docker-compose. Move to Kind for the integration-test pass. Use the shared dev cluster only when you need cross-service coordination.

## Mode 1: docker-compose with hot reload

This is the daily-driver loop. Your service runs on the host with `uvicorn --reload`; supporting services (Postgres for your DB, MinIO for shared storage, a local identity provider) run in containers.

<Steps>
  <Step title="Write the compose file">
    ```yaml docker-compose.yaml theme={null}
    services:
      postgres:
        image: postgres:17-alpine
        environment:
          POSTGRES_USER: dev
          POSTGRES_PASSWORD: dev
          POSTGRES_DB: hello_solution
        ports: ["5432:5432"]
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U dev"]
          interval: 2s
          retries: 20

      minio:
        image: minio/minio:latest
        command: server /data --console-address ":9001"
        environment:
          MINIO_ROOT_USER: minioadmin
          MINIO_ROOT_PASSWORD: minioadmin
        ports: ["9000:9000", "9001:9001"]
        healthcheck:
          test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/ready"]
          interval: 2s
          retries: 20

      # Local identity provider for JWT validation. Uses the platform's standard
      # dev image so JWKS endpoints behave realistically.
      keycloak:
        image: quay.io/keycloak/keycloak:26.0
        command: ["start-dev"]
        environment:
          KEYCLOAK_ADMIN: admin
          KEYCLOAK_ADMIN_PASSWORD: admin
        ports: ["8080:8080"]
    ```
  </Step>

  <Step title="Write a .env.example">
    ```bash .env.example theme={null}
    # Database (matches docker-compose service)
    DATABASE_URL=postgresql+asyncpg://dev:dev@localhost:5432/hello_solution

    # Object storage (MinIO with path-style addressing)
    OBSTORE_ENDPOINT=http://localhost:9000
    OBSTORE_ACCESS_KEY=minioadmin
    OBSTORE_SECRET_KEY=minioadmin
    OBSTORE_BUCKET=hello-solution
    OBSTORE_PATH_STYLE=true

    # Auth (point at the local identity provider realm you create with the bootstrap below)
    KEYCLOAK_ISSUER_URL=http://localhost:8080/realms/dev
    KEYCLOAK_AUDIENCE=hello-solution-api

    # LLM gateway (set to a real LiteLLM gateway URL in dev/stg/prod;
    # for local-only loops you can stub the LLM client)
    LLM_GATEWAY_URL=http://localhost:4000
    LLM_GATEWAY_API_KEY=local-dev-key
    LLM_DEFAULT_MODEL=gemini-3.5-flash
    ```

    Copy to `.env` (which should be in `.gitignore`) before running.
  </Step>

  <Step title="Boot supporting services and run your app with hot reload">
    ```bash theme={null}
    docker compose up -d
    docker compose ps  # confirm all healthy

    # Pre-create the MinIO bucket
    docker run --rm --network host minio/mc \
      sh -c "mc alias set local http://localhost:9000 minioadmin minioadmin && mc mb -p local/hello-solution"

    # Run your service with hot reload
    set -a; source .env; set +a
    uv run uvicorn api.main:app --host 0.0.0.0 --port 8000 --reload
    ```

    Editing `packages/api/src/api/*.py` reloads the server inside \~1 second. Hit `http://localhost:8000/echo?msg=hi` to confirm.
  </Step>

  <Step title="Tear down">
    ```bash theme={null}
    docker compose down -v   # -v also removes the named volumes
    ```
  </Step>
</Steps>

## Mode 2: Kind cluster

Use Kind when you need real Kubernetes semantics (probes firing, ConfigMaps mounting, Ingress routing). The [Quickstart](/guides/solution-dev/quickstart) walks through this end-to-end. Make iteration faster with this rebuild-and-load script:

```bash kind-load.sh theme={null}
#!/usr/bin/env bash
set -euo pipefail

CLUSTER="${1:-hello-solution}"
TAG="${2:-dev}"

docker build -t hello-solution-api:$TAG .
kind load docker-image hello-solution-api:$TAG --name $CLUSTER

# Trigger a rolling restart (image pull policy IfNotPresent + same tag won't redeploy)
kubectl -n em-hello-solution rollout restart deployment/hello-solution-api
kubectl -n em-hello-solution rollout status deployment/hello-solution-api
```

Run `./kind-load.sh` after each code change. \~30 seconds per cycle.

## Mode 3: Port-forward to shared dev cluster

Use this when you need real platform behavior — real identity provider realms, real authorization data, real Connections. Run your code locally and forward platform services from a shared `dev` cluster.

```bash theme={null}
# Connect to your shared dev cluster (context provisioned out of band by your platform team)
kubectl config use-context <your-dev-cluster-context>

# Port-forward platform services to localhost.
# Each em-runtime service exposes Service port 8000; the local port is chosen for distinctness.
kubectl -n em-runtime port-forward svc/em-runtime-governance 8001:8000 &
kubectl -n em-runtime port-forward svc/em-runtime-assets    8002:8000 &
kubectl -n em-runtime port-forward svc/em-runtime-utils     8003:8000 &
kubectl -n em-runtime port-forward svc/keycloak             8080:8080 &

# Run your service against them
set -a; source .env.dev; set +a
uv run uvicorn api.main:app --reload
```

Useful pitfall to know about: when `port-forward` drops (any error in the cluster), curl from your service will fail until you restart the forward. Wrap forwards in a tiny supervisor script (`while true; do kubectl port-forward ... ; done`) for long-running loops.

## Mocking platform services

Three things are commonly mocked in unit tests:

1. **JWT verification** — generate a signed test token with a fixture private key; load the matching public key as `KEYCLOAK_JWKS_OVERRIDE` (a path to a JSON file your auth code prefers over the JWKS endpoint when set).
2. **Permission checks** — patch the Governance SDK's permission method to return `True` (or the value under test).
3. **LiteLLM gateway** — point `LLM_GATEWAY_URL` at a stub HTTP server that returns canned completions; or use `litellm`'s built-in mock provider (`model="mock-openai/..."`).

See [SDKs › Python](/sdks/python) for SDK extension points and [Authenticate Users](/guides/solution-dev/authenticate-users) for the JWT fixture pattern.

## Common pitfalls

<AccordionGroup>
  <Accordion title="Kind: services on the cluster cannot reach localhost">
    Containers in Kind cannot use `localhost` to reach your host machine. Use `host.docker.internal` (macOS/Windows) or the host's LAN IP (Linux). Or — better — run dependencies inside Kind with another `helm install`.
  </Accordion>

  <Accordion title="MinIO: SignatureDoesNotMatch errors">
    MinIO requires path-style addressing. Set `OBSTORE_PATH_STYLE=true` (or the equivalent in your S3 client config). Virtual-hosted style works on real S3 but not on MinIO without DNS magic.
  </Accordion>

  <Accordion title="Hot reload not picking up changes">
    `uvicorn --reload` watches the working directory by default. If your source is in a non-default location (e.g., `packages/api/src/api/`), pass `--reload-dir packages/api/src`. For container-based reload, mount the source directory as a volume and run uvicorn inside the container.
  </Accordion>

  <Accordion title="Missing X-Project-ID returning 400 from platform APIs">
    Every call to Governance, Assets, or Utils requires `X-Project-ID`. In local dev, hardcode a fixture project ID (e.g., `00000000-0000-0000-0000-000000000001`) until you wire user-driven project switching.
  </Accordion>
</AccordionGroup>

<h2 id="ci-smoke">
  CI smoke test
</h2>

The [Quickstart](/guides/solution-dev/quickstart) runs end-to-end in CI weekly via `.github/workflows/quickstart-smoke.yml`. The workflow provisions a Kind cluster, builds the example image, installs the chart, port-forwards, and asserts both endpoints respond. If it fails, an issue is opened automatically with the label `solution-dev-guide` — that is your signal to update the guide.

This is the strongest possible self-sustainability signal: the quickstart cannot rot silently.

## Next steps

<CardGroup cols={2}>
  <Card title="Authenticate users" icon="shield-check" href="/guides/solution-dev/authenticate-users">
    Wire JWT validation into your local app.
  </Card>

  <Card title="Manage secrets" icon="key" href="/guides/solution-dev/manage-secrets">
    Move secrets out of `.env` into the platform pipeline.
  </Card>

  <Card title="Use shared storage" icon="database" href="/guides/solution-dev/use-shared-storage">
    Replace MinIO test code with the obstore client.
  </Card>

  <Card title="Troubleshooting" icon="circle-exclamation" href="/guides/solution-dev/troubleshooting">
    Stuck? Check known causes by error symptom.
  </Card>
</CardGroup>
