Skip to content

Run locally with Docker Compose

This guide walks you through running the complete Kessel stack on your local machine with a single command. The setup includes Kessel Inventory API, Kessel Relations API, SpiceDB, RBAC, Kafka, Kafka Connect, and Kessel Inventory Consumer — everything you need for development and integration testing.

  1. Podman or Docker with the compose plugin.

    The startup script auto-detects Podman or Docker. Ensure the podman compose (or docker compose) subcommand is available — the standalone docker-compose binary is not supported.

  2. yq — used by the startup script to extract RBAC role definitions from the upstream configmap.

    Terminal window
    go install github.com/mikefarah/yq/v4@latest
  3. curl — used to download the SpiceDB schema and RBAC configuration. Usually pre-installed on Linux and macOS.

  4. Git — to clone the repository.

  1. Clone the Inventory API repository.

    Terminal window
    git clone https://github.com/project-kessel/inventory-api.git
    cd inventory-api
  2. Start all services.

    Terminal window
    make kessel-up

    This command:

    • Creates a shared kessel network (if it doesn’t already exist)
    • Downloads the SpiceDB schema from the stage rbac-config repo
    • Fetches and extracts RBAC role definitions from the same repo using yq
    • Starts all services via compose with the appropriate profiles

To check that all containers are running and healthy (Docker users: substitute docker for podman):

Terminal window
podman compose -f development/full-kessel/docker-compose.yaml \
--profile relations --profile consumer --profile rbac \
ps --format "table {{.Name}}\t{{.Status}}"
PortServiceProtocol
8081Inventory APIHTTP
9081Inventory APIgRPC
8000Relations API*HTTP
9000Relations API*gRPC
50051SpiceDBgRPC
9080RBAC ServerHTTP
8083Kafka ConnectHTTP
9092Kafka
5432SpiceDB DatabasePostgres
5433Inventory DatabasePostgres
15432RBAC DatabasePostgres
6379Redis

*Relations API will be absorbed into Inventory API in a future release. The Relations API service and its ports will be removed from the compose setup at that time. SpiceDB and its database will remain — Inventory API will use them directly.

Quick smoke tests to confirm the stack is working:

  1. Inventory API (HTTP)

    Terminal window
    curl -sf http://localhost:8081/api/kessel/v1/livez
  2. RBAC Server (HTTP)

    Terminal window
    curl -sf http://localhost:9080/metrics | head -5
  3. Kafka Connect (HTTP)

    Terminal window
    curl -sf http://localhost:8083/connectors

For a comprehensive end-to-end validation, run the integration test suite. This exercises the full service flow: tenant bootstrapping via RBAC V2, simulated host events via Kafka, Inventory Consumer processing, Relations API tuple verification, and resource deletion.

Terminal window
make kessel-compose-integration-test

This requires additional tools: kcat, grpcurl, jq, and curl.

To use your own SpiceDB schema instead of the default one downloaded from GitHub:

Terminal window
SCHEMA_ZED_FILE=/path/to/your/schema.zed make kessel-up

You can also change the download URL by editing SCHEMA_ZED_URL in development/full-kessel/.env.

Edit development/full-kessel/.env to use locally built or alternative images:

INVENTORY_API_IMAGE=localhost/kessel-inventory:dev
RELATIONS_API_IMAGE=localhost/relations-api:dev
INVENTORY_CONSUMER_IMAGE=localhost/inventory-consumer:dev
RBAC_IMAGE=localhost/rbac:dev

To include Prometheus, Grafana, and Alertmanager:

Terminal window
make kessel-up-monitoring
PortService
9050Prometheus
3000Grafana
9093Alertmanager

Grafana is available at http://localhost:3000 with default login admin / admin. It is pre-loaded with a local Prometheus datasource and the current Kessel dashboards.

If your service uses the outbox pattern with Debezium CDC to report resources, you can register your own connector against the Kafka Connect pod that is already running in the full-kessel stack.

  1. Create an outbox table in your service’s database. The table schema must match the Debezium Outbox Event Router format:

    CREATE TABLE outbox (
    id UUID NOT NULL,
    aggregatetype VARCHAR(255) NOT NULL,
    aggregateid VARCHAR(255) NOT NULL,
    version VARCHAR(255) NOT NULL,
    operation VARCHAR(255) NOT NULL,
    payload JSONB,
    PRIMARY KEY (id)
    );
  2. Ensure your database has WAL-level set to logical. This is required for Debezium to read from the PostgreSQL write-ahead log. If you’re adding a new database to the compose file, include -c wal_level=logical in the Postgres command.

  3. Create a connector configuration file. This example connects to a database called myservice-db and captures changes from public.outbox:

    my-service-connector.json
    {
    "name": "my-service-outbox-connector",
    "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.server.name": "my-service",
    "database.dbname": "my_database",
    "database.hostname": "myservice-db",
    "database.port": "5432",
    "database.user": "postgres",
    "database.password": "postgres",
    "topic.prefix": "my-service",
    "table.include.list": "public.outbox",
    "transforms": "outbox",
    "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter",
    "transforms.outbox.table.fields.additional.placement": "operation:header,version:header",
    "transforms.outbox.table.expand.json.payload": true,
    "value.converter": "org.apache.kafka.connect.json.JsonConverter",
    "plugin.name": "pgoutput",
    "slot.name": "my_service_debezium",
    "snapshot.mode": "no_data",
    "poll.interval.ms": 250
    }
    }

    Key fields to customize:

    • name and slot.name: Must be unique across all connectors on the Kafka Connect pod
    • database.*: Connection details for your service’s database
    • table.include.list: The schema-qualified outbox table name
    • topic.prefix: Debezium uses this with aggregatetype to form the output topic name (outbox.event.{aggregatetype})
  4. Register the connector with the Kafka Connect REST API:

    Terminal window
    curl -sf -X POST http://localhost:8083/connectors \
    -H 'Content-Type: application/json' \
    -d @my-service-connector.json
  5. Verify the connector is running:

    Terminal window
    curl -sf http://localhost:8083/connectors/my-service-outbox-connector/status | jq .

    The output should show "state": "RUNNING" for both the connector and its task.

Terminal window
make kessel-down

This stops and removes all containers but preserves volumes (database data, Grafana state).

The compose file uses fixed host ports. If a port is already in use, podman compose up fails with an “address already in use” error. Find the conflicting process:

Terminal window
lsof -i :8081
# or
ss -tlnp | grep 8081

Stop the conflicting service before restarting.

Check the logs for the specific service:

Terminal window
podman compose -f development/full-kessel/docker-compose.yaml logs inventory-api --tail 50

Common causes:

  • Database not ready — usually self-resolves via healthcheck dependencies. Wait 30–60 seconds and check again.
  • Schema download failure — network issue reaching GitHub. See below.

The startup script downloads schema.zed and RBAC role definitions from GitHub. If this fails (e.g., behind a firewall or VPN), use local files:

Terminal window
SCHEMA_ZED_FILE=./my-schema.zed RBAC_CONFIG_FILE=./my-rbac-config.yml make kessel-up

Verify all containers are on the shared network:

Terminal window
podman network inspect kessel --format '{{range .Containers}}{{.Name}} {{end}}'

If the network is missing, make kessel-down followed by make kessel-up recreates it.