this step by step guide will show the basics of using the carrier cli

Let's go

Installing Carrier

Head over to https://github.com/devguardio/carrier/releases and download the latest binary for your platform.

Suggestion: Rename the download locally to just "carrier" Depending on your OS, copy them to a location within your PATH. This is usually /usr/bin, but you may want to add them to your home directly and instead change your PATH variable. See this stackoverflow thread for instructions.


Creating an identity

Carrier is built on eliptic curve identities, specifically ed25519. Every device and every user in the system has an identity. We're going to generate yours now.

$ carrier setup
identity: oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9

This is your identity that was generated out of randomness from your system. The carrier network will know your computer by this long number. No registry with any provider is nessesary to create an identity and you may create as many as you want. Unlike an api key, a carrier identity is not tied to a specific service. you may use it in peer to peer communication, or talk to any other service on the carrier network without needing prior registration.

In addition to your identity, a secret is stored in ~/.devguard/carrier.toml . Never share this file with anyone, but do make backups.

Putting your thing on the network

Now that we have an identity, we can can expose services on the global bus. carrier does not have user-definable names like domains. There is no central registry, instead everything is cryptographically tied to your identity.

for now we're going to use the default services exposed by the carrier cli

$ carrier publish
[INFO endpoint] identity: oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9
[INFO endpoint] via udp
[INFO endpoint] via udp
[INFO endpoint] broker: oW9mVQjsTauUNjweCww5GMoXHKhpbM4CCAos1HSJSy8rkr3
[INFO endpoint] [:status = 200, ]

Congratulations, your machine is now on the carrier bus!


Authorizations are based on identities as well. We can authorize different identities for different services, but for this quickstart guide, we're just authorizing our current machine to connect to itself.

$ carrier identity
$ carrier authorize oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9
$ carrier publish


one of the built in services is carrier shell. In a new terminal, open a shell on the publisher:

$ carrier shell oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9
[:status = 200, ]

you can open a shell to any device on the network that your local identity is authorized for. There's no need to configure any network, like open any ports. In best case, carrier will find a direct peer to peer path, worst case it will relay your encrypted traffic through the devguard carrier ring.

more services

the default publish cli has plenty services. find out which ones are available on any host using the discovery command.

