propeller logo

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

ToolVersionPurpose
Go1.26.0+Building Propeller binaries
RustLatestBuilding the proplet runtime
Docker20.10+Running base services (Atom, FluxMQ)
Make3.81+Build automation
Mosquitto ToolsLatestMQTT CLI utilities

These are only needed if you plan to build WASM modules locally:

Note: rustup will install the Rust toolchain and cargo. 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:

ServiceImage
Managerghcr.io/absmach/propeller/manager:latest
CLIghcr.io/absmach/propeller/cli:latest
Proxyghcr.io/absmach/propeller/proxy:latest
Propletghcr.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:latest

For WASI-NN (OpenVINO) support:

docker pull ghcr.io/absmach/propeller/proplet:wasi-nn

Note: The WASI-NN image is only available for linux/amd64 architecture due to OpenVINO limitations.

Multi-Architecture Support

Docker images are built for multiple architectures:

Architecturemanagercliproxypropletproplet-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 install

What 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.go

This 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 install

See 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-base

This runs:

docker compose -f docker/compose.yaml --env-file docker/.env up -d

The 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 more

Only 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 propeller

Logs 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 provision

This 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.toml file in the current directory

The process will look something like this:

asciicast

On success:

Successfully created config.toml file

Your 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.toml

The volume mounts for config.toml are already configured in docker/compose.propeller.yaml. Copy the config and start Propeller:

make start-propeller

This runs:

docker compose -f docker/compose.propeller.yaml --env-file docker/.env up -d

The 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 -5

Expected:

{"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 -5

Expected:

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 -3

Expected:

{"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/health

Verify the proplet has registered:

curl http://localhost:7070/proplets

Deploy 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 demo

Get 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-f0ea46830664

Upload 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-1c2b4f1d0447

Check Results

curl "http://localhost:7070/tasks/<task_id>" | jq

The 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-proplet

Then 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-proxy

Docker 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-proplets

The 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-proplet

Or in docker/compose.propeller.yaml, add an environment entry per proplet service:

environment:
  PROPLET_CONFIG_SECTION: proplet2

After copying the updated config.toml into docker/, restart only the Propeller services:

make stop-propeller
make start-propeller

Postman Collection

A Postman collection of all API calls is available for interacting with the Propeller system.

What's Next?

GoalDocumentation
Build from sourceDeveloper Guide
Understand the architectureArchitecture
See working examplesExamples
Run on embedded devicesEmbedded Proplet
Use the REST APIAPI Reference
Monitor deploymentsMonitoring
Enable secure enclavesTEE

Troubleshooting

make install fails with permission denied

Your $GOBIN path isn't configured:

export GOBIN=$HOME/go/bin
export PATH=$PATH:$GOBIN
make install

propeller-cli provision fails

Ensure Atom is fully started:

docker logs propeller-atom | tail -10

Look for: Atom service started

Services can't find config.toml

The config must be mounted into Docker containers. Verify:

ls docker/config.toml

If missing, copy it: cp config.toml docker/config.toml

WASM execution fails

Check proplet logs for runtime errors:

docker logs propeller-proplet | grep -i error

For detailed troubleshooting, see the Developer Guide.

Stopping Propeller

make stop-propeller
make stop-base

This stops all Propeller containers first, then all base (Atom, FluxMQ, etc.) containers, while preserving data volumes.

On this page