A stateful stand-in for every external system your service calls, and the channels that call it.
3.1K

The world your service thinks is real.
Your service calls other systems: a ledger, a customer directory, a stock service, a partner's API. Antipode plays all of them, on one port, and remembers what happened. Take a payment and the balance goes down. Ask for the balance and you get the new number. Retry with the same idempotency key and the money still leaves only once.
It plays the other direction too. Webhooks, Kafka records and RabbitMQ, SQS or SNS messages go into your service over the real transport, at the press of a button. You build the whole world in your browser, or a coding agent writes it for you from your service's code.

docker run -p 8090:8090 --name antipode \
-v "$PWD/antipode:/app/config" \
--add-host host.docker.internal:host-gateway \
husseinakar/antipode
| Part | Why it is there |
|---|---|
-p 8090:8090 | Your browser and your service reach Antipode here. |
-v "$PWD/antipode:/app/config" | Your world is saved in antipode/antipode.json on your disk, so it outlives the container and can be committed. |
--add-host host.docker.internal:host-gateway | Lets Antipode reach your service on your machine. Docker Desktop has this built in; Linux needs the line. |
Then open:
| The designer, where you build the world | http://localhost:8090/__admin/designer |
| The console, where you use it | http://localhost:8090/ |
Start the demo service that stands in for yours, from the same image:
docker run --rm -p 8080:8080 --add-host host.docker.internal:host-gateway \
-e ANTIPODE_URL=http://host.docker.internal:8090 \
husseinakar/antipode node examples/demo-service/service.mjs
In the designer, click Template, then Presets. Under Reference world, press Load it and confirm.
Press Console →. Your service is in the middle: today that is the demo service. The channels that call it are on the left, and the systems it calls are on the right.
Hover the Order walk card and press ▶. One order goes in, and six calls go out to the systems on the right.
Hover Reservations and press its list button, what it holds. The hold that order placed is in the table.
Press the power button on Reservations and choose once and 503 Unavailable. Send another order: it stops at Reservations. All live in the top bar clears every break.
1. Run it from your service's repository, with the command above. The antipode/ folder there is
Antipode's config folder from now on.
2. Generate the world from your code. A skill for coding agents reads your Feign clients,
WebClient and RestTemplate calls, controllers, listeners and OpenAPI document, and writes
antipode/antipode.json. Install it into your service's repository:
mkdir -p .claude/skills/antipode-config
curl -s localhost:8090/__admin/skill \
-o .claude/skills/antipode-config/SKILL.md
Then run /antipode-config in Claude Code. The file is plain Markdown, so Cursor, Gemini CLI and any
agent that reads AGENTS.md can follow it too.
3. Check it in the designer. Because the folder is mounted, the new world is already loaded: refresh the designer. Open each system, use Try it on its endpoints, and Save. A config from somewhere else goes in through Template → Import. You can also build the world by hand: drag External service and Inbound channel onto the map.
4. Point your service at Antipode. Set the base URL of every system your service calls to
http://localhost:8090. Antipode tells them apart by path. In the designer, Your service → Base URL
is where channels deliver to your service: http://host.docker.internal:8080 by default.
5. Commit antipode/antipode.json, and add antipode/history/ to .gitignore. A colleague who
runs the same command gets the same world.
Hover a card to see its buttons.
| Path | What it holds | Mount it? |
|---|---|---|
/app/config | antipode.json, its earlier versions, uploaded files | Yes, to keep your world |
/app/data | Each system's live data | Only to keep data between containers |
/app/behaviors | Behaviour packs, JavaScript for answers a config can't give | Only if you have one |
On Linux, the container runs as user 1000, and a bind mount keeps your folder's owner. Make the
folder writable by that user, or add --user "$(id -u):$(id -g)".
services:
antipode:
image: husseinakar/antipode:latest
ports: ['8090:8090']
volumes:
- ./antipode:/app/config
extra_hosts: ['host.docker.internal:host-gateway']
Keep extra_hosts if you start your service from your IDE, outside Compose. Antipode's data stops
with the project, so a standalone container is better when your app restarts often. It can still join
the project's network: docker network connect your-project_default antipode.
Set each broker's address on its transport, under Transports in the designer.
| Address | Standalone container | In your Compose project |
|---|---|---|
| Antipode, in your service's base URLs | http://localhost:8090 | http://antipode:8090 |
| Your service, in Base URL | http://host.docker.internal:8080 ᵈ | http://your-app:8080 |
| Kafka, in the transport's brokers | host.docker.internal:9092 | kafka:9092 ᵈ |
| RabbitMQ, in the transport's url | http://host.docker.internal:15672 | http://rabbitmq:15672 ᵈ |
| SQS or SNS, in the transport's endpoint | http://host.docker.internal:4566 | http://localstack:4566 ᵈ |
ᵈ is the default.
rabbitmq:management image and a real user: RabbitMQ only lets guest in from localhost.AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to the container. A Protobuf or Avro message travels
as base64 in the body, with nothing in front of it.localhost:9092
works from inside the container..avsc or .proto into antipode/schemas/,
imports included, or add it under Schemas in the designer. Point your service's
schema.registry.url at http://localhost:8090/__registry.| Variable | What it does |
|---|---|
ANTIPODE_PORT | The port. Default 8090 |
ANTIPODE_MODE | mock answers calls, proxy sends them to the real systems |
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY | Credentials for SQS and SNS |
The full guide, with the designer, the console, proxy mode and behaviour packs: https://github.com/hussein-akar/antipode
Tags: latest follows the main branch. Each release is also published as its version, such as
1.9.0 and 1.9. Built for linux/amd64 and linux/arm64.
Licence: Apache License 2.0.
Content type
Image
Digest
sha256:f25e09f5d…
Size
58.9 MB
Last updated
11 days ago
docker pull husseinakar/antipode