Advanced Device Provisioning Workflow for Large Fleets: Preloading and Pre-provisioning

Editor's Note: This post was updated on January 25, 2018, to remove a statement that flasher type devices are not supported in the pre-provisioning workflow, as this limitation no longer exists.

Let's imagine you have a factory where you are creating resin.io-enabled devices that need to function when they first boot up. Maybe your devices have to function even before they are able to connect to the Internet and download software. Or perhaps you are sending out SD cards for your customers to boot their devices, and you'd like those devices to show up on resin.io without any extra setup (so your customers could say, for example, "I see that the HQ Lobby signage machine just came online for the first time").

This tutorial will walk you through the steps required to provision a large fleet of devices, preload the latest version of your application container, and associate specific resin.io devices with the SD cards you've prepared. After covering the necessary prerequisites, the tutorial will explain:

  1. Preloading your application container into an SD card image, which you can then burn many times
  2. Pre-provision a device onto resin.io
  3. Configure a burned SD card to boot with the identity of a pre-provisoned device and network connection settings

This write-up assumes you're already registered on resin.io. If you've not yet signed up, follow our getting started guide before you continue!

Prerequisites

Tools

We'll use resin-cli, the resin command line interface, which requires Node.js 6 or above, and the resin-preload-image-script which needs Docker, and currently only supports Linux (no MacOS or Windows support), requiring the BTRFS storage drivers for resinOS 1.x or AUFS drivers for resinOS 2.x preloading (Ubuntu recommended).

First, install resin-cli by calling npm install --global --production resin-cli on the machine you use for provisioning. This might require elevated privileges in some circumstances. After this, you need to run resin login to get access to the resin.io functionality through the command line, and can test that you are successfully logged in by issuing resin whoami. You can see the full set of commands available using resin help --verbose.

Then download or git clone the resin-preload-image-script tool onto your development machine.

Setting up your application

If you already have an application, you just need the ID of the application, which you can get e.g. from the resin apps command:

$ resin apps
ID     APP NAME      DEVICE TYPE           ONLINE DEVICES DEVICES LENGTH  
123456 mydeployment  raspberrypi3          1              1  
...

If you are starting from scratch, you can create a new application with resin app create by setting an application name and a device type (the available device types can be found by resin devices supported):

$ resin app create mydeployment --type raspberrypi3
Application created: mydeployment (raspberrypi3, id 123456)  

Note the ID from the output.

Before continuing, make sure that you have already successfully pushed code to this application, meaning there's something to preload! See our getting started guide for your first push. If you are setting up your git remote manually, you will need to use the <username>@git.resin.io:<repo>.git address, replacing <username> with your username (get it from resin whoami), and <repo> with the GIT REPOSOTORY value from the output of resin app <appname> command. Alternatively, check the getting started guide for how to get the correct remote value from the resin.io dashboard.

Step 1: Preload your application container into an SD card image

The first step is creating an SD card image that is preloaded with your latest application container. This way, the device can run your container the first time it boots without connecting to the Internet and downloading the container. Begin by downloading an unconfigured device image for your specific device type (the same as the one set for your application; you can check the value by calling resin apps). For example:

$ resin os download raspberrypi3 -o raspberrypi3-mydeployment.img --version menu
Getting device operating system for raspberrypi3  
Downloading Device OS 1.24.1 [========================] 100% eta 0s  
The image was downloaded successfully  

There are a number of different settings available for the --version flag, for example default and latest, see the whole list by checking the help with resin os download --help.

Once the download has finished, navigate to the directory to which you've downloaded the resin-preload-image-script. The included script will be used to inject the latest version of your application into this unconfigured device image. You can get help with regards to this script by running ./preload.sh --help.

The preload procedure requires Docker to be running on your development machine and needs a few extra pieces of information. You need to supply it with your application ID (APP_ID), and an API/Auth Token for your account, which you can get from the preferences page in the resin.io dashboard (API_TOKEN). You can set these values up as environment variables as:

$ export APP_ID=...
$ export API_TOKEN=...

and then run the preload script, pointing it to the unconfigured device image as:

$ ./preload.sh --app ${APP_ID} --api-token "${API_TOKEN}" --img /path/to/raspberrypi3-mydeployment.img

where you need to replace the value after the --img flag with the path to the image. Please note that you need to provide the full absolute path for --img to work correctly! If everything worked, you'll see the script mounting the image file, using Docker to download your application, and unmounting the image:

asciicast

Note, that in some cases, depending of your Docker and privileges setup, you might need to run this script with sudo!

Now the image file is ready to be burned to an SD card (preferably using Etcher!). You can burn the file onto as many SD cards as you like! After the SD card is burned, one more step is needed to associate each card with a specific newly created device.