$ carrier disco oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9
DiscoveryResponse {
    carrier_revision: 72,
    carrier_build_id: "0.10-bPFGYHHH9Q",
    application: "carrier-cli",
    application_version: "0.10-bPFGYHHH9Q",
    paths: [


sysinfo is a powerful stats service, espcially when collected at scale using carrier conduit or nexus.

$ carrier sysinfo oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9
[:status = 200, ]
Sysinfo {
    Uname {
        sysname: "Linux",
        release: "5.2.4-arch1-1-ARCH",
        machine: "x86_64",
    Mem {
       free: 45266868,
    Netdev {
        name: "eth0",
        macaddr: "fa:0c:fd:f9:a2:c6",
        addrs: [
            NetAddress {
                addr: "",
                mask: "",
                broadcast: "",

File Transfer

if your target has a file system, you can push files. This will also be useful later on when you combine services to build an over the air updater.

$ carrier push oWDw4B1f88jiGj71qMCYTL8RCCdpRYfgbSNXQSDVByRVHV9 /localfile  /remotefile
rtt 0ms | 14.06 KB / 49.14 KB [=======>--------------------] 28.62 % 6.22 MB/s 0s

Peer Discovery

you may have noticed we so far ignored two additional lines from the first carrier setup.

shadow: oT136mPPVYptwoMvnjb2AUXird6tsKsyQ2S46TCTNYr7WwH
shadow-secret: oWBx6jaNJo3XLCvs56EnwBb6uBhJZVEYbnXJNrz9nsFHyrf

they are also permanently stored in your ~/.devguard/carrier.toml The public 'shadow' address is a subnet on which devices can discover each other. Carrier is free to use for up to 10 peers per shadow.

In order to use massive managment features like conduit, you must pick one of the shadow/secret pair and copy it over to all of your devices ~/.devguard/carrier.toml.

All your devices will then appear in carrier subscribe

$ carrier subscribe oT136mPPVYptwoMvnjb2AUXird6tsKsyQ2S46TCTNYr7WwH


carrier conduit is how you automate devices at scale.

This document will guide you step by step through building your own conduit, which will connect to your fleet and collect sysinfo stats. Usually you would process those stats further, for example write them into a database available to webservices.

Conduit runs on your systems, and establishes end to end encrypted channels to your devices. We at devguard do not offer querying your device data via rest interfaces, because this would compromise the principle of end to end encryption.

You can however, implement your http interfaces inside conduit, should they be more suitable for your workflow.

simple data collector with nodejs

before you continue please make sure all devices are on the same shadow. see quickstart/shadows

At scale, you will want an automated way to collect data from your systems. building a data collector in nodejs is the easiest way to get started, if you already know javascript.

$ npm install --save devguard-carrier

we also recommend using protobufs. for this example you will need the file "carrier.sysinfo.v1.proto" from the carrier source code here

$ npm install --save protobufjs
var carrier = require('devguard-carrier');
var protobuf = require("protobufjs");


// load the protobuf files
protobuf.load("carrier.sysinfo.v1.proto", function(err, root) {

    // we will call the sysinfo endpoint, which is this type
    var Sysinfo = root.lookupType("carrier.sysinfo.v1.Sysinfo");

    // create a new conduit
    const conduit = new carrier.Conduit();

    // whenever we find a new peer on the shadow
    conduit.on("publish", (identity, peer) => {

        // we connect to it
        // note that we could also decide to only connect some peers,
        // this is useful if you want to black/whitelist something,
        // or for horizontal scaling when running multiple conduits in prallel

        // discover the services this peer has on offer
        peer.discover((disco) => {

            console.log("disco", identity, disco);

            // if the device has the sysinfo service
            if (disco.paths.includes("/v2/carrier.sysinfo.v1/sysinfo")) {

                let headers = {
                    ":path" : "/v2/carrier.sysinfo.v1/sysinfo",

                // we poll it every 10 seconds
                // this is soft-realtime multiplexed into an established connection
                // so alot different than http polling.
                // some services may also continuously deliver messages
                // in which case the first integer arg is a reconnect delay,
                // should the connection drop for whatever reason

                peer.schedule(10, headers, (identity, message) => {
                    let sysinfo = Sysinfo.decode(new Uint8Array(message));
                    console.log(identity, sysinfo);

    // let's party


rust setup

before you continue please make sure all devices are on the same shadow. see quickstart/shadows

building your conduit in rust will help performance alot in high throughput scenarios but requires rust knowledge. If you do not know rust, please refer to the official getting started guide

create a new project and add carrier as a dependency to Cargo.toml

$ cargo new conduit-myproject
Created binary (application) `conduit-myproject` package
$ cd conduit-myproject
$ edit Cargo.toml

in main.rs you would do this:

use carrier::{self, conduit, Identity};

pub fn main() {
    // load ~/.devguard/carrier.toml
    let config = carrier::config::load().unwrap();
    let conduit = conduit::Builder::new(config).unwrap();
    // start peer discovery
    conduit.start(move |identity: Identity, broker: &mut conduit::ConduitState| {

        // connect to the discovered peer
        let mut peer  = broker.connect(identity.clone());

        // open a sysinfo stream
        let headers = carrier::headers::Headers::with_path("/v2/carrier.sysinfo.v1/sysinfo");

        // if it is closed, reopen in 10 seconds.
        // sysinfo always closes after one message, because it is sync.
        // polling a channel is MUCH cheaper than polling http
        // because it is inside an established connection, like http2
        peer.schedule(std::time::Duration::from_secs(10), headers,
            // for each message
            move |identity: &Identity, msg: carrier::proto::Sysinfo|{
                println!("{}: {:?}", identity, msg);

Custom Publishers

when building IoT devices, you will inevitably want to build your own services can be called through carrier. You can use the cli to call any service using "carrier get"

At the present time, the only way to do this is using the rust api, specifically the PublisherBuilder For a fully functional example, check out this example on github

pub fn main() -> Result<(), Error> {
    let poll            = osaka::Poll::new();
    let config          = carrier::config::load()?;
    let mut publisher   = carrier::publisher::new(config)
        .route("/v2/myorg.mything.v1/whodis",      None, whoami)
        .with_disco(NAME.to_string(), VERSION.to_string())

pub fn whoami(
    _poll: osaka::Poll,
    _headers: carrier::headers::Headers,
    _identity: &carrier::identity::Identity,
    mut stream: carrier::endpoint::Stream,
) -> Option<osaka::Task<()>> {
        whodis: "peter".to_string()