Deploying a Rust Web Service on Raspberry Pi 4: Practical Guide with Cloudflare Tunnel

Pudding Entertainment
8 min readJul 17, 2023
Generated by Bing Image Creator

Unlocking the power of your Raspberry Pi 4 and harnessing the potential of Rust for your applications is an exciting endeavor. In this article, we will explore the practical aspects of deploying a Rust application on a local Raspberry Pi 4. But that’s not all — we’ll take it a step further by leveraging Cloudflare tunnel to expose your application securely to the internet. Get ready to dive into the world of Raspberry Pi, Rust, and seamless deployment, as I guide you through the process of setting up your own production-ready environment. Let’s unlock the true potential of your Raspberry Pi 4 and bring your Rust application to life!

Prerequisites

Before we get started, make sure you have a Rust project ready to deploy and a Cloudflare account to facilitate the process. It’s important to note that this article embraces a slightly more traditional approach to deployment, utilizing bash scripts and running binaries as systemd services. While this may be considered ‘old school,’ it offers a solid foundation for understanding the fundamental deployment principles. In future articles, we will delve into a more contemporary approach using containers and Docker, expanding your deployment toolkit.

Raspberry Pi and Building the Binary

In this section, we will focus on preparing your Raspberry Pi and building the necessary binary for your project. It’s important to identify the model of your Raspberry Pi and verify its processor architecture to ensure compatibility. To do this, you can use the following commands:
To determine the model version, run:
cat /proc/cpuinfo
This command will provide information about your Raspberry Pi, including the model. For example:
Model: Raspberry Pi 4 Model B Rev 1.5
Make sure to choose an OS that suits your Raspberry Pi model.

To check the architecture version, execute:
uname -a
This command will display the architecture details of your Raspberry Pi. Look for the architecture field, which could be either armv7l (x32 processor) or ARMv8 or aarch64 (x64). For instance:
Linux backend 5.15.0–1030-raspi #32-Ubuntu SMP PREEMPT Tue May 23 12:11:51 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux

Next, you need to ensure that your Rust project is built for the appropriate architecture. Use the command rustup target list to see the list of supported architectures. Based on your Raspberry Pi’s architecture, you can add the corresponding target:

  • For x64 (aarch64), add aarch64-unknown-linux-gnu
  • For x32 (armv7l), add armv7-unknown-linux-gnueabihf

By adding the appropriate target, you ensure that the binaries will be built specifically for your Raspberry Pi’s architecture. This step is crucial for successful deployment and execution.

Note! Depending on the dependencies used in your application, you may encounter cross-compilation issues. One notable example is OpenSSL. However, these challenges can be overcome by leveraging the vendored feature. By vendoring the dependencies, you include them directly in your project’s source code, ensuring that they are compatible with the target architecture of your Raspberry Pi. This approach helps address potential compatibility issues and simplifies the deployment process.

Now that we have identified the correct processor architecture and verified it, we can proceed with building the binary for our Raspberry Pi. To accomplish this, we will use the Cargo tool, which is the package manager and build system for Rust projects.
To build the binary, open a terminal and navigate to your project directory. Then, execute the following commands:

cargo clean
cargo build - target aarch64-unknown-linux-gnu - release

These commands will clean any previously built artefacts and compile the code specifically for the aarch64 architecture, which is suitable for Raspberry Pi. The resulting binary file will be located in the target/aarch64-unknown-linux-gnu/release folder.
With the binary successfully built, we can now proceed to configure the local network.

Local Network Configuration

Let’s begin by configuring a static IP address for our Raspberry Pi to ensure a reliable connection. First step here is to ensure that the IP address range in your internal network is properly set up to avoid any IP conflicts. This step is crucial for maintaining a stable and organized network environment, especially if you plan to add more Raspberry Pis in the future.

To configure the IP address range, you will need to access your router’s admin page and navigate to the LAN DHCP Server settings. The exact location of this configuration may vary depending on your router model, but you should look for an option labelled “IP Pool Starting Address” or similar. Set the value that provides enough space for future Raspberry Pis. For example, you can set it to “192.168.0.5” to allocate the IP addresses from “192.168.0.5” onwards, leaving room for additional devices.
By configuring the IP address range in your internal network, you ensure that each Raspberry Pi is assigned a unique IP address without any conflicts. This will facilitate seamless communication and prevent any network-related issues.

Now that we have set up the IP address range on your router, let’s proceed to configure the static IP address for the Raspberry Pi.
The exact instructions may vary depending on the operating system. In this tutorial, we will focus on Ubuntu 22 and use netplan for network configuration.
To begin, navigate to the /etc/netplan folder and create a new file named 01-netcfg.yaml. Open the file and add the following lines to it:

network:
version: 2
renderer: networkd
ethernets:
eth0:
dhcp4: no
addresses:
- 192.168.0.2/24 <-- enter desired IP address
routes:
- to: default
via: 192.168.0.1 <-- ensure the correct getaway
nameservers:
addresses:
- 1.1.1.1
- 8.8.8.8

Once you have saved the file, apply the network configuration changes by running the following command:
sudo netplan apply
This will apply the new network configuration and assign the static IP address to your Raspberry Pi.

To establish a connection between your local machine and the Raspberry Pi, we will set up a simple SSH connection using RSA keys. This allows for secure and password-less access to the Raspberry Pi.
Since this tutorial is geared towards solo developers running Raspberry Pis at home, I will simplify the connection setup process. Here’s how you can establish the SSH connection:
1. Generate a new RSA key certificate by running the command ssh-keygen on your local machine. This will create a new key pair in the ~/.ssh/ directory.
2. Copy the RSA key to the Raspberry Pi by running the command ssh-copy-id -i ~/.ssh/id_rsa username@192.168.0.2. Replace username with your actual username and 192.168.0.2 with the IP address of your Raspberry Pi.
3. Once the key has been copied, perform a quick connection test by running the command ssh -i ~/.ssh/id_rsa username@192.168.0.2. This should allow you to log in to the Raspberry Pi without being prompted for a password.

