Skip to content
axxbetadocs
v0.2.26next
GitHub

Manage the services under test

View .mdOpen in Claude

services: in axx.yaml describes the system under test: your service and anything it needs, such as databases, brokers and mocks. axx run starts them, waits until they are ready, runs the scenarios, stops them and runs their cleanup.

A service is what runs on its own and waits for whoever connects to it, by an address. A program its user starts, uses and ends is not a service, even when it is what you test: a command-line tool, or an MCP server its clients run over stdio. The step that uses it runs it, as its user would: the cli pack’s and the mcp pack’s command.

axx.yaml
services:
infra:
dir: ./infra # relative to axx.yaml
command: docker compose up --wait postgres kafka
ready:
tcp: localhost:5432
cleanup: docker compose down -v --remove-orphans
api:
dependsOn: [infra]
command: ./gradlew bootRun # or npm start, go run ./cmd/api, ...
env:
SPRING_PROFILES_ACTIVE: acceptance
ready:
http:
url: http://localhost:8080/actuator/health
timeout: 120s
interval: 1s
stop:
signal: SIGTERM
grace: 20s

Each key is described in the configuration reference. A command that exits 0 before the service is ready is fine, which is what docker compose up -d or --wait does: Axx keeps polling the ready checks.

If you also run a compose file yourself while you develop, give the stack Axx starts from it a name of its own, in command and cleanup alike: docker compose -p parcels-axx up. Compose otherwise names both stacks after the folder, so Axx’s takes over yours, and down -v deletes your stack’s volumes, your local data with them. axx init names the stack it writes so.

Service output goes to .axx/logs/services.log. When a service fails to start, the error shows the last lines of its output and a stable code from AXX-E0400 to AXX-E0415.

Terminal window
axx up # start every enabled service (or: axx up api) and wait until ready
axx run # reuses the running services: no start, no stop
axx run --tags @smoke
axx env # which services are running, left over from a killed run, or not cleaned up
axx down # stop the services and run their cleanup

This is the fastest local loop, and the one agents should use. The services keep their data from run to run, and axx run warns so when it reuses them: a scenario that creates something (registers a parcel by its reference, say) finds what an earlier run created and fails where a fresh start would pass. axx down, then axx up, starts them afresh; check a change on fresh services before you trust its result.

An agent whose sandbox keeps it from Docker cannot start services that run with docker compose. axx host, started in the project on your machine, starts them for its runs: each run asks the host for its services, the host starts them as axx.yaml says and cleans up after the run, and it starts only a definition you approved. Run tests from an agent’s sandbox shows how.

A run that is killed before it can stop its services (stopped by force from an editor, say, or crashed) leaves them in .axx/run/state.json. The next run cleans up first, as axx down does: it stops what is left, runs the cleanups, says so, and starts from a clean slate. What belongs to a run still going, or to axx up, is left alone: a run then starts no service and says what is left (AXX-E0415).

A cleanup can fail: docker compose down without access to Docker, say. The run reports it (AXX-E0411), and .axx/run/state.json keeps the cleanup, so what it should have removed is not forgotten. Until it succeeds:

  • runs and axx up try it again first, and start no service while it fails, saying what is left (AXX-E0415): containers, volumes and requests recorded by a mock would be where the next run starts from;
  • axx down runs the cleanup again, and says cleaned up after an earlier run once it succeeds;
  • axx doctor lists what is left, and so do axx env and the MCP env tool’s status, with each service running, left over from a run that was killed, or not cleaned up.

To run the service from your IDE (with breakpoints, hot reload, a profiler), tell Axx not to start it. Axx still starts everything else and waits for its readiness checks:

Terminal window
axx run --attach api

--no-start skips starting, stopping and cleaning up every service; use it when the whole system is already running somewhere.

Give the service a debug command and a debugger to wait for:

axx.yaml
services:
api:
command: ./gradlew bootRun
debug:
command: ./gradlew bootRun -PappJvmArgs=-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=n
debugger:
type: java # java, go, nodejs or python
port: 5005
mode: ide-listens # the IDE listens, the service connects (default for java)
onUnavailable: retry # retry (default), fail, or fallback to the normal command
retry: {attempts: 10, delay: 3s}
Terminal window
axx run --debug # every service with a debug block
axx run --debug=api # only api
axx up --debug=api

With mode: ide-listens, start a listening debugger in your IDE first (in IntelliJ IDEA: a Remote JVM Debug configuration in Listen to remote JVM mode on port 5005), then run Axx. With mode: app-listens (delve, --inspect, debugpy), the service opens the port and you attach; the parcels example runs its Go service under Delve this way. If no debugger is listening, onUnavailable decides whether to wait, fail with AXX-E0409, or run without debugging.

In a repository with several services, start only the ones the selected scenarios need:

axx.yaml
active:
enabled: true
onNoTags: fallback # no selected scenario has a tag: start every enabled service (or: error)
services:
orders:
command: ./gradlew :orders:bootRun
active: {tags: ["@orders"]}
billing:
command: ./gradlew :billing:bootRun
active: {tags: ["@billing", "@payments"]}
wiremock:
command: docker compose up wiremock # no active.tags: always starts

A service with active.tags starts when any selected scenario carries one of its tags, and the services in its dependsOn start with it. Services without active.tags always start. axx run --tags @billing starts billing and wiremock, not orders.

A service can also wait for what the scenarios use, with active.uses: it starts when a selected scenario uses a step of one of its packs, no tag needed. A service only the mobile apps call, say:

axx.yaml
services:
push-gateway:
command: docker compose up push-gateway
active: {uses: [mobile-android, mobile-ios]}