Step 2: Pre-provision a device

You can create a new device's record for your application by running resin device register, replacing mydeployment with the name of your actual application:

$ resin device register mydeployment
Registering to mydeployment: 7807b813deefda6e2c7a1b149ff37d2770e203c31ed6c8ff2f71b8dfad5831  

The output of the command is the UUID of your new device, used in the following steps. If you visit your application dashboard at this stage, you'll see a new device in Configuring state.

Preprovisioned Device on the Dashboard

You can also change the name of the device from the automatically assigned one to adhere to your naming convention:

$ resin device rename 7807b813deefda6e2c7a1b149ff37d2770e203c31ed6c8ff2f71b8dfad5831
? How do you want to name this device? hq-lobby-1

Step 3: Configure the SD card with the device settings

To start configuring this newly created device, run resin config generate, which creates a new configuration file (here config.json, but you'll likely want to have a different file for each of your new devices). You'll be asked for the device's prospective network settings and update interval:

$ resin config generate --output config.json --device 7807b813deefda6e2c7a1b149ff37d2770e203c31ed6c8ff2f71b8dfad5831
? Network Connection wifi
? Wifi SSID MyNetwork
? Wifi Passphrase ************
? Check for updates every X minutes 1
...

For the final step, you need to inject this configuration into the preloaded but not-yet-configured image on the SD card burned in the last step. You can do that using resin config inject. You usually have to call this with administrator privileges (and if so, you will need to log in with sudo resin login as well). Following the example:

$ sudo resin config inject config.json --type raspberrypi3

where you should adjust your configuration file's name from the previous step, and the device type to match your application's setup.

After this, you can send off the SD card to the end user!

What Happens on the first boot?

Imagine that you followed these steps and sent the SD card to your customer. The first time their device starts from that card, your application container will start to run automatically.

If the device can then connect to the Internet, it will attempt to connect to the resin.io services. The device will indicate that it's online and will show up in the dashboard with the preset UUID and name.

If there is a newer version of your application container available, the device will download and apply the changes automatically. The device also receives and applies any environment variables set for the fleet or the particular device.

This process saves time and bandwidth downloading the initial version of the application on a device. From that point on, the device will continue functioning as a normal resin.io enabled device, and as more devices are added to the fleet, they will be easier to set up and organize.

Further Tips

Environment variables

Environment variables currently cannot be preloaded onto the SD card. Those settings are instead downloaded and applied by the device the first time it connects to the Internet. Make sure that your application functions even without external environment variables by setting good default variable values and fallback application behaviour.

Using resin-cli, you can still set both application or device environment variables through the resin env add command, which the device will receive the first time it gets online. So you can make device-specific environment configuration part of your pre-provisioning process, too!

Scripting Pre-provisioning

The device pre-provisioning process can easily be automated. Here's an example of a shell script to create a device, configure it, and inject the configuration into an already-burned SD card that is currently plugged into the development machine:

#!/usr/bin/env bash

## Change this value to your application name!
export APP_NAME=mydeployment

# Automatically get device type from resin-cli
DEVICE_TYPE=`resin apps | grep -w ${APP_NAME} | awk '{ print $3 }'`

echo "Preprovisioning device"  
UUID=`resin device register ${APP_NAME} | awk '{ print $4 }'`  
echo "New device: ${UUID}"

echo "Setting device name"  
resin device rename ${UUID}

echo "Config generation"  
## The following step will ask for network configuration
resin config generate --device ${UUID} --output config.${UUID}.json

echo "Config injection, insert the SD card to configure"  
## The following step will ask for the proper drive with exiting
## image to inject the config into
sudo resin config inject config.${UUID}.json --type ${DEVICE_TYPE}  

This is just an example, or course, and would need to be adjusted for your particular use case.

Auth Token login

If you'd like to use a single method of authentication with the resin.io service, you can use your Auth Token for both tools by logging in with resin login --token "${AUTH_TOKEN}". Please note that the token currently has a lifetime of 7 days, after which you'll need to use a new token from the dashboard for both the resin-cli and the preload script.


When you are starting starting off with resin.io, you likely appreciate the automatic nature of provisioning new devices without any user intervention. As your fleet grows, however, that process becomes less scalable. Hopefully this tutorial helps you scale your device deployment process!

We are also going to make this process a lot smoother in the coming months. In the meantime, we thought we'd give you a heads up about what's possible today.

Let us know in the forums if you have any comments or questions, and we would love to hear any experience you have applying these instructions to your use case!

comments powered by Disqus
Terms of Service | Privacy Statement | Master agreement | Copyright 2019 Balena | All Rights Reserved