This book is intended to getting you started with devguard carrier and discovering its end to end encrypted remote managment features.

We will cover the usage of the commandline interface. A terminal and a unix-like os is required (linux, macos, windows subsystem for linux).

For hobby and home users with below 10 devices, this guide should hopefully cover everything that you need. If not, feel to open an issue on its github repo

For large scale IoT installations, this book is not sufficient and it's best to contact devguard directly.

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.

rust setup

building a conduit requires basic 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 Carrier.toml

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

simple data collector

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

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()