Manage the services under test
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.
Declare a service
Section titled “Declare a service”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: 20sEach 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.
Keep services running between runs
Section titled “Keep services running between runs”axx up # start every enabled service (or: axx up api) and wait until readyaxx run # reuses the running services: no start, no stopaxx run --tags @smokeaxx env # which services are running, left over from a killed run, or not cleaned upaxx down # stop the services and run their cleanupThis 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.
When a run cannot start them
Section titled “When a run cannot start them”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.
When a run is stopped by force
Section titled “When a run is stopped by force”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).
When a cleanup fails
Section titled “When a cleanup fails”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 uptry 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 downruns the cleanup again, and sayscleaned up after an earlier runonce it succeeds;axx doctorlists what is left, and so doaxx envand the MCPenvtool’sstatus, with each servicerunning,left overfrom a run that was killed, ornot cleaned up.
Run a service yourself
Section titled “Run a service yourself”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:
axx run --attach api--no-start skips starting, stopping and cleaning up every service; use it when the whole system is already running somewhere.
Debug a service
Section titled “Debug a service”Give the service a debug command and a debugger to wait for:
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}axx run --debug # every service with a debug blockaxx run --debug=api # only apiaxx up --debug=apiWith 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.
Start only what a run needs
Section titled “Start only what a run needs”In a repository with several services, start only the ones the selected scenarios need:
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 startsA 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:
services: push-gateway: command: docker compose up push-gateway active: {uses: [mobile-android, mobile-ios]}