Skip to content

Run a service locally

A Scala service runs on a laptop with sbt run and a Postgres started by Docker Compose. It needs no other configuration: the runtime's defaults point at a database named ankka on localhost:5432 and serve HTTP on port 9000. A Python service runs the same way, with ankka's runtime started beside it as a container called the sidecar.

The service that runs locally is a real single-node cluster. It takes the same code path a multi-node deployment takes, so what works on a laptop works deployed.

Start the database

A service created from the template carries a docker-compose.yml for Postgres and an sbt task that extracts ankka's database schema from the runtime library:

sbt schema                # writes the runtime's schema files to target/ddl
docker compose up -d      # Postgres 17 on localhost:5432, initialised from target/ddl

The schema is the journal, snapshot, durable state, projection offset and timer tables the runtime reads and writes. It comes from the ankka-runtime artifact your build resolves, so it always matches the runtime version the service runs.

Postgres applies its initialisation directory only to an empty data volume. After sbt schema changes anything, for example after upgrading ankka, recreate the volume:

docker compose down -v && docker compose up -d

down -v deletes the local database's data. That is the intended cost of a schema change on a laptop.

Run the service

sbt run                   # HTTP on http://localhost:9000

The service logs every route it serves as it starts, then answers:

curl -XPOST localhost:9000/items/i1 -H 'content-type: application/json' -d '{"name":"Widget","count":2}'
curl localhost:9000/items/i1

Stop it with ctrl-c and start it again: the item is still there, because an entity rebuilds its state from the journal in Postgres, not from memory.

Configure the database and HTTP port

Every setting a service needs on a laptop has a default and an environment variable that overrides it:

Variable Default Meaning
ANKKA_DB_HOST localhost Postgres host
ANKKA_DB_PORT 5432 Postgres port
ANKKA_DB_NAME ankka database name
ANKKA_DB_USER ankka database user
ANKKA_DB_PASSWORD ankka database password
ANKKA_HTTP_PORT 9000 the port the HTTP server binds
ANKKA_HTTP_INTERFACE 0.0.0.0 the interface the HTTP server binds

For example, a second service on the same machine, with its own database and port:

ANKKA_DB_NAME=orders ANKKA_HTTP_PORT=9100 sbt run

Never point two services at the same database. Timers, view tables and projection offsets are keyed by component id alone, so two services sharing a database overwrite and delete each other's rows. Create a database per service, locally as the platform does when deployed. See Databases for the reasons.

A project of several services does this for each of them: scripts/run.sh <service> makes a database named for the service, applies the schema to it, and starts the service on the port its service.json names. See Build a project of several services.

The complete list of variables and settings is in Runtime configuration. A service can also override any setting in its own src/main/resources/application.conf.

Run two nodes as one cluster

Two copies of a service on one machine can form one cluster, which is the quickest way to see sharding and hand-off without Kubernetes. The first node needs a fixed cluster port so the second can name it:

# terminal 1
ANKKA_CLUSTER_PORT=17355 sbt run

# terminal 2
ANKKA_CLUSTER_SEED_NODES=pekko://ankka@127.0.0.1:17355 ANKKA_HTTP_PORT=9001 sbt run

An item written through :9000 reads back through :9001, because both nodes share one set of entities, sharded across them, and one journal.

Variable Default Meaning
ANKKA_CLUSTER_PORT random the port this node's cluster remoting binds
ANKKA_CLUSTER_SEED_NODES empty comma-separated addresses of existing nodes to join, each pekko://ankka@host:port

With neither set, a node picks a random remoting port and joins itself. That is why several services and test suites can run on one laptop without knowing about each other, and why a plain sbt run needs no cluster configuration at all. See Clusters and instances for how the same service forms its cluster when deployed.

Run several services that call each other

Each service run on a laptop announces itself under the name it states (ankka.service.name, or ANKKA_SERVICE_NAME) in a file under ~/.ankka/running/. A call to another service by name reaches the address that service announced, so services started in any order find each other with nothing configured, whether or not ankka local console is running; the console lists the same files. Where those files cannot be read, as from a sidecar in a container, ankka.local-services.<name> names the address instead, as calling other services shows.

Run a Python service

A Python service is a process, and ankka's runtime runs beside it as the sidecar. Locally the sidecar is a container: the ankka repository's docker-compose.yml has it under the polyglot profile, with a Postgres for it to use.

docker compose --profile polyglot up -d      # Postgres and the sidecar; HTTP on localhost:9000
uv run python main.py                        # your process, listening on 9010; the sidecar finds it
curl localhost:9000/carts/c1

The repository's compose file runs ankka-sidecar:latest, the sidecar of that checkout, which sbt sidecar/Docker/publishLocal builds. A project made with ankka init --language python (or typescript) carries its own docker-compose.yml instead, which pulls the published sidecar, ghcr.io/thinkmorestupidless/ankka-sidecar, at the project's ankka version.

The sidecar reaches your process at host.docker.internal:9010. Docker Desktop provides that name; on Linux the compose file maps it with host-gateway, which is also what any compose file of your own needs. Your process never binds an HTTP port and never connects to the database: the sidecar serves the routes your process declared, owns the journal, and forwards each request to your process over gRPC.

Stop both and start both, and the data is still there, in the sidecar's journal in Postgres. See Services in other languages for how the two halves divide the work.

Run a Rust service

A Rust service is built to a WebAssembly module, and ankka's runtime loads the module into its own process: there is one container and no process of yours to start. The ankka repository's docker-compose.yml has the runtime under the wasm profile, with the module bind-mounted read-only:

cargo build --release --target wasm32-unknown-unknown   # the module, under target/wasm32-unknown-unknown/release
docker compose --profile wasm up -d                    # Postgres and the runtime; HTTP on localhost:9000
curl localhost:9000/carts/c1

The compose file mounts the repository's example cart by default; ANKKA_WASM_MODULE_PATH points it at another build. Rebuild the module and docker compose --profile wasm restart runtime to load the new one. The runtime owns the journal and serves the routes the module declared, exactly as it does for a process. See Services in other languages.

Run a web-hosted service

ankka local web runs a web-hosted service's process on this machine behind the same proxy a cluster runs: its mounts answer at their paths, and it calls services by name at the calling address. Services are found as the local console finds them, or named with --service:

ankka local web --service cart=http://127.0.0.1:9000 -- npm run dev

With a command after --, the proxy runs it on a free port with PORT and ANKKA_SERVICES_URL set, and ends with its exit code. Without one, it prints both and waits for the process at the descriptor's processPort. See Deploy a user interface.

Give agents a model key

A service with agents needs a model provider. Anthropic's and OpenAI's keys come from the environment:

export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
sbt run

In a Python service the agent loop runs in the sidecar, so a key is set on the sidecar's container, not on your process. ANKKA_MODEL_NAME on the sidecar chooses the Claude model, and with both keys ANKKA_MODEL_DEFAULT says which answers an agent that names none. A model running on your own machine needs no key: see Run a model on your own machine. Tests need no key either: see Testing.

See what it is doing

The local console lists every ankka service running on the machine and shows each one's components, routes, traces and agent sessions:

ankka local console       # http://localhost:9889

Start it before or after the service; the order does not matter. See The local console.