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.