Getting Started
Install and deploy Propeller, the open-source WebAssembly orchestrator. Step-by-step setup for Go, Rust, Docker, and Wasmtime — from clone to first Wasm workload deployment.
This guide walks you through installing Propeller and running your first WASM workload in under 15 minutes.
Prerequisites
| Tool | Version | Purpose |
|---|---|---|
| Go | 1.26.0+ | Building Propeller binaries |
| Rust | Latest | Building the proplet runtime |
| Docker | 20.10+ | Running base services (Atom, FluxMQ) |
| Make | 3.81+ | Build automation |
| Mosquitto Tools | Latest | MQTT CLI utilities |
These are only needed if you plan to build WASM modules locally:
Note:
rustupwill install the Rust toolchain andcargo. You will also use it to add WebAssembly targets.
Download Pre-built Binaries
You can download pre-built binaries from the GitHub Releases page.
Docker Images
Propeller also provides pre-built Docker images via GitHub Container Registry:
| Service | Image |
|---|---|
| Manager | ghcr.io/absmach/propeller/manager:latest |
| CLI | ghcr.io/absmach/propeller/cli:latest |
| Proxy | ghcr.io/absmach/propeller/proxy:latest |
| Proplet | ghcr.io/absmach/propeller/proplet:latest |
| Proplet (WASI-NN) | ghcr.io/absmach/propeller/proplet:wasi-nn |
To pull the latest images:
docker pull ghcr.io/absmach/propeller/manager:latest
docker pull ghcr.io/absmach/propeller/cli:latest
docker pull ghcr.io/absmach/propeller/proxy:latest
docker pull ghcr.io/absmach/propeller/proplet:latestFor WASI-NN (OpenVINO) support:
docker pull ghcr.io/absmach/propeller/proplet:wasi-nnNote: The WASI-NN image is only available for
linux/amd64architecture due to OpenVINO limitations.
Multi-Architecture Support
Docker images are built for multiple architectures:
| Architecture | manager | cli | proxy | proplet | proplet-wasi-nn |
|---|---|---|---|---|---|
| amd64 | ✓ | ✓ | ✓ | ✓ | ✓ |
| arm64 | ✓ | ✓ | ✓ | ✓ | ✗ |
| riscv64 | ✓ | ✓ | ✓ | ✗ | ✗ |
Build from Source
Step 1: Clone and Build
git clone https://github.com/absmach/propeller.git
cd propeller
make all -j $(nproc)
make installWhat the build process does
During the build, you will see output similar to:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-s -w -X 'github.com/absmach/propeller.BuildTime=...' -X 'github.com/absmach/propeller.Version=v0.3.0'" -o build/manager cmd/manager/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "..." -o build/cli cmd/cli/main.go
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "..." -o build/proxy cmd/proxy/main.go
cd proplet && cargo build --release && cp target/release/proplet ../build
Finished `release` profile [optimized] target(s) in 15.36s
GOOS=js GOARCH=wasm tinygo build -buildmode=c-shared -o build/addition.wasm -target wasi examples/addition/addition.go
GOOS=js GOARCH=wasm tinygo build -buildmode=c-shared -o build/compute.wasm -target wasi examples/compute/compute.go
GOOS=js GOARCH=wasm tinygo build -buildmode=c-shared -o build/hello-world.wasm -target wasi examples/hello-world/hello-world.goThis means:
- All Go binaries were built and placed into
build/ - All example WASM modules were built using TinyGo into
build/
make install copies the compiled binaries into your $GOBIN directory so you can run them directly from your terminal.
If make install fails
You likely don't have your Go binary path ($GOBIN) configured. Set it up like this:
export GOBIN=$HOME/go/bin
export PATH=$PATH:$GOBIN
make installSee the Developer Guide for other common build problems.
Step 2: Start Base Services
Atom and FluxMQ provide identity, authorization, and messaging for Propeller. From the root of the propeller repo:
cd propeller
make start-baseThis runs:
docker compose -f docker/compose.yaml --env-file docker/.env up -dThe output will look something like:
docker compose -f docker/compose.yaml --env-file docker/.env up -d
[+] up 18/18
✔ Network propeller-base-net Created
✔ Volume propeller_atom-db-volume Created
✔ Container propeller-fluxmq Started
✔ Container propeller-atom-migrate Started
✔ Container propeller-atom Started
✔ Container propeller-nginx Started
... 13 moreOnly base services (Atom, FluxMQ, Nginx, and their dependencies) start at this point. Wait ~30–60 seconds for internal services to stabilise before proceeding. Verify with:
docker ps | grep propellerLogs from docker logs propeller-atom look like this once Atom is ready:
{"time":"...","level":"INFO","msg":"Atom service started","port":8080}Step 3: Provision Propeller
Now we create everything Propeller needs inside Atom and generate the config file:
propeller-cli provisionThis command will:
- Log you into Atom (you must have an Atom user already created; if not, create one via Atom's API or signup flow)
- Create a tenant (or find an existing one with the same name)
- Create a manager entity (kind: service, profile: Service Account) with an API key
- Create proplet entities with API keys
- Create a proxy entity with an API key
- Create a channel (resource) for MQTT communication
- Connect all entities to the channel via permission blocks and direct policies (publish & subscribe)
- Write all IDs and keys into a
config.tomlfile in the current directory
The process will look something like this:
On success:
Successfully created config.toml fileYour config.toml will look like:
# Propeller Configuration
# Each identity is an Atom entity of kind "service", profile "Service Account".
[manager]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "f2fe9a33-144a-4346-a5d6-38e2eb07815e"
api_key = "ef7da52b-c01f-4b62-9502-6723d639405b"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"
[proplet]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "fa407362-9c5f-41b8-9a09-9d0c0b039287"
api_key = "991c4d03-2f2c-4ba5-97a6-45bead85457e"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"
[proxy]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "fa407362-9c5f-41b8-9a09-9d0c0b039287"
api_key = "991c4d03-2f2c-4ba5-97a6-45bead85457e"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"Step 4: Configure and Start Propeller
Copy the configuration to the Docker directory (the Propeller compose file mounts config.toml from there):
cp config.toml docker/config.tomlThe volume mounts for config.toml are already configured in docker/compose.propeller.yaml. Copy the config and start Propeller:
make start-propellerThis runs:
docker compose -f docker/compose.propeller.yaml --env-file docker/.env up -dThe three Propeller containers start, read config.toml from their mount paths, and connect to Atom and FluxMQ over the shared propeller-base-net network.
Step 5: Verify Deployment
Check each service is running:
# Manager logs
docker logs propeller-manager | tail -5Expected:
{"time":"...","level":"INFO","msg":"MQTT connection established"}
{"time":"...","level":"WARN","msg":"COORDINATOR_URL not configured - FL features will not be available"}
{"time":"...","level":"INFO","msg":"Subscribe to MQTT topic completed successfully","duration":"64ms"}
{"time":"...","level":"INFO","msg":"cron scheduler started","check_interval":60000000000}
{"time":"...","level":"INFO","msg":"manager service http server listening at manager:7070 without TLS"}# Proplet logs
docker logs propeller-proplet | tail -5Expected:
INFO Starting Proplet (Rust) - Instance ID: ...
INFO MQTT client created (TLS: false)
INFO Published discovery message
INFO Subscribed to topic: .../control/manager/start
INFO Subscribed to topic: .../control/manager/stop# Proxy logs
docker logs propeller-proxy | tail -3Expected:
{"level":"INFO","msg":"MQTT connection established"}
{"level":"INFO","msg":"starting proxy service"}
{"level":"INFO","msg":"successfully subscribed to topic"}Health check
Verify the manager API is up:
curl http://localhost:7070/healthVerify the proplet has registered:
curl http://localhost:7070/propletsDeploy Your First Workload
List Available Proplets
curl http://localhost:7070/proplets | jq{
"offset": 0,
"limit": 100,
"total": 1,
"proplets": [
{
"id": "a95517f9-5655-4cf5-a7c8-aa00290b3895",
"name": "Dankert-Gene",
"task_count": 0,
"alive": true,
"last_alive_at": "2026-02-13T09:36:53.478319036Z",
"metadata": {
"description": "Proplet running in Docker",
"tags": ["docker", "prod"],
"location": "rack-1",
"ip": "172.18.0.5",
"environment": "docker",
"os": "linux",
"hostname": "propeller-proplet",
"cpu_arch": "x86_64",
"total_memory_bytes": 8589934592,
"proplet_version": "0.1.0",
"wasm_runtime": "wasmtime-internal"
}
}
]
}Create a Task
curl -X POST "http://localhost:7070/tasks" \
-H "Content-Type: application/json" \
-d '{"name": "add", "inputs": [10, 20]}'{
"id": "2226e867-1f9f-4ff2-b8e7-f0ea46830664",
"name": "add",
"kind": "standard",
"state": 0,
"cli_args": null,
"inputs": [10, 20],
"daemon": false,
"encrypted": false,
"start_time": "0001-01-01T00:00:00Z",
"finish_time": "0001-01-01T00:00:00Z",
"created_at": "2026-02-13T09:38:50.765084043Z",
"updated_at": "0001-01-01T00:00:00Z",
"next_run": "0001-01-01T00:00:00Z",
"priority": 50
}You can also use the CLI:
# propeller-cli tasks create <name>
propeller-cli tasks create demoGet a Task
curl -X GET "http://localhost:7070/tasks/2226e867-1f9f-4ff2-b8e7-f0ea46830664"CLI equivalent:
# propeller-cli tasks view <id>
propeller-cli tasks view 2226e867-1f9f-4ff2-b8e7-f0ea46830664Upload a WASM Module
There are two ways to upload a WASM file:
Option A — Upload via file:
curl -X PUT "http://localhost:7070/tasks/97946bd0-26b1-4d14-aca5-1c2b4f1d0447/upload" \
-F 'file=@build/addition.wasm'Option B — Upload via base64-encoded WASM in the task body:
curl --location --request PUT 'http://localhost:7070/tasks/2226e867-1f9f-4ff2-b8e7-f0ea46830664' \
--header 'Content-Type: application/json' \
--data '{
"file": "AGFzbQEAAAABBwFgAn9/AX8DAgEABwgBBG1haW4AAAoJAQcAIAAgAWoL"
}'CLI equivalent:
propeller-cli tasks update 2226e867-1f9f-4ff2-b8e7-f0ea46830664 '{"file": "AGFzbQEAAAABBwFgAn9/AX8DAgEABwgBBG1haW4AAAoJAQcAIAAgAWoL"}'Start the Task
curl -X POST "http://localhost:7070/tasks/97946bd0-26b1-4d14-aca5-1c2b4f1d0447/start"{ "started": true }CLI equivalent:
# propeller-cli tasks start <id>
propeller-cli tasks start 97946bd0-26b1-4d14-aca5-1c2b4f1d0447Check Results
curl "http://localhost:7070/tasks/<task_id>" | jqThe task state will change from 0 (pending) to 3 (completed) with the result in the results field.
Stop a Task
curl -X POST "http://localhost:7070/tasks/<task_id>/stop"Creating Tasks from OCI Registry Images
For WASM modules stored in an OCI registry, specify the image URL during task creation. The proxy automatically retrieves the WASM file from the registry when the task starts, eliminating manual uploads:
curl -X POST "http://localhost:7070/tasks" \
-H "Content-Type: application/json" \
-d '{"name": "add", "inputs": [10, 20], "image_url": "docker.io/mrstevenyaga/add.wasm"}'Alternative: Run Without Docker
You can run Propeller binaries directly on your host (useful for development or debugging). Atom and FluxMQ still run in Docker via make start-base, but the Propeller processes run locally.
The provisioning step is the same: run propeller-cli provision to generate config.toml, then ensure it is in the directory where you start the binaries.
Stop the Docker-based Propeller services first if they are running:
docker stop propeller-manager propeller-proxy propeller-propletThen start each service in a separate terminal:
# Terminal 1
propeller-manager
# Terminal 2
propeller-proplet
# Terminal 3 — set registry env vars before starting proxy
export PROXY_REGISTRY_URL="docker.io"
export PROXY_AUTHENTICATE="TRUE"
export PROXY_REGISTRY_USERNAME="" # set if your registry requires auth
export PROXY_REGISTRY_PASSWORD="" # set if your registry requires auth
propeller-proxyDocker is recommended for production; local binaries are easier for debugging.
Adding More Proplets to an Existing Deployment
Once your initial deployment is running, you can add more proplets without re-provisioning from scratch. The add-proplets subcommand reads your existing config.toml, creates new Atom service entities with API keys, connects them (via permission blocks and direct policies) to the same channel, and appends their credentials to the file.
propeller-cli provision add-propletsThe command asks for your Atom username and password, then for the number of new proplets to create. For example, if you already have one proplet ([proplet]) and add two more, the file becomes:
[manager]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "f2fe9a33-144a-4346-a5d6-38e2eb07815e"
api_key = "ef7da52b-c01f-4b62-9502-6723d639405b"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"
[proplet]
...
[proplet2]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "b3c12d45-89ef-4f10-a123-7c5d8e9f0a12"
api_key = "22ab3ef0-9c1d-48f2-b6a5-cd5f1234567e"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"
[proplet3]
tenant_id = "182c0907-002c-4bfd-8bf3-e4f40c58dde6"
entity_id = "d7e89f01-2345-6789-abcd-ef0123456789"
api_key = "33cd4fg1-0d2e-59g3-c7b6-de6g2345678f"
channel_id = "8c6e1e6c-fc89-43b4-b00b-884a690c7419"
[proxy]
...Note: New sections are always numbered from where the existing count left off.
Each new proplet process needs to know which section of config.toml to read. Set the PROPLET_CONFIG_SECTION environment variable before starting it:
PROPLET_CONFIG_SECTION=proplet2 propeller-proplet
PROPLET_CONFIG_SECTION=proplet3 propeller-propletOr in docker/compose.propeller.yaml, add an environment entry per proplet service:
environment:
PROPLET_CONFIG_SECTION: proplet2After copying the updated config.toml into docker/, restart only the Propeller services:
make stop-propeller
make start-propellerPostman Collection
A Postman collection of all API calls is available for interacting with the Propeller system.
What's Next?
| Goal | Documentation |
|---|---|
| Build from source | Developer Guide |
| Understand the architecture | Architecture |
| See working examples | Examples |
| Run on embedded devices | Embedded Proplet |
| Use the REST API | API Reference |
| Monitor deployments | Monitoring |
| Enable secure enclaves | TEE |
Troubleshooting
make install fails with permission denied
Your $GOBIN path isn't configured:
export GOBIN=$HOME/go/bin
export PATH=$PATH:$GOBIN
make installpropeller-cli provision fails
Ensure Atom is fully started:
docker logs propeller-atom | tail -10Look for: Atom service started
Services can't find config.toml
The config must be mounted into Docker containers. Verify:
ls docker/config.tomlIf missing, copy it: cp config.toml docker/config.toml
WASM execution fails
Check proplet logs for runtime errors:
docker logs propeller-proplet | grep -i errorFor detailed troubleshooting, see the Developer Guide.
Stopping Propeller
make stop-propeller
make stop-baseThis stops all Propeller containers first, then all base (Atom, FluxMQ, etc.) containers, while preserving data volumes.