Run a service locally

Running a service locally is helpful to test and debug. The following sections provide commands for starting and stopping a single service locally.

Prerequisites

In order to run your service locally, you’ll need to have the following prerequisites:

  • Docker 20.10.14 or higher.

  • Access to the gcr.io/kalix-public container registry. This is a public container registry that provides a Kalix Runtime container suited for local development. Make sure this registry is not blocked by your firewall.

The samples provided with the Kalix SDKs all have docker-compose files preconfigured to run your service locally.

Starting your service

To start your service locally, run the following command from the root of your project:

Java
mvn kalix:runAll
Scala
sbt runAll

This command will start your Kalix service and a Kalix Runtime using the included docker-compose.yml file.

If you prefer, you can instead start docker-compose manually by running docker-compose up in one terminal and in another terminal start your Kalix service with:

Java
mvn kalix:run
Scala
sbt run

Invoking your service

After you start the service it will accept invocations on localhost:9000. You can use gRPCurl to invoke your service.

As an example, we will use the customer-registry sample.

As an example, we will use the customer-registry sample.

You can inspect your services with the gRPC list and describe commands:

grpcurl -plaintext localhost:9000 list

grpcurl -plaintext localhost:9000 describe customer.api.CustomerService

Be sure to use gRPCurl 1.8.7 (or above) since previous versions have been found to have some issues while describing an endpoint.

Get a customer value from the CustomerService:

Linux or macOS
grpcurl \
 -d '{"customer_id": "vip", "email": "vip@example.com", "name": "Very Important", "address": {"street": "Road 1", "city": "The Capital"}}' \
 --plaintext localhost:9000 \
  customer.api.CustomerService/Create
  • The message payload in JSON format with -d

  • The address of the Kalix Runtime (using plaintext instead of TLS)

  • The RPC procedure name which is deduced from the protobuf definition of the component you’re calling.

Windows 10+
grpcurl ^
 -d '{"customer_id": "vip", "email": "vip@example.com", "name": "Very Important", "address": {"street": "Road 1", "city": "The Capital"}}' ^
 --plaintext localhost:9000 ^
  customer.api.CustomerService/Create
  • The message payload in JSON format with -d

  • The address of the Kalix Runtime (using plaintext instead of TLS)

  • The RPC procedure name which is deduced from the protobuf definition of the component you’re calling.

Shutting down the service

Use Ctrl+C to shut down the service. When stopping your service, it will also shutdown the Kalix Runtime container and any other container you have defined in docker-compose.yml. Unless you have chosen to start docker-compose manually in a separate terminal, in which case you will need to stop it manually as well.

Running service with persistence enabled

By default, Kalix services are configured to run locally with persistence disabled. This means the Kalix Runtime will use an in-memory datastore for the state of your services. This is useful for local development since it allows you to quickly start and stop your service without having to worry about cleaning the database.

However, if you want to run your service with persistence enabled to keep the data when restarting, you may update the docker-compose file as below.

version: "3"
services:
  kalix-runtime:
    image: gcr.io/kalix-public/kalix-runtime:1.1.31
    # uncomment volumes when persistence is enabled
    volumes: (1)
      - ./target/kalix:/var/kalix
    ports:
      - "9000:9000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      PERSISTENCE_ENABLED: "true" (2)
      USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal}
      USER_SERVICE_PORT: ${USER_SERVICE_PORT:-8080}
1 Maps local directory to the Kalix Runtime database directory /var/kalix.
2 Sets PERSISTENCE_ENABLED environment variable to true.

Running multiple services locally

A typical Kalix application is composed of one or more services deployed to the same Kalix project. When deployed under the same Kalix project, two different services can make calls to each other or subscribe to each other’s event streams by simply using their logical names.

The same can be done on your local machine by configuring the services to run on different ports and by configuring them to "discover" each other using some extra configurations.

In this section, we will show you how to configure your local development environment to run two services and have them call each other.

For that we will use two of our existing samples: customer-registry and customer-registry-subscriber.

For that we will use two of our existing samples: customer-registry and customer-registry-subscriber.

The customer-registry sample provides a service to register customers and the customer-registry-subscriber subscribes to an event stream produced by the customer-registry service, building a View from it.

Customer Registry Sample

The docker-compose.yml file from customer-registry is left untouched and use the usual default ports. The user service will run on port 8080 and the Kalix Runtime on port 9000.

version: "3"
services:
  kalix-runtime:
    image: gcr.io/kalix-public/kalix-runtime:1.1.31
    container_name: java-protobuf-eventsourced-customer-registry
    ports:
      - "9000:9000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      JAVA_TOOL_OPTIONS: >
      # jvm -D properties can be added under this environment map (note: remove this comment when adding properties)

      USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal}
      USER_SERVICE_PORT: ${USER_SERVICE_PORT:-8080}

Customer Registry Subscriber Sample

On the other hand, in the customer-registry-subscriber we will use port 8081 and 9001 respectively to avoid port conflicts with the customer-registry service.

