Building and Using a Custom Nerves System

Nerves provides a way to combine your Elixir project code with everything else that’s needed to build firmware for embedded devices. Let’s see how to customize one of the provided systems and use it in a firmware build.

This worked as of mid-October 2016, but Nerves is still under development and things may change. Check the official documentation if things don’t seem to be working:

Pick a System

First, decide which of the existing systems you want to customize. In my case, this will be the one for the Raspberry Pi 2, which lives here:

I’ve forked that project and will be using my fork in the rest of the article. This step is not absolutely required unless you want to be able to commit your changes, and it can be done later if you change your mind, so feel free to skip it and just use the repo linked above.

It’s also possible to create a system from scratch, which you may need to do if you’re porting Nerves to new hardware, but that is far beyond the scope of this article.

Get Linux

Because they’re based on Buildroot, Nerves Systems currently have to be built in Linux. Since I’m on OS X, I’m using Docker to create a container with Ubuntu that I can use for this part.

If you already have access to a remote Linux host, that will work, and if you’re using Linux as your development machine, then you probably already know this stuff. :)

$ docker run -it ubuntu /bin/bash
# apt-get update
# apt-get install git g++ libssl-dev libncurses5-dev bc m4 make unzip cmake
# apt-get install wget cpio python bzip2
# exit

The first apt-get line is from the Nerves docs. The second is for things that were assumed to be present in a standard Ubuntu install, but were not included in the Docker image.

Now you have a container with all the necessary stuff installed. This would be a good time to save your work.

$ docker ps -a
... copy the id
$ docker commit [the_id] nerves_basic

This may take a while. When it finishes, you can run docker images to check that the image is there.

Build an Existing System

To check that everything works, let’s build the un-modified nerves_system_rpi2 system. Start a container using your new image, and expose a directory for the output:

$ docker run -it -v ~/docker_stuff:/tmp/output nerves_basic /bin/bash
$ git clone
$ git clone
# ./nerves_system_br/ nerves_system_rpi2/nerves_defconfig rpi2_out
# cd rpi2_out
# make
... run `make help` to see other options, such as:
# make system
# cp nerves_system_rpi2.tar.gz /tmp/output/
# exit

This maps docker_stuff in my home directory to /tmp/output inside the container. When the file is copied, it should show up in ~/docker_stuff.

This is another good time to save your work in the container as a new image, so that you can start over from here.

docker ps -a
... copy the container id
docker commit [container id] nerves_after_build

Every time you ‘docker run’ it creates a new container based on the specified image. Next time, we’ll start and attach to the container.

Note: I tried to use the docker volume as the output directory to avoid having to copy and expand the .tar.gz file, but I ran into a permissions error.

Set Up Build Machine

In order to use the newly built system, we’ll need to expand the .tar.gz file. Remember it was copied to a Docker volume at ~/docker_stuff, so check that it’s there, and then:

$ cd /tmp
$ tar -xzvf ~/docker_stuff/nerves_system_rpi2.tar.gz

Now there should be a nerves_system_rpi2 directory under /tmp with the contents of the .tar.gz file.

Next, set the NERVES_SYSTEM environment variable to this path so that you override Nerves’ default behavior of fetching the ‘official’ system.

$ export NERVES_SYSTEM=/tmp/nerves_system_rpi2

And since we’re building for the Raspberry Pi 2, let’s also set NERVES_TARGET which is used in the mix.exs of Nerves projects:

$ export NERVES_TARGET=rpi2

Build Firmware

Presumably the reason you wanted a custom system is that you need it to build firmware for your own project. If you don’t have a project, you can use one of the Nerves example projects like blinky or hello_wifi.

Let’s use the hello_wifi project in the nerves-examples repo.

$ git clone
$ cd nerves-examples/hello_wifi
... edit config/config.exs if you'd like to insert your ssid and password.
$ mix deps.get
$ mix firmware

Hopefully that completes with no errors! You should see it download the toolchain, but not the system, since it’s using the local one you built. You can go ahead and mix firmware.burn if you’d like to try it on your device.

Modify a System

Now let’s return to the Docker image and use Buildroot to make a modification to the existing RPi2 system.

$ docker ps -a
... find the one using the nerves_basic image and copy the container id (or the name)
$ docker start [container id or name]
$ docker attach [container id or name]
... press enter to get a prompt
# cd rpi2_out
# make menuconfig

