Getting Started
View SourceIntroduction
Nerves provides tooling and libraries for building software images to run on embedded systems. It uses the rock-solid Erlang virtual machine, and brings the happy development experience of Elixir to your micro computers.
While the Nerves project provides base runtime libraries for hardware access and network configuration, nearly all of the Elixir ecosystem is available.
Nerves uses the Linux kernel to support a large variety of hardware. It is not a Linux distribution, though, and contains little of what you would find on a typical embedded Linux system. Instead, it starts the Erlang runtime as one of the first OS processes and lets Erlang and Elixir take over from there. Not to fear, if you need something from Linux, Nerves provides a way to use most of the packages available through Buildroot.
Nerves Burner
Looking for the fastest way to get started with discovering Nerves? Then we strongly recommend to check out Nerves Burner.
This tool removes the friction of burning your first MicroSD card. Nerves Burner supports:
- Nerves Livebook to run Livebook on your device and play with Elixir in no time
- Circuits Quickstart to learn about controlling leds, and other hardware components with Elixir
- Setting up Wifi so you can easily connect to your device
This is what Nerves Burner looks like:

Nerves + Livebook
A great path to exploring Nerves for the first time is by setting up the Nerves Livebook project. This allows you to try out the Nerves project on real hardware without needing to build a project from scratch.
Within minutes, you'll have a Raspberry Pi or Beaglebone running Livebook on top of Nerves. You'll be able to run code in Livebook and work through Nerves tutorials from the comfort of your browser.
Underjord has put together a fantastic video to help walk-through the entire setup process.
If you'd rather build your own firmware from scratch, make yourself at ease, you're in the right place.
Development environment
Before you start using Nerves, it is important that you follow the instructions from the Installation Guide. It will help you get your machine configured for running Nerves. Come back here when you're done!
Creating a project
Let's get you set up and through your first Hello World moment. If you already have some experience with Nerves, you should skip this section and go straight to the core documentation.
We will start by creating a new Nerves project. The nerves.new project generator can be called
from anywhere and can take either an absolute path or a relative path.
mix nerves.new hello_nerves
Nerves will generate the required files and directory structure for your application. We'll give more details about them in the Anatomy of a Nerves project section.
As described by the project generator, the next step is to change to the project directory, choose a target, and fetch the target-specific dependencies.
What is a target? It is the platform for which your firmware is built (for example, a Raspberry Pi Zero 2W). The firmware is a binary image containing both the Linux operating system we need, as well as your Nerves project. This is what we will build with Nerves and then flash on the target. For the rest of this section, we will assume that you are working with a Raspberry Pi board, but the instructions apply to other targets as well. If you ever get confused about the terms we use in this guide, we've consolidated a list of common terms for you.
In the introduction, we mentioned that Nerves uses Linux as its foundation. But we don't use a pre-existing Linux distribution, instead, we use a build system to compile only what we need, that is what Buildroot is for. It allows us to use just the right amount of Linux software we need to keep our images as small as possible. Don't worry, you don't need to understand how Buildroot works at this point, but in order to be able to continue, you need to know which Nerves System you will need for your target.
The Nerves System is a pre-compiled Linux Operating System, built with Buildroot, on which you will run your application. But to avoid having to compile our Nerves system each time we build a firmware, we leverage pre-compiled Nerves systems. Assuming you are using Nerves for the first time on a Raspberry Pi, this is the list of Nerves systems for each Pi version (Target):
| Target | System | Tag |
|---|---|---|
| Raspberry Pi A+, B, B+ | nerves_system_rpi | rpi |
| Raspberry Pi Zero and Zero W | nerves_system_rpi0 | rpi0 |
| Raspberry Pi 2 | nerves_system_rpi2 | rpi2 |
| Raspberry Pi 3A and Zero 2 W (32 bits) | nerves_system_rpi3a | rpi3a |
| Raspberry Pi 3A and Zero 2 W (64 bits) | nerves_system_rpi0_2 | rpi0_2 |
| Raspberry Pi 3 B, B+ | nerves_system_rpi3 | rpi3 |
| Raspberry Pi 4 | nerves_system_rpi4 | rpi4 |
| Raspberry Pi 5 | nerves_system_rpi5 | rpi5 |
One Nerves System can support multiple Pis
Note that some Pi versions or variations share the same system! For instance, you'll need to use nerves_system_rpi3a for a Raspberry Pi Zero 2W running at 32 bits and nerves_system_rpi0_2 for a Raspberry Pi Zero 2W 64 bits, so look carefully to make sure you pick the right system.
What is my device's MIX_TARGET?
Visit the Supported Targets Page for information on what target name to
use for each of the boards that Nerves supports. The default target is host
unless you specify a different target in your environment. If you are not using a Raspberry Pi to follow this guide, you should go take a look and identify the system you need. What is relevant at this point is what's in the tag column.
Since the Raspberry Pi Zero 2W is the cheapest device you can find that supports Nerves, we will assume that's the target you are using for the rest of this guide. We will use the 64 bits flavour of the system, hence using the tag rpi0_2 throughout this guide.
The target is chosen using a shell environment variable called MIX_TARGET. Do not forget to replace the rpi0_2 in the examples below with the right tag for your target.
MIX_TARGET Pro tip
It is not mandatory, but you can set the MIX_TARGET environment variable once and for all in your current shell using:
export MIX_TARGET=rpi0_2
You will have to do this again if you close your terminal or if you open a new one though.
An often used approach is to have two shell windows open: one for running
commands against your local machine (the host target), and one with the desired MIX_TARGET
variable set.
This allows you quick access to use host-based tooling in the former and
deploy updated firmware from the latter, all without having to modify the
MIX_TARGET variable in your shell.
Let's get all the dependencies that our system needs.
cd hello_nerves
MIX_TARGET=rpi0_2 mix deps.get
You should now have installed all the dependencies required! If you encounter any issues at this point, make sure you've followed the Installation Guide properly. It's time to build our first firmware with:
MIX_TARGET=rpi0_2 mix firmware
After a couple minutes at most, you should see the following message:
Firmware built successfully! 🎉
Now you may install it to a MicroSD card using `mix burn` or upload it
to a device with `mix upload` or `mix firmware.gen.script`+`./upload.sh`.It's time to burn our firmware and try it out on our Raspberry Pi! 🔥
Insert your MicroSD card in your computer and run the following command:
MIX_TARGET=rpi0_2 mix firmware.burn
Warning - This will wipe any existing data on your card
Nerves will replace any existing partition or data on your MicroSD card. Make sure you save any important data you have on it before burning it with your Nerves firmware. You do not need to partition the card before you use it, Nerves takes care of everything for you.
Most MicroSD cards should be suitable, but in case you have issues with your Raspberry Pi booting with it, check if there is not a compatibility issue by reading the SD Cards section of the official documentation and search online for best brands and models for your board.
Nerves should automatically discover the right drive to flash the image and ask you to confirm. If you have more than one device available, Nerves might get confused and fail here. In that case, check the Create a bootable SD card section for more guidance. But here is an example of what you should see:
==> hello_nerves
Nerves environment
MIX_TARGET: rpi0_2
MIX_ENV: dev
Use XX.X GiB memory card found at /dev/sdX? [Yn]Press Y or the Enter key and after a few seconds or minutes, your card will be burnt with your brand new nerves firmware. You can now insert your MicroSD card in your Raspberry Pi!
Before you boot it, we need to choose a way to connect with it once Nerves is launched. We will describe the easiest method (Ethernet over USB) in this guide, but there is more on the Connecting to your Nerves Target page if you want to take a look at it.
Connecting to Nerves via USB
By default, on most systems, Nerves provides an ethernet over USB interface interface. It means that you just need to plug your Pi to your computer with the appropriate USB cable to be able to interact with it. Once it is booted, you will see a new network interface created on your own computer with an IP assigned. If you run into some issues trying to connect with USB, check the USB Data Cable section to help you as it might be related to the cable you are using.
Once it is booted, you can access your Raspberry Pi with the following command:
ssh nerves.local
Be patient though, as it can take 30 seconds or more at first boot. You can run ping nerves.local to know when your Pi is up and running.
The way Nerves does this is by copying your ssh public keys in the firmware and setting all up with Vintage Net Direct, one of the supported Vintage Net configurations.
SSH public keys
Since Nerves copies your SSH public keys in the firmware image, make sure you use the same computer to create the firmware and to connect to the device. Otherwise, you will be met with a login prompt.
I can't reach nerves.local
If for some reason you can't reach nerves.local, check your operating system's network settings. You should see a network interface with an IP address starting with 172.31.. Check the details of that interface and in the DHCP settings, check for the gateway IP address, this is your target's IP and you can ssh to that IP instead of nerves.local.
If you are using an HDMI capable Pi and USB is really not working for you, try to connect it to a screen or a TV and see if it displays the IEx prompt.
Using IEx
Once you are connected to your target device, an IEx prompt will appear with
NervesMOTD.
IEx is your main entry point to interacting with Elixir, your program, and hardware.
ssh nerves.local
Interactive Elixir (1.18.3) - press Ctrl+C to exit (type h() ENTER for help)
████▄▄ ▐███
█▌ ▀▀██▄▄ ▐█
█▌ ▄▄ ▀▀ ▐█ N E R V E S
█▌ ▀▀██▄▄ ▐█
███▌ ▀▀████
hello_nerves 0.2.0 (40705268-3e85-52b6-7c7a-05ffd33a31b8) arm rpi0_2
Uptime : 1 days, 3 hours, 6 minutes and 29 seconds
Clock : 2022-08-11 21:44:09 EDT
Firmware : Valid (B) Applications : 57 started
Memory usage : 87 MB (28%) Part usage : 2 MB (0%)
Hostname : nerves-mn02 Load average : 0.15 0.12 0.14
wlan0 : 10.0.0.25/24, 2601:14d:8602:2a0:ba27:ebff:fecb:222a/64, fe80::ba27:ebff:fecb:222a/64
usb0 : 172.31.36.97/30, fe80::3c43:59ff:fec9:6716/64
Nerves CLI help: https://hexdocs.pm/nerves/iex-with-nerves.html
Toolshed imported. Run h(Toolshed) for more info.
iex(nerves@nerves.local)1>
In the IEx prompt type HelloNerves.hello(), and you should see your first Elixir application output! 🥳
iex> HelloNerves.hello()
:worldThe Toolshed package contains many useful commands. Enter the following command to display the help for the Toolshed package.
iex> h ToolshedGo ahead and try them out to explore your target's runtime environment.
For more info on Nerves-specific use of the IEx prompt, refer to IEx with Nerves Page.
Anatomy of a Nerves project
Now that we have managed to boot our Pi with our own firmware, let's see what a Nerves project actually looks like:
hello_nerves
├── config
├── lib
├── mix.exs
├── README.md
├── test
└── rootfs-overlay
└── etc
└── iex.exsThe mix.exs is where we make the link between our firmware and the Nerves System that is needed for our target.
defmodule HelloNerves.MixProject do
use Mix.Project
@app :hello_nerves
@version "0.1.0"
@all_targets [
:rpi,
:rpi0,
:rpi0_2,
:rpi2,
:rpi3,
:rpi3a,
:rpi4,
:rpi5,
:bbb,
:osd32mp1,
:x86_64,
:grisp2,
:mangopi_mq_pro
]
def project do
[
app: @app,
version: @version,
elixir: "~> 1.17",
archives: [nerves_bootstrap: "~> 1.14"],
start_permanent: Mix.env() == :prod,
deps: deps(),
releases: [{@app, release()}],
preferred_cli_target: [run: :host, test: :host]
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger, :runtime_tools],
mod: {HelloNerves.Application, []}
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# Dependencies for all targets
{:nerves, "~> 1.10", runtime: false},
{:shoehorn, "~> 0.9.1"},
{:ring_logger, "~> 0.11.0"},
{:toolshed, "~> 0.4.0"},
# Allow Nerves.Runtime on host to support development, testing and CI.
# See config/host.exs for usage.
{:nerves_runtime, "~> 0.13.0"},
# Dependencies for all targets except :host
{:nerves_pack, "~> 0.7.1", targets: @all_targets},
# ...
{:nerves_system_rpi, "~> 1.24", runtime: false, targets: :rpi},
{:nerves_system_rpi0, "~> 1.24", runtime: false, targets: :rpi0},
{:nerves_system_rpi0_2, "~> 1.24", runtime: false, targets: :rpi0_2},
{:nerves_system_rpi2, "~> 1.24", runtime: false, targets: :rpi2},
{:nerves_system_rpi3, "~> 1.24", runtime: false, targets: :rpi3},
{:nerves_system_rpi3a, "~> 1.24", runtime: false, targets: :rpi3a},
{:nerves_system_rpi4, "~> 1.24", runtime: false, targets: :rpi4},
{:nerves_system_rpi5, "~> 0.2", runtime: false, targets: :rpi5},
{:nerves_system_bbb, "~> 2.19", runtime: false, targets: :bbb},
{:nerves_system_osd32mp1, "~> 0.15", runtime: false, targets: :osd32mp1},
{:nerves_system_x86_64, "~> 1.24", runtime: false, targets: :x86_64},
{:nerves_system_grisp2, "~> 0.8", runtime: false, targets: :grisp2},
{:nerves_system_mangopi_mq_pro, "~> 0.6", runtime: false, targets: :mangopi_mq_pro}
]
end
def release do
[
overwrite: true,
#...
cookie: "#{@app}_cookie",
include_erts: &Nerves.Release.erts/0,
steps: [&Nerves.Release.init/1, :assemble],
strip_beams: Mix.env() == :prod or [keep: ["Docs"]]
]
end
end
As you can see in the @all_targets global variable and in the deps function, we list all the official Nerves Systems, but only the one selected with MIX_TARGET as explained above will be used when you build your firmware.
Just like any Elixir project, deps is where you can add additional dependencies that you need.
The application function is where you describe your whole application. The :mod key let's you define the module that will be invoked when the application is started. At this point in your Nerves journey, this is the only part that matters, but you're welcome to read more about the application function by running mix help compile.app in your terminal.
The module named HelloNerves.Application is located in the project's lib/hello_nerves directory.
If you have any experience with Elixir, this should feel like home. A Nerves Application is just a good old Elixir OTP application where we implement the Application behaviour. The start/2 callback starts a supervison tree, just like any other Elixir OTP application.
Updating your firmware
Working on a Nerves project, you'll find yourself making changes to your application and wanting to try these changes on your target. However, we don't want to always remove the MicroSD card from the target, which means we will update the firmware over the network. Make sure you can connect to your device with USB before following this section.
Something easy we can change is the hello function in our HelloNerves module located in lib/hello_nerves.ex:
defmodule HelloNerves do
# ...
def hello do
:world
end
endLet's just change :world to :nerves 😉
def hello do
:nerves
endSave the file and rebuild the firmware with:
MIX_TARGET=rpi0_2 mix firmware
Since we already have Nerves running on the target which is connected with a USB cable, we can upload our new firmware over the network. We don't need to run firmware.burn anymore.
MIX_TARGET=rpi0_2 mix upload
It will push your new version of the firmware and reboot the target. Once it is accessible again, run ssh nerves.local. When you get to the IEx prompt, you should see the following when calling the hello function:
iex> HelloNerves.hello()
:nervesCongratulations! 🎊 You've just reached your very own Nerves Hello world moment and have assimilated all the basic concepts you need to go further. Whether you want to Run a phoenix app, play around with your Pi's GPIO, the world is now your oyster. If at any point in your journey you feel stuck, reach out to the Nerves community through our communication channels. Welcome to Nerves!
Example projects
If you are interested in exploring other Nerves codebases and projects, you can check out our collection of example projects.
Be sure to set your MIX_TARGET environment variable appropriately for the
target hardware you have. Visit the Supported Targets Page for more
information on what target name to use for the boards that Nerves supports.
The nerves_examples repository contains several example projects to get you
started. The simplest example is Blinky, known as the "Hello World" of hardware
because all it does is blink an LED indefinitely. If you are ever curious about
project structuring or can't get something running, check out Blinky and run it
on your target to confirm that it works in the simplest case.
git clone https://github.com/nerves-project/nerves_examples
export MIX_TARGET=rpi0_2
cd nerves_examples/blinky
mix do deps.get, firmware, firmware.burn
Common terms
In the following guides, support channels, and forums, you may hear the following terms being used.
| Term | Definition |
|---|---|
| host | The computer on which you are editing source code, compiling, and assembling firmware |
| target | The platform for which your firmware is built (for example, Raspberry Pi Zero W, Raspberry Pi 4, or Beaglebone Black) |
| toolchain | The tools required to build code for the target, such as compilers, linkers, binutils, and C runtime |
| system | A lean Buildroot-based Linux distribution that has been customized and cross-compiled for a particular target |
| firmware bundle | A single file that contains an assembled version of everything needed to burn firmware |
| firmware image | Built from a firmware bundle and contains the partition table, partitions, bootloader, etc. |
Community links
Do not hesitate to seek for help if you feel stuck at any point during your journey with Nerves.
- Elixir Slack #nerves channel
- Elixir Discord #nerves channel
- Nerves Forum
- Nerves Meetup
- Nerves Newsletter
Deploying your firmware
Moved to Using Nerves
Create the firmware bundle
Moved to Using Nerves
Create a bootable SD card
Moved to Using Nerves
Connecting to your device
Moved to Using Nerves