Moreover, since customer-registry-subscriber needs to subscribe to customer-registry and since we will be running it on our local machine, we need to show it where to find the customer-registry service.

This is done by passing an extra property (kalix.dev-mode.service-port-mappings.customer-registry) to its Kalix Runtime to let it create a mapping between the logical name customer-registry and the host and port where the customer-registry service is running.

Note that you need to add service port mappings to the Kalix Runtime configuration for the service that depends on the other service. Here, customer-registry-subscriber depends on customer-registry, therefore we add the service port mapping to the Kalix Runtime associated with the customer-registry-subscriber.

version: "3"
services:
  kalix-runtime:
    image: gcr.io/kalix-public/kalix-runtime:1.1.31
    container_name: java-protobuf-eventsourced-customer-registry-subscriber
    ports:
      - "9001:9001"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      HTTP_PORT: "9001"
      JAVA_TOOL_OPTIONS: >
        -Dkalix.dev-mode.service-port-mappings.customer-registry=host.docker.internal:9000
      USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal}
      USER_SERVICE_PORT: "8081"

With both services configured, we can start them independently by running mvn kalix:runAll sbt runAll in two separate terminals.

From a third terminal, we can create a customer on customer-registry service.

Linux or macOS
grpcurl \
 -d '{"customer_id": "vip", "email": "vip@example.com", "name": "Very Important", "address": {"street": "Road 1", "city": "The Capital"}}' \
 --plaintext localhost:9000 \
  customer.api.CustomerService/Create
Windows 10+
grpcurl ^
 -d '{"customer_id": "vip", "email": "vip@example.com", "name": "Very Important", "address": {"street": "Road 1", "city": "The Capital"}}' ^
 --plaintext localhost:9000 ^
  customer.api.CustomerService/Create

While watching the logs in customer-registry-subscriber service, we will see it receiving the customer created event. After that we can query its View.

grpcurl --plaintext localhost:9001 customer.view.AllCustomersView/GetCustomers

Running other services with Docker

In the previous example, we run two services independently with mvn kalix:runAll sbt runAll. Each time, the service is started together with a Kalix Runtime running in a Docker container.

An alternative is to build a larger docker-compose file containing dependent services. In the case of customer-registry-subscriber, we can have a second docker-compose file containing a Kalix Runtime for customer-registry-subscriber, a Kalix Runtime for customer-registry and the customer-registry itself.

At the root of the customer-registry-subscriber service, we can find an alternative docker-compose file called docker-compose-integration.yml.

version: "3"
services:
  kalix-runtime:
    image: gcr.io/kalix-public/kalix-runtime:1.1.31
    container_name: java-protobuf-eventsourced-customer-registry-subscriber
    depends_on:
      - kalix-runtime-customer-registry
    ports:
      - "9001:9001"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      HTTP_PORT: "9001"
      JAVA_TOOL_OPTIONS: >
        -Dkalix.dev-mode.service-port-mappings.customer-registry=host.docker.internal:9000
      USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal}
      USER_SERVICE_PORT: "8081"

  kalix-runtime-customer-registry:
    image: gcr.io/kalix-public/kalix-runtime:1.1.31
    container_name: java-protobuf-eventsourced-customer-registry
    ports:
      - "9000:9000"
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      JAVA_TOOL_OPTIONS: >
      # jvm -D properties can be added under this environment map (note: remove this comment when adding properties)

      USER_SERVICE_HOST: ${USER_SERVICE_HOST:-host.docker.internal}
      USER_SERVICE_PORT: "8080"

  customer-registry:
    image: kcr.us-east-1.kalix.io/acme/eventsourced-customer-registry:latest
    ports:
      - "8080:8080"
    environment:
      HOST: customer-registry

But first, we need to build an image for customer-registry. For local development, we don’t need to publish it to a remote container registry. It suffices to build it locally.

We can build the image by calling the following command at the root of customer-registry:

Java
mvn package docker:build
Scala
sbt docker:publishLocal

Next we can run customer-registry-subscriber and instead pass docker-compose-integration.yml to it.

Run the following command at the root of customer-registry-subscriber:

Java
mvn -Dkalix.dev-mode.docker-compose-file=docker-compose-integration.yml kalix:runAll
Scala
sbt -Dkalix.dev-mode.docker-compose-file=docker-compose-integration.yml runAll

This time, mvn kalix:runAll sbt runAll will start docker-compose-integration.yml instead. The customer-registry service and its companion Kalix Runtime will run alongside customer-registry-subscriber and its own Kalix Runtime.

Note that the configuration is exactly the same except that now we are running one single mvn kalix:runAll sbt runAll command and the docker-compose file we are using contains all the dependencies required by customer-registry-subscriber.

This approach can be extended to any service you might want to integrate with. It can be any other Kalix service that you plan to deploy to the same Kalix project or even external services. The only requirement is to have a Docker image for it.