Note: since you must type make menuconfig in the output directory, it seems that you must have built the system once before trying to modify it? That doesn’t make sense if you’re creating one from scratch. So probably it’s just because of the relative directory that it uses to copy the nerves-defconfig.

This will pop up the blue-and-gray Buildroot Configuration menu.

Buildroot Configuration initial screen

Use / to search for something… how about the driver for the rtl8188eu wifi adapter?

Buildroot Configuration search

and now we know that it can be found under Target Packages -> Hardware Handling -> rtl8188eu

Buildroot Configuration navigate and enable

Navigate the menus to find it, and press space to enable that option.

Then arrow over to Save, and confirm to save the .config file.

Exit out of the Buildroot Config dialog, and back at the prompt:

# make savedefconfig

This will update the nerves_defconfig over in nerves_system_rpi2 by way of a relative directory path.

# cd ../nerves_system_rpi2
# git diff
diff --git a/nerves_defconfig b/nerves_defconfig
index 9c01b43..6c76080 100644
--- a/nerves_defconfig
+++ b/nerves_defconfig
@@ -52,6 +52,7 @@ BR2_PACKAGE_LINUX_FIRMWARE_RTL_87XX=y

And this is why I checked out my fork of nerves_system_rpi2 earlier – so that I can commit that change:

# git checkout -b with_8188
# git add .
# git commit -m "Add support for rtl8188eu"

Which you can see here:

Build Modified System

Now that we have modified the system, let’s build it again:

# cd ..
# ./nerves_system_br/ nerves_system_rpi2/nerves_defconfig rpi2_out
# cd rpi2_out
# make
# make system
# cp nerves_system_rpi2.tar.gz /tmp/output
# exit

Build Firmware

Remove the directory where you expanded the unmodified custom system, and expand the modified one:

$ cd /tmp
$ rm -rf /tmp/nerves_system_rpi2
$ tar -xzvf ~/docker_stuff/nerves_system_rpi2.tar.gz

Ideally the modified system would have a different name, or at least a different version number. It’s a really bad idea to build the same artifact name and version number with different contents. You should be able to look at the filename and tell what it contains.

Remember to set the NERVES_SYSTEM environment variable as above, or Nerves will fetch and use the released version of the system rather than your locally built one. And set NERVES_TARGET also if needed.

$ cd /path/to/nerves-examples/hello_wifi
$ mix deps.get
$ mix firmware

Then, optionally, mix firmware.burn if you want to burn a micro SD card to try it in your device.

Unfortunately, enabling the rtl8188eu driver was not enough to get my wifi adapter to work. I hear that the 8188 and 8192 are problematic on the latest Linux kernel. Which is just super because I have two of each!

So, this offer remains open!

Nerves chocolate offer


If things aren’t working, try deleting the _build and/or rel/hello_wifi directories under the hello_wifi project.


We’ve seen how to use Buildroot to modify a Nerves system, and how to use that custom system when building firmware.

Copyright 2016 Wendy Smoak - This post first appeared on and is CC BY-NC licensed.



Focusing on José Valim: A Biographical Interview

José Valim is the creator of the Elixir programming language. Austin Erlang is taking a deeper look into José’s life to quantify the tenets of successful programming and living a rich and rewarding life. Introduction José was born in Porto Alegre, Brazil in 1986. He is married with one young...


MongooseIM 2.0.0beta2: the power of an XMPP platform, with the simplicity of a REST API

We are thrilled to announce the MongooseIM platform version 2.0.0beta2. MongooseIM platform 2.0.0beta2 is about one massive change: a REST API, both for backend integration, and for client/server development. This is a major step towards the game-changing version 2.0.0, which will be released in the coming weeks. MongooseIM 2.0.0 will tremendously lower the barrier of entry to our platform for most developers worldwide.

REST API for backend integration

Following popular and obvious demand, MongooseIM now implements a new REST API, for backend integration.

Integration problem for backend developers and DevOps

The MongooseIM platform is mostly used in very large and complex infrastructures, in various types of data centers (cloud, bare metal, or hybrid). In that context, there is always a high need for tight integration and coupling between the MongooseIM platform, and the full ecosystem in the data center.

For most backend developers, as well as for DevOps, this is very difficult as such an integration requires understanding the powerful hooks system in the core of MongooseIM server or the command line interface, and how they relate to the purpose of the infrastructure.

It was thus very difficult to develop interconnected pieces of architectures.

The obvious solution: a REST API

Since most techies use modern REST APIs these days, this was the obvious and natural choice, and our customers and prospects confirmed and supported this thinking.

The MongooseIM platform now offers a very simple backend REST API:

  • management: users and sessions
  • messaging:
    • send one-to-one messages
    • send group chat messages (both MUC and MUC light)
    • retrieve users’ message history

The documentation is simply the de facto standard: Swagger. With Swagger, it is even more handy, as many code generators can help build code from scratch, with little human customisation. This doc is supporting the Open API Specification. Check out the backend REST API of MongooseIM.

Ease of development and maintenance, plus consistency in infrastructure

As a result, it is now much easier for backend developers to write code against MongooseIM, and maintain it. Also it is much easier for DevOps to interconnect MongooseIM with other servers. Overall, CTOs will be thrilled to have a more consistent set of backends that all discuss over REST APIs, for a seamless infrastructure maintenance experience.


The list of methods made available today is quite narrow, and it is on purpose: we will extend the MongooseIM REST API methods to answer your needs.

REST API for the clients developers

This part may sound less understandable, maybe a bit “revolutionary” and unorthodox for an XMPP platform. To be honest, we thought a lot about it, and hesitated for a long time. But our customers and prospects have sent the signal loud and clear.

Putting it simply: we are not breaking out of XMPP! We are offering the power of an XMPP platform to REST API developers worldwide!

Let us walk you through this thinking.

The problem with XMPP and XML

The MongooseIM platform is based on the XMPP protocol and philosophy. And XMPP is awesome. It is an open standards protocol, backed by both the XSF and the IETF, offering high flexibility and outstanding extensibility. It is always surprising in many ways, as mature features often reveal themselves as highly efficient in modern and contemporary contexts.

XMPP has evolved a lot over the years, to the point that it can look complex and too massive for a lot of developers. As a result, it might seem discouraging to learn so much for most developers.

Additionally, XMPP relies on XML, which is not very trendy, to say the least. The crowds massively prefer JSON these days. We won’t argue here.

As a result, a large audience of developers do not like XMPP nor XML anymore. The consequence for business owners and recruiters is that it has become difficult and painful to find good (and available) XMPP developers. It would be a missed opportunity, as the MongooseIM platform is highly performant and flexible.

The REST API solution for client/server developers

The MongooseIM platform now implements a very simple REST API for client-side developers, with JSON format.

The current implementation covers a minimal set of use cases:

  • authentication
  • send one-to-one messages
  • retrieve message history

In other words, we have created a REST API interface to the MongooseIM platform, by removing all the complexities of XMPP, and by switching from XML to JSON. We have kept only the bare minimal: all you need to quickstart a highly efficient application, with a flexible and powerful backend that will scale to millions.

The documentation is also available on Swagger: check out the client REST API of MongooseIM.

Lowering the barrier of entry to MongooseIM

This client/server REST API with JSON format allows most client-side developers to access the power and massive scalability of MongooseIM, yet maintaining a very simple and efficient codebase on the client.

It is now possible to build massively scalable chat system with the MongooseIM platform, yet keeping a very fast time-to-market, with minimal initial developers investment, virtually no learning curve, and lowering the risk of developer turnover.

It is especially true for applications builders who want to add chat (instant messaging features) in their multi-screen apps, for higher and sustainable acquisition, retention, and engagement.


Please send your needs and pain points, in order to grow further the feature set. That way it will be easier for your apps to sustain your growth.

We are even planning a custom hosting solution, and a standard SaaS. Please contact us for more information.

MongooseIM = XMPP + REST API

A simple REST API with JSON format might sound antagonist to XMPP. Our client-side REST API is an addition to XMPP in MongooseIM. Messaging features will all remain available using the XMPP protocol, and more and more will be available through our REST API.

In other words, we keep the powerful XMPP protocol, we just add a simple client/server REST API for more developers and simpler coding.

Documentation additions

A lot of developers, devops, sysadmins, and CTOs just have difficulties understanding how things work at a high level. As MongooseIM has quite a different architecture than the more classical web applications, there was indeed some explanations due here on the high-level architecture.

We added explanations and schemas for some key concepts, such a transient vs persistent data, nodes joining or leaving a cluster, RDBMS vs NOSQL data storage.

We also created a few new documentation pages to explain the basics on some of the clustering considerations, and also detailed to global architectures, like multi-datacenter setups for very large deployments.

