Developer's Guide
Contribute to Propeller by Abstract Machines. Covers forking the Go/Rust codebase, building all components, running tests, and submitting pull requests to the open-source repo.
Getting Propeller
Propeller source can be found in the official Propeller GitHub repository. You should fork this repository in order to make changes to the project. The forked version of the repository should be cloned using the following:
git clone https://github.com/your-github-username/propeller.git $SOMEPATH/propeller
cd $SOMEPATH/propeller
git remote add upstream https://github.com/absmach/propeller.gitBuilding Propeller
Prerequisites
To build Propeller, you will need the following:
- A Go compiler (Go 1.26.0)
- Rust (1.97.1) (required for
make propletandmake all) - Make
- Docker (29.6.2) (required for
make start-baseand Docker image builds) - Wasmtime (47.0.2) (required to test compiled Wasm examples locally)
- TinyGo (0.41.1) (required for Wasm example builds)
- Mockery (required for
make testandmake test-all)
Downloading Pre-built Binaries
Instead of building from source, you can download pre-built binaries from the GitHub Releases page.
Docker Images
Propeller provides pre-built Docker images via GitHub Container Registry:
# Pull all services
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 support (amd64 only)
docker pull ghcr.io/absmach/propeller/proplet:wasi-nnGitHub Releases
Binaries for all services are available in the GitHub Releases:
- Go services (manager, cli, proxy): Available for linux/amd64, linux/arm64, linux/riscv64
- Proplet (Rust): Available for linux/amd64, linux/arm64, linux/riscv64
Download the appropriate binary for your platform from the releases page.
Building
Use the GNU Make tool to build all Propeller services:
make allThis will build all Go services (manager, cli, proxy), Rust proplet, and WASM examples for your platform.
Note: This requires both Go and Rust toolchains to be installed.
To build only the Go services without Rust and WASM dependencies:
make manager cli proxyThis is useful for testing the core services without a full development environment setup.
To build Propeller for other platforms, use the following:
| OS | Architecture | Command |
|---|---|---|
| Linux | amd64 | GOOS=linux GOARCH=amd64 make all |
| Linux | arm64 | GOOS=linux GOARCH=arm64 CGO_ENABLED=0 make all |
| Linux | riscv64 | GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 make all |
make allalso compiles the Rust proplet, which always targets your host architecture. When cross-compiling (GOARCH != host), the Go services are built for the target platform, but the proplet is still built for the host. Build the proplet for a non-native target separately — see Building Proplet (Rust) for ARM.
Cross-compiling
The manager links cgo packages — mattn/go-sqlite3 for SQLite storage and wasmtime-go for the Wasm plugin — so it requires a C compiler. cli and proxy are pure Go and cross-compile without any special toolchain.
You have two options for cross-compiling the manager:
- Static cgo-free build (recommended) — set
CGO_ENABLED=0on themakecommand line. This overrides themanagertarget's default ofCGO_ENABLED=1(see the Makefile) and produces a statically linked binary with no external C toolchain. SQLite storage and the Wasm plugin are compiled out;memoryandbadgerstorage still work. - Keep cgo enabled — install a cross C compiler and point
CC/CXXat it. This keeps SQLite storage and the Wasm plugin, at the cost of installing the cross toolchain.
# Option 1: static build, no cross C toolchain required
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 make all
# Option 2: keep cgo (SQLite + Wasm plugin) using a cross compiler
GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc CXX=aarch64-linux-gnu-g++ make managerInstalling Cross-compilation Toolchains
The cross C toolchains below are required for the Rust proplet cross-build and for option 2 (a cgo-enabled manager). They are not needed for CGO_ENABLED=0 Go builds.
On Debian/Ubuntu, install the required toolchains:
# For ARM64 (aarch64)
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
# For RISC-V 64-bit
sudo apt-get install -y gcc-riscv64-linux-gnu libc6-dev-riscv64-cross
# For ARMv7 (32-bit)
sudo apt-get install -y gcc-arm-linux-gnueabihf libc6-dev-armhf-crossOn macOS with Homebrew:
# For ARM64
brew install aarch64-unknown-linux-gnu
# For RISC-V
brew install riscv64-unknown-linux-gnuBuilding for ARM on Linux
Cross-compile the Go services for Linux ARM targets:
# Build for ARM64 (static, cgo-free)
GOOS=linux GOARCH=arm64 CGO_ENABLED=0 make all
# Build for RISC-V 64-bit
GOOS=linux GOARCH=riscv64 CGO_ENABLED=0 make all
# Build for ARMv7 (32-bit ARM)
GOOS=linux GOARCH=arm CGO_ENABLED=0 make allThe
managertarget defaults toCGO_ENABLED=1(see the Makefile), but aCGO_ENABLED=0value on themakecommand line overrides it. Without cgo, SQLite storage and the Wasm plugin are unavailable — run the manager withMANAGER_STORAGE_TYPE=memoryorbadger.
Building Proplet (Rust) for ARM
The Rust proplet is not cross-compiled by make all; it is always built for your host architecture. To build it for another architecture:
# Install the target
rustup target add aarch64-unknown-linux-gnu
# Set the linker for cross-compilation
export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
# Build
cd proplet
cargo build --release --target aarch64-unknown-linux-gnuFor RISC-V:
rustup target add riscv64gc-unknown-linux-gnu
export CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gcc
cd proplet
cargo build --release --target riscv64gc-unknown-linux-gnuBuilding for ARM on macOS
The Go services (including the cgo-enabled manager) build natively on Apple Silicon without extra tooling:
# ARM64 (native)
GOOS=darwin GOARCH=arm64 make manager cli proxy
# x86_64 (Intel macs)
GOOS=darwin GOARCH=amd64 make manager cli proxyThe Rust proplet does not build on macOS or Windows — it depends on Linux kernel headers (e.g. loopdev from guest-components). Use a Linux environment or GitHub Actions to build the proplet. Cross-compiling from Intel macOS to ARM64 requires the macOS SDK and is not directly supported.
Building an individual service
You can build individual services using the following:
make <service>For example, to build the manager service, use the following:
make managerThe built binaries will be located in the build directory.
The Rust proplet is built separately (requires Rust installed). Use:
make propletThis runs cargo build --release inside the proplet directory and copies the resulting binary to build/proplet.
For more information on the proplet, see the Proplet Documentation.
Building examples
Available WASM examples can be built using:
make <example>Available examples include: addition, compute, hello-world, http-client, http-server, and filesystem.
For example, to build the addition example:
make additionThis compiles the example to WebAssembly format. The compiled .wasm file is located in the build directory.
Building the Plugin SDK Crates
Propeller includes two Rust crate SDKs for extending behaviour via WebAssembly plugins:
crates/propeller-plugin-sdk— SDK for writing Manager pluginscrates/propeller-proplet-plugin-sdk— SDK for writing Proplet plugins
Build both with:
cargo build --release --workspace --manifest-path crates/propeller-plugin-sdk/Cargo.toml
cargo build --release --workspace --manifest-path crates/propeller-proplet-plugin-sdk/Cargo.tomlFor authoring plugins, see the Plugins documentation and the plugin-auth and proplet-plugin-example examples.
Testing Examples
To test the compiled Wasm example locally with Wasmtime (requires Wasmtime installed):
wasmtime --invoke add ./build/addition.wasm 1 2Expected output:
warning: using `--invoke` with a function that takes arguments is experimental and may break in the future
warning: using `--invoke` with a function that returns values is experimental and may break in the future
3Some examples require specific configuration or environment variables when running under Propeller:
- http-client/http-server: Require
PROPLET_HTTP_ENABLED=trueconfiguration - filesystem: Requires
PROPLET_DIRS=/tmpor your desired directory mount
These variables are configured when running the proplet service, not during the build.
Installing
Once you have built the Go services, you can install them using:
export GOBIN=~/go/bin
export PATH=$GOBIN:$PATH
make installExample output:
cp build/cli ~/go/bin/propeller-cli
cp build/manager ~/go/bin/propeller-manager
cp build/proxy ~/go/bin/propeller-proxyThe make install target copies binaries from the build directory to $GOBIN with a propeller- prefix. The $GOBIN variable must be set before running make install, or the command will fail with a permission error. If $GOBIN is not set, it defaults to $HOME/go/bin.
After installation, ensure $GOBIN is in your PATH:
export PATH=$GOBIN:$PATHThis installs:
$GOBIN/propeller-cli- Command-line interface (17 MB)$GOBIN/propeller-manager- Task orchestrator service (25 MB)$GOBIN/propeller-proxy- Wasm module proxy service (7 MB)
Testing
The project uses mockery to generate mock interfaces for testing. The make test target automatically runs mockery --config .mockery.yaml to generate mocks before running tests.
Important: The project uses mockery v3. Install it with:
go install github.com/vektra/mockery/v3@latestThe project uses pkgname: mocks and generates individual mock files for each interface.
To run the Go service tests:
make testExample output:
Generating mocks...
mockery --config .mockery.yaml
ok github.com/absmach/propeller/manager 0.218s
PASSThis target runs go test -v ./manager for the manager service.
To run all tests, including the Rust proplet test suite:
make test-allThis runs Go tests with go test -v ./... and Rust tests with cargo test --release.
Linter
Propeller uses golangci-lint to lint the code. Install golangci-lint:
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latestThen run the linter:
make lintThis lints all Go code and runs cargo fmt --all -- --check, cargo clippy -- -D warnings, and cargo check --release on the Rust proplet code.
The linting process can take 2-3 minutes or longer depending on system resources. This is normal behavior. The linter performs comprehensive code quality checks across the entire codebase.
Base Services (Atom & FluxMQ)
Starting Base Services
To start the base infrastructure (Atom authentication/authorization and FluxMQ message broker) for Propeller, use:
make start-baseThis runs docker compose -f docker/compose.yaml --env-file docker/.env up -d and starts all required Atom and FluxMQ services.
Example output:
docker compose -f docker/compose.yaml --env-file docker/.env up -d
[+] up 7/7
✔ Container propeller-fluxmq-node1 Running 0.0s
✔ Container propeller-fluxmq-auth Running 0.0s
✔ Container propeller-nginx Running 0.0s
✔ Container propeller-jaeger Running 0.0s
✔ Container propeller-atom-db Healthy 0.5s
✔ Container propeller-atom Healthy 1.0s
✔ Container propeller-atom-ui Running 0.0s- Docker must be installed and running
- No other services should be bound to the ports specified in
docker/.env(typically 1883 for MQTT, 443 for HTTPS, etc.)
Provisioning Propeller
After the base services are running, provision Propeller resources:
propeller-cli provisionThis interactive command:
- Creates Atom tenant and entity credentials
- Generates a
config.tomlfile with all necessary configuration - Sets up entities for manager, proplet, and proxy services
The generated config.toml contains Atom credentials (tenant_id, entity_id, api_key) that are required for all services to communicate.
Using config.toml with Docker Compose
Copy the generated config to the docker folder:
cp config.toml dockerThe volume mounts for config.toml are already configured in docker/compose.propeller.yaml. This allows you to use a single config.toml instead of setting environment variables.
Configuring Docker Containers with config.toml
The docker/compose.propeller.yaml file already includes volume mounts for the generated config.toml in each service:
For the manager service:
volumes:
- ./config.toml:/config.tomlFor the proplet service:
volumes:
- ./config.toml:/home/proplet/config.tomlFor the proxy service:
volumes:
- ./config.toml:/config.tomlAfter copying the config file, start the Propeller services:
make stop-propeller
make start-propellerVerifying Service Connectivity
Once services are running, verify they connected to MQTT:
Manager logs (healthy state):
{"time":"2026-07-31T10:36:22.215275551Z","level":"INFO","msg":"MQTT connection established"}
{"time":"2026-07-31T10:36:22.244721274Z","level":"WARN","msg":"MANAGER_COORDINATOR_URL not configured - FL features will not be available"}
{"time":"2026-07-31T10:36:22.428315303Z","level":"INFO","msg":"Subscribe to MQTT topic completed successfully","duration":"183.383289ms"}
{"time":"2026-07-31T10:36:22.428427657Z","level":"INFO","msg":"Recover interrupted tasks completed successfully","duration":"17.263µs"}
{"time":"2026-07-31T10:36:22.428646062Z","level":"INFO","msg":"cron scheduler started","check_interval":60000000000}
{"time":"2026-07-31T10:36:22.428710044Z","level":"INFO","msg":"manager service HTTP server listening at manager:7070"}
{"time":"2026-07-31T10:36:22.797370311Z","level":"INFO","msg":"successfully created proplet"}Proplet logs (healthy state):
{"timestamp":"2026-07-31T10:36:22.126178Z","level":"INFO","fields":{"message":"Starting Proplet (Rust) - Client ID: 7fe414f2-ec79-4ae9-9f8f-32213f473024"}}
{"timestamp":"2026-07-31T10:36:22.126312Z","level":"INFO","fields":{"message":"MQTT client created (TLS: false)"}}
{"timestamp":"2026-07-31T10:36:22.126492Z","level":"INFO","fields":{"message":"Starting MQTT event loop"}}
{"timestamp":"2026-07-31T10:36:22.126681Z","level":"INFO","fields":{"message":"Using external Wasm runtime: wasmtime"}}
{"timestamp":"2026-07-31T10:36:22.128103Z","level":"INFO","fields":{"message":"Telemetry server listening on 0.0.0.0:9092"}}
{"timestamp":"2026-07-31T10:36:22.153216Z","level":"INFO","fields":{"message":"Starting PropletService"}}
{"timestamp":"2026-07-31T10:36:22.162750Z","level":"INFO","fields":{"message":"Published discovery message"}}
{"timestamp":"2026-07-31T10:36:22.162769Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/start"}}
{"timestamp":"2026-07-31T10:36:22.162773Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/stop"}}
{"timestamp":"2026-07-31T10:36:22.162776Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/registry/server"}}
{"timestamp":"2026-07-31T10:36:22.172227Z","level":"INFO","fields":{"message":"MQTT session not present, triggering re-subscription"}}
{"timestamp":"2026-07-31T10:36:22.172408Z","level":"INFO","fields":{"message":"Reconnection detected, re-subscribing to topics"}}
{"timestamp":"2026-07-31T10:36:22.172443Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/start"}}
{"timestamp":"2026-07-31T10:36:22.172451Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/stop"}}
{"timestamp":"2026-07-31T10:36:22.172458Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/registry/server"}}
{"timestamp":"2026-07-31T10:36:22.172463Z","level":"INFO","fields":{"message":"Successfully re-subscribed to topics after reconnection"}}Stopping Base Services
The base services can be stopped using the following:
make stop-baseCI/CD
GitHub Actions Workflows
Propeller uses GitHub Actions for continuous integration and deployment. The main workflows are located in .github/workflows/.
Build Workflow (build.yml)
The build workflow builds all Propeller services for multiple platforms:
- Go services (manager, cli, proxy): Built for linux/amd64, linux/arm64, linux/riscv64
- Proplet (Rust): Built for linux/amd64, linux/arm64, linux/riscv64
Go Services Build
The Go services are built using the following matrix:
| OS | Architecture | Service |
|---|---|---|
| linux | amd64 | manager |
| linux | arm64 | manager |
| linux | riscv64 | manager |
| linux | amd64 | cli |
| linux | arm64 | cli |
| linux | riscv64 | cli |
| linux | amd64 | proxy |
| linux | arm64 | proxy |
| linux | riscv64 | proxy |
The Go services are built with CGO_ENABLED=0 for static binaries, so they cross-compile without extra toolchains. The GCC cross-compilers installed via apt are used only for the Rust proplet job.
Proplet (Rust) Build
The proplet is built using Cargo with the following targets:
| Architecture | Rust Target |
|---|---|
| amd64 | x86_64-unknown-linux-gnu |
| arm64 | aarch64-unknown-linux-gnu |
| riscv64 | riscv64gc-unknown-linux-gnu |
Cross-compilation for arm64 and riscv64 requires installing the appropriate GCC cross-compilers:
# For arm64
sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross
# For riscv64
sudo apt-get install -y gcc-riscv64-linux-gnu libc6-dev-riscv64-crossThe linker is set via environment variables:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER=riscv64-linux-gnu-gccPlatform Limitations
-
macOS/Windows builds: Not supported in CI because:
loopdevcrate (from guest-components) requires Linux kernel headers (linux/loop.h)- Cannot cross-compile from Linux to macOS/Windows due to platform-specific dependencies
-
FreeBSD/NetBSD/OpenBSD: Not supported because:
- Cannot cross-compile from Linux due to
aws-lc-sysusing different pthreads API
- Cannot cross-compile from Linux due to
Running Propeller Binaries Directly
For development and debugging, you can run Propeller services directly on your host instead of in Docker containers. This allows easier access to logs, debuggers, and faster iteration.
Prerequisites: The base services must still be running (via make start-base), and you must have a valid config.toml in your working directory.
First, stop the Docker-based Propeller services:
docker stop propeller-manager propeller-proxy propeller-propletStarting the Manager
In a terminal, run:
propeller-managerExpected output:
{"time":"2026-07-31T10:36:22.215275551Z","level":"INFO","msg":"MQTT connection established"}
{"time":"2026-07-31T10:36:22.244721274Z","level":"WARN","msg":"MANAGER_COORDINATOR_URL not configured - FL features will not be available"}
{"time":"2026-07-31T10:36:22.428315303Z","level":"INFO","msg":"Subscribe to MQTT topic completed successfully","duration":"183.383289ms"}
{"time":"2026-07-31T10:36:22.428427657Z","level":"INFO","msg":"Recover interrupted tasks completed successfully","duration":"17.263µs"}
{"time":"2026-07-31T10:36:22.428646062Z","level":"INFO","msg":"cron scheduler started","check_interval":60000000000}
{"time":"2026-07-31T10:36:22.428710044Z","level":"INFO","msg":"manager service HTTP server listening at manager:7070"}The manager exposes an HTTP API on localhost:7070.
Starting the Proplet
In another terminal:
propeller-propletExpected output:
{"timestamp":"2026-07-31T10:36:22.126178Z","level":"INFO","fields":{"message":"Starting Proplet (Rust) - Client ID: 7fe414f2-ec79-4ae9-9f8f-32213f473024"}}
{"timestamp":"2026-07-31T10:36:22.126312Z","level":"INFO","fields":{"message":"MQTT client created (TLS: false)"}}
{"timestamp":"2026-07-31T10:36:22.126492Z","level":"INFO","fields":{"message":"Starting MQTT event loop"}}
{"timestamp":"2026-07-31T10:36:22.126681Z","level":"INFO","fields":{"message":"Using external Wasm runtime: wasmtime"}}
{"timestamp":"2026-07-31T10:36:22.128103Z","level":"INFO","fields":{"message":"Telemetry server listening on 0.0.0.0:9092"}}
{"timestamp":"2026-07-31T10:36:22.153216Z","level":"INFO","fields":{"message":"Starting PropletService"}}
{"timestamp":"2026-07-31T10:36:22.162750Z","level":"INFO","fields":{"message":"Published discovery message"}}
{"timestamp":"2026-07-31T10:36:22.162769Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/start"}}
{"timestamp":"2026-07-31T10:36:22.162773Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/control/manager/stop"}}
{"timestamp":"2026-07-31T10:36:22.162776Z","level":"INFO","fields":{"message":"Subscribed to topic: m/c676fdcf-4682-4c06-ad85-968c4f3b7642/c/26453a49-20e8-4d67-ad20-7de663bda008/registry/server"}}The proplet automatically registers itself with the manager.
Starting the Proxy
The proxy pulls WASM modules from OCI registries. Configure it with environment variables:
export PROXY_REGISTRY_URL="docker.io"
export PROXY_AUTHENTICATE="TRUE"
export PROXY_REGISTRY_USERNAME="" # set if registry requires auth
export PROXY_REGISTRY_PASSWORD="" # set if registry requires auth
propeller-proxyExpected output:
{"time":"2026-07-31T14:28:21.896489814+03:00","level":"INFO","msg":"successfully initialized MQTT and HTTP config"}
{"time":"2026-07-31T14:28:21.89656186+03:00","level":"INFO","msg":"starting proxy service"}
{"time":"2026-07-31T14:28:21.896517887+03:00","level":"INFO","msg":"MQTT connection established"}
{"time":"2026-07-31T14:28:21.942002962+03:00","level":"INFO","msg":"successfully subscribed to topic"}
{"time":"2026-07-31T14:28:21.942081961+03:00","level":"INFO","msg":"health server listening","addr":":9191"}Postman Collection
A Postman collection for the Propeller API is available at public/postman_collection.json. Import this into Postman to quickly test all API endpoints.