Note! While we have established a basic SSH connection, there are additional security best practices that you can implement on the Raspberry Pi side. These include setting up a firewall and limiting SSH access for the root user. While these topics are beyond the scope of this tutorial, I encourage you to explore them on your own to enhance the security of your Raspberry Pi and local network setup.

By setting up the SSH connection, you now have a secure and convenient method to access and manage your Raspberry Pi from your local machine. This will be particularly useful as we proceed with deploying the Rust application on the Raspberry Pi in the next section.

Application as Systemd Service

Next, we will explore how to run our application as a systemd service. This will ensure that our application runs automatically on system startup and can be easily managed as a background service.
To begin, it’s recommended to create a dedicated user to run the binary instead of using the root user. This helps mitigate potential security risks. You can create a user specifically for running the application by executing the following command:
adduser myuser

Next, we need to create the systemd configuration for our application. This configuration file will define how the service is managed by systemd. You can create a file named project-name.service and populate it with following:

[Unit]
Description=Project Name Backend
After=network.target

[Service]
User=myuser
Group=myuser
Restart=on-failure
ExecStart=/opt/backend/project-name
WorkingDirectory=/opt/backend
StandardOutput=append:/var/log/project-name/backend.log
StandardError=append:/var/log/project-name/backend.log

[Install]
WantedBy=multi-user.target

Additionally, to enable automatic restart of the service when the binary changes on the disk, we will set up a “watcher” service and a “watcher” path. These components will monitor the binary file and trigger a restart when changes are detected:

project-name-watcher.service

[Unit]
Description=Project Name restarter - to reload the service when something changes on disk
After=network.target
StartLimitIntervalSec=60
StartLimitBurst=20

[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart project-name.service

[Install]
WantedBy=multi-user.target

project-name-watcher.path

[Path]
Unit=project-name-watcher.service
PathModified=/opt/backend

[Install]
WantedBy=multi-user.target

Once the systemd service and watcher components are configured, we can start them using the following commands:

systemctl start project-name.service
systemctl start project-name-watcher.service

Finally, we need to enhance our release script to copy the freshly built binary to the Raspberry Pi. Before we proceed, let’s simplify the process by configuring the IP address in the /etc/hosts file instead of hardcoding it into the script. This approach will make it easier to transition to a non-local environment in the future. You can add the following line to the /etc/hosts file:
192.168.0.2 backend

This configuration maps the IP address to a hostname, allowing us to reference the Raspberry Pi using its hostname instead of the IP address.
Now, let’s modify our release script to include the rsync command. This command will efficiently synchronize the binary file from our local machine to the Raspberry Pi:

cargo clean
cargo build - target aarch64-unknown-linux-gnu - release
rsync -e "ssh -i ~/.ssh/id_rsa" -avO - delete - no-perms target/aarch64-unknown-linux-gnu/release/project_name username@backend:/opt/backend

With these modifications in place, running the release script will copy the newly built binary to the Raspberry Pi, allowing us to deploy and execute our application on the target device.

Cloudflare configuration

Finally, we need to configure a Cloudflare tunnel to make our application accessible over the internet. The official Cloudflare website provides detailed instructions on setting up the tunnel, covering all the necessary steps. For the sake of completeness, let’s briefly run through the installation process here.

  1. Download and install https://pkg.cloudflare.com/index.html
  2. Run cloudflared tunnel login. It will print out the following:
Please open the following URL and log in with your Cloudflare account:
https://dash.cloudflare.com/argotunnel?aud=&callback=<url>
Leave cloudflared running to download the cert automatically.
You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
/root/.cloudflared/cert.pem

3. Run cloudflared tunnel create <tunnel_name>

Tunnel credentials written to /root/.cloudflared/<id>.json. cloudflared chose this file based on where your origin certificate was found. Keep this file secret. To revoke these credentials, delete the tunnel.
Created tunnel <tunnel_name> with id <id>

You can also check it by running cloudflared tunnel list

4. Modify the configuration file and setup the url nano ~/.cloudflared/config.yaml

url: http://localhost:80
tunnel: <id>
credentials-file: /root/.cloudflared/<id>.json

5. Start routing the traffic cloudflared tunnel route dns <tunnel_name> website.com

6. Configure cloudflared as a service with this instructions

If all the steps are performed correctly you should be able to access your website from outside of your local network now.

Afterwards

Deploying a Rust application on a Raspberry Pi can be an exciting endeavor, combining the power and versatility of Rust with the accessibility of a small, affordable computing device. Whether you’re building a personal project or experimenting with IoT applications, this tutorial provided a foundation for deploying and running your Rust application on a local Raspberry Pi.

The application that is being deployed this way can be found at this GitHub repository.

Also, make sure to check out the website itself — https://illuvi-analytics.com, which is being proudly served from my Raspberry Pi 4.

Remember to keep exploring and expanding your knowledge, applying security best practices, and adapting your deployment strategies as new tools and techniques emerge in the ever-evolving landscape of software development. Happy deploying!

Special thanks to my dearest friend François Guerraz for valuable inputs.

Support

If you like the content you read and want to support the author — thank you very much!

Here is my Ethereum wallet for tips:

0xB34C2BcE674104a7ca1ECEbF76d21fE1099132F0

--

--

Pudding Entertainment

Serious software engineer with everlasting passion for GameDev. Dreaming of next big project. https://pudding.pro