Our contributions to the ecosystem (outside the scope of the MongooseIM server) were not clear, they are documented now. We contribute to both open standards, and open source software, whether we maintain it, or it is maintained by friends. For example, we contribute to XMPPFramework for iOS, and Smack for Android. We now offer a consistent set of features over MongooseIM server, Smack, and XMPPFramework: a given feature we support is available and tested on the three.

Please read our draft roadmap, offering enhanced visibility over where the MongooseIM platform is going next. Feel free to comment on it, and influence the journey!

Many thanks to our contributors on the MongooseIM server

During this development cycle, we received some code contributions to server. Special thanks to our contributors: @bernardd, @igors, @arkdro

Our contributions to the ecosystem

We have again contributed to XMPPFramework for iOS, Smack for Android, and Movim.

Many thanks to Florian Schmaus (Smack @Flowdalic), Chris Ballinger (XMPPFramework @chrisballinger), and Timothée Jaussoin (Movim @edhelas) for the strong support in our contributions with their kindness, availability, openness, and guidance.

Call for tests and comments: REST API, PubSub, MUC light

This 2.0.0beta2 will be our last beta before 2.0.0, so this is a call for tests, all comments welcome. We encourage everyone to play with the REST API on both backend and clients, but also MongooseIM 2.0.0beta1’s PubSub and MUC light.

Upcoming 2.0.0

With all your feedback, we will release the full, final 2.0.0 in the coming weeks, roughly with the same featureset, with the obvious additional bugfixes and polishing.

This is the last beta before the 2.0.0 version that will be available in the coming weeks. Please feel free to download MongooseIM 2.0.0beta2 and test it extensively. Some rough edges have already been fixed, and we encourage to report any further inconvenience.

We will not add any big new feature before our 2.1.x series, as after the 2.0.0 release we will enter maintenance mode, with bug fixing and optimisations, with probable 2.0.1 and 2.0.2 versions.

Get the fresh news!

Meanwhile, get our fresh news: subscribe our announcement mailing list.


Observer ssh tunnel

This post is just to document an script that opens an ssh tunnel to connect to a remote Erlang VM via Observer.

First, I want to thank to my friend Rubén Caro for sharing all his knowledge when we worked together on this. You must check his github repos.

This is the script. You can pass it three parameters.

  • The remote node name.
  • The remote user name.
  • The remote server.

For convenience all three parameters are given default values.

It assumes a node@ name convention for the nodes.



# Default values.

function get_params()
    case $# in
	    # OK, all parameters have been given.
	    echo "./ $default_node $default_user $default_server"

    echo "node: $node"
    echo "user: $user"
    echo "server: $server"


function get_ports()
    while read line
	if [ "$port1" = "" ]
	    port1=`echo $line | grep running | cut -d ' ' -f 7`

	if [ "$port2" = "" ]
	    port2=`echo $line | grep $node | cut -d ' ' -f 5`
    done < <(ssh $user@$server $EPMD -names)

function make_tunnel()
    echo $port1, $port2

    ssh -N -L $port1:$server:$port1 -L $port2:$server:$port2 $user@$server

function main()
    # Parameters.

    get_params $1 $2 $3

main $1 $2 $3

Let’s see it working.

The remote node.

$ iex --name uno@ --cookie abc
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10]
[hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)

The tunnel.

$ ./ uno jmimora
node: uno
user: jmimora
4369, 50864

In the local iex we will ping the remote VM.

$ iex --name dos@ --cookie abc
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10]
[hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
iex(dos@> :'uno@'

Now you can and select your remote node in the nodes menu or start a remote shell.

$ iex --name dos@ --cookie abc
Erlang/OTP 18 [erts-7.3] [source] [64-bit] [smp:4:4] [async-threads:10]
[hipe] [kernel-poll:false] [dtrace]

Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)
User switch command
 --> h
  c [nn]            - connect to job
  i [nn]            - interrupt job
  k [nn]            - kill job
  j                 - list all jobs
  s [shell]         - start local shell
  r [node [shell]]  - start remote shell
  q                 - quit erlang
  ? | h             - this message
 --> r 'uno@' 'Elixir.IEx'
 --> j
   1  {erlang,apply,[#Fun<Elixir.IEx.CLI.1.100888354>,[]]}
   2* {'uno@','Elixir.IEx',start,[]}
 --> c 2
Interactive Elixir (1.2.5) - press Ctrl+C to exit (type h() ENTER for help)

You will have to type the remote password twice in case you don’t use ssh keys.

That’s it.

Have fun.


Copyright © 2016, Planet Erlang. No rights reserved.
Planet Erlang is maintained by Proctor.