September 3rd 2022

Making the Ultimate Raspberry Pi server setup

You can also watch the video version of this blog (less in-depth)

Introduction

About a year ago, I bought a raspberry pi 4 model b, one of the fastest raspberry pi's, which I plan to use for some of my projects. For a while, I used it to host a basic discord bot. Eventually, I stopped using it that much, and it must have been a faulty model because one day when I tried to boot it, it would not turn on.

After unsuccessfully trying to troubleshoot it for a few months, I contacted the seller and managed to get a new working one. For quite a while, I had wanted to power my raspberry pi from a solar panel so it would be running purely off green energy. A setup like that would also allow 100% uptime as you wouldn't need to worry about power cuts.

I also wanted to make a hardware setup like one I had seen on youtube. I also recently bought some new Arduino components.

Earlier this year, I purchased and installed my first solar panel, so I finally had all the hardware I needed for this setup.

Making the setup

The hardware I'm using

For this setup, I'm using: The raspberry pi 4 model B (obviously), A heatsink/fan case for active cooling, an ethernet cable (not sure what type), a 2TB external hard drive (for extra storage), a USB C power cable, a 5v DC step-down converter (to step down the solar panel voltage), a female USB A pigtail cable, a rechargeable battery (to hold solar power until it is night), a solar charge controller, a 130W solar panel and a bunch of random cables.

A very dusty picture of the hardwareA very dusty picture of the hardware

For the Arduino-powered stats display, I'm using an ELEGOO Mega R3 Controller Board, a 10-segment LED bar graph, a potentiometer, a passive buzzer, an LCD1602 module, a USB B cable, a membrane switch module, a few resistors and a lot of jumper wires.

The Arduino-powered status displayThe Arduino-powered status display

The software I'm using for this setup.

For the software side of this setup, I'm using docker (for running different processes as isolated containers), overclocking (not really software, but I thought I should mention it), log2ram, Portainer, some custom software I wrote in python to control the Arduino powered status display and a bunch of modifications to the settings of the raspberry pi.

Installing raspberry pi OS

When choosing the OS version, I decided on the 32-bit version instead of the 64-bit. I chose to use the 32-bit version of the OS because I've heard that some applications are more optimised for the 32-bit version as it's a lot older. Also, because the 64-bit version is relatively new, it is probably a lot less stable. I chose not to use it for those two reasons. I then enabled ssh, set a username and password in the raspberry pi imager and flashed the OS image onto the USB drive.

Installing raspberry pi OSInstalling raspberry pi OS

The install failed a few times (probably because I was using a USB flash drive rather than a micro SD card), but eventually, it worked correctly, and I managed to boot the raspberry pi.

Troubleshooting VNC server

After I had got the raspberry pi working while plugged into the monitor, I tried to connect to it with VNC on my computer, but it was unusably laggy and slow. I first thought it must have been a problem with the raspberry pi. After searching online, I found this forum post which said that the problem occurred when you connected to the raspberry pi without it connected to a monitor. According to the forum post, one of the drivers was causing this problem. After following the steps in the forum post, I managed to fix the issue.

Fixing VNC serverFixing VNC server

Overclocking the Raspberry Pi

Although it might seem quite complicated, overclocking the raspberry pi is easy. The only "real requirement" is that you have some form of active cooling (such as a fan) so that the raspberry pi doesn't overheat. Fortunately, I already had a heatsink case with two fans. All I needed to do was open a terminal and type the command: "sudo nano /boot/config.txt". Once the file had opened, I added the two lines: "over_voltage=2" and "arm_freq=1500", closed the file and rebooted the raspberry pi. When the raspberry pi rebooted, I opened up chromium, opened a few tabs, then opened the terminal and used the command: "watch -1 vcgencmd measure_clock arm". The command's output was between 1.3 billion and 1.5 billion, which meant I'd overclocked it successfully because the raspberry pi would usually only be at a maximum of 1.2 billion hertz.

Overclocking the Raspberry PiOverclocking the Raspberry Pi

Installing log2ram to stop the boot drive from wearing down

The first proper software that I installed on the raspberry pi was log2ram. Flash memory such as a micro SD card, a USB drive or an SSD will very slowly wear down every time you read or write data until it eventually stops working. Micro SD cards are some of the easiest forms of flash memory to wear down. In raspberry pi OS, one of the main things that wear it down is log files which constantly get written to the micro SD card (in this case, USB flash drive).

Log2ram prevents this by storing the logs in ram instead of the micro SD card. Then once per day or when the raspberry pi is shutting down, it will write the logs all at once to the micro SD card. This greatly helps to minimise the number of times data is written to it.

One disadvantage of log2ram is that if your raspberry pi suddenly loses power (such as in a power cut) you will lose all of your unsaved log files. This shouldn't be too much of a problem for me though, because I'm running my raspberry pi off of a solar panel and a battery so it should never lose power.

I installed it by running the commands in this specific order: "sudo apt install rsync" (not required to install log2ram but recommended). Then "wget https://github.com/azlux/log2ram/archive/master.tar.gz -O log2ram.tar.gz" to download the tar archive, "tar xf log2ram.tar.gz" to extract the files, "cd /home/pi/log2ram-master" to navigate to the extracted directory, "sudo ./install" to run the install script and finally "sudo reboot" to reboot the raspberry pi.

Log2ram can be configured by editing the config file located at: "/etc/log2ram.conf". Although after looking at the available settings to edit, I decided to keep it as it was, as there were no settings I wanted to change.

Setting up a static local IP address

One problem with self-hosting things on your home network is that most routers assign a different local IP address to a device every time it joins the network. This means that if you are hosting a web server, for example, the IP address of the webserver may change and won't be accessible from the same IP address. This can also be a problem when port forwarding if the router uses the device's IP address rather than the MAC address (which doesn't usually change).

Fortunately, this problem is easy to solve because, on most modern routers, you can reserve a specific IP address for a specific device's MAC address. If you have a reserved IP address set, it will ensure that the device it's reserved for will always be assigned that IP address and won't give it to any other device.

The network router connected to the Raspberry PiThe network router connected to the Raspberry Pi

Setting up Docker

One major part of the software side of this setup is docker. Docker is a platform for running containerised applications which helps make sure applications will run on any machine and can be managed and scaled easily. I don't actually know much about docker, so you can read more here. When I started installing and setting up docker for this project, I didn't know or understand anything about it at all, so this project was a great way of learning docker.

I installed docker by running the command "curl -sSL https://get.docker.com | sh" to install docker and then "sudo usermod -aG docker pi" to set up the correct user permissions. Finally, I ran the command: "docker run hello-world" which successfully ran and gave the output of "Hello from Docker!" which means that I had installed docker successfully! :)

Installing Docker on the Raspberry PiInstalling Docker on the Raspberry Pi

Installing and setting up Portainer docker dashboard

When using docker on a server, it's hard to view and manage all of your containers remotely. Docker dashboards solve this issue by allowing you to remotely view detailed statuses of your containers, easily manage them and more! Most docker dashboards run as a web server you can connect to from any device on the network. The static IP address I set previously is helpful because it ensures the docker dashboard is always accessible from the same IP address.

Out of the many docker dashboards available, I decided to go with portainer because it was lightweight (which is especially important for a raspberry pi), popular, well supported for the raspberry pi and had a lot of features. I may use a different and more lightweight dashboard in the future.

Installing portainer on the raspberry pi is simple because Portainer is actually a docker container. Because it's a docker container, I just used the command: "sudo docker pull portainer/portainer-ce:latest" to download the container from Docker Hub (which is where most docker containers are) and then "sudo docker run -d -p 9000:9000 --name=portainer --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data:/data portainer/portainer-ce:latest" to run the container. It will then always run automatically when the raspberry pi turns on.

Once Portainer was running, I opened up the portainer dashboard in my web browser by entering my raspberry pi's IP address followed by port 9000 (the default port portainer runs on). I then set up a username and password to use with portainer to log in.

The Portainer dashboardThe Portainer dashboard

I then opened up the container view, which showed two containers, one being Portainer, which was running, and the other being the "hello-world" container I installed while checking the docker setup. The latter was not currently running, showing that Portainer was probably working correctly.

How I'm powering the raspberry pi from a solar panel

Rather than powering my raspberry pi off of mains/AC, which can be affected by power cuts, and would also be using electricity 24/7, I instead decided I wanted to use a solar panel so my raspberry pi would be running off of 100% green energy.

The Raspberry Pi connected to the solar panel setup via USBThe Raspberry Pi connected to the solar panel setup via USB

The way my solar setup works is the 24v solar panel connects to the 12v solar controller (with a fuse between them). The solar controller is connected to a rechargeable battery (so that the raspberry pi can still be powered at night when there is no power from the solar panel) and a configurable dc step-down converter.

The step-down converter is set to 5.5V, the same voltage as the official raspberry pi power supplies, and turns the 12V output from the controller into 5.5V for the raspberry pi. The step-down converter connects to a pigtailed female USB A cable which you could plug any USB cable into to provide power (including a phone charger!), but in this case, I'm using the USB C cable for powering the raspberry pi. That's a basic overview of how it works.

Arduino-powered status display

A while ago, I saw a Raspberry Pi setup on youtube which had a small screen displaying some of the Raspberry Pi's stats and was in a 3D-printed enclosure. I thought it was a really cool setup and wanted to make my own version. When I finally had a working raspberry pi and a bunch of Arduino components, I finally decided to make it.

The Arduino-powered status displayThe Arduino-powered status display

Some features that I created for the Arduino circuit were: an LCD screen that displayed different stats such as CPU temperature and power draw, a bar graph to show how much storage is left on the hard drive, and a keypad that could control parts of the circuit and send commands to the Raspberry Pi, and a speaker which would start playing a song if the Raspberry Pi stopped sending data.

The Arduino is connected to the raspberry pi via a USB B cable (an uncommon cable mainly used for printers) and would receive data from the Raspberry Pi over a serial connection. The circuit took around 4 hours of coding in total and building the circuit itself took a while to do because it's very hard to debug especially as I know basically nothing about electronics. I'm probably going to make another blog in the future with a much more in-depth explanation of how I made it because it would take ages to explain how the whole circuit works here in this post.

Creating containerised software to interact with the Arduino

On its own, the Arduino circuit doesn't do much because it's designed to receive all of its data from the Raspberry Pi. So I wrote my own python program that would get a bunch of data, such as CPU temperature, the Raspberry Pi's current power draw and the number of docker containers currently running every 10 seconds and send the data to the Arduino. Unfortunately, when researching how to get the current power draw of the Raspberry Pi, I couldn't find a way to get the current milliamp power draw on the Raspberry Pi. :( (If you know how to do this, grateful if you could let me know!)

Firstly, I made some basic python code that would connect to the Arduino over serial and would get the CPU temperate and the number of docker containers running, every 10 seconds and send that data to the Arduino. In the end, I couldn't work out how to get the data from docker as it was quite confusing to do and I would have also had to get it working while running inside of a docker container which seemed very complicated considering I didn't even know most of the basics.

After I'd made the basic python program, I started researching how to make it run into a docker container. After looking at a few tutorials, I found that, for a basic docker container, I needed a "Dockerfile" and (optionally) a ".dockerignore" file. A "Dockerfile" is where you set up the environment and run different commands and instructions to set up the container. The ".dockerignore" file is similar to a ".gitignore" file as it is where you define all folders or files to not include in the container.

The Dockerfile used for the programThe Dockerfile used for the program

Once I'd worked out how to set up the python environment in the container, I started trying to work out how to build the container. I first followed this guide to install it on windows. For some reason, I really struggled with building the docker container on windows because I just couldn't manage to get docker desktop to work properly. One of the problems I had was trying to get it to work with the WSL2 backend because I didn't have Windows 10 Pro, I couldn't use Hyper-v to run it (because Microsoft is so money-hungry).

When I followed the steps to set up docker desktop, I couldn't get it working. One of the main problems was that it would keep throwing an error message saying that virtualisation wasn't enabled (even though it was), which is why it took me so long to get it working. I think it wasn't working because I hadn't upgraded my WSL distro to WSL 2 properly but didn't realise it because of the confusing error message. But eventually, I did get it working correctly.

To build the container for the raspberry pi, I ran the command: "docker build -t redstarbird/raspberry-pi-arduino-status --platform linux/arm". The first time I tried to build the image, I forgot to include the "--platform linux/arm" option so it didn't build it for the arm architecture that the Raspberry Pi uses so it couldn't run the container. Once the container was built, I uploaded it to docker hub using the docker desktop GUI. I then ran the container on the raspberry pi.

Part of the Python code for the status display Docker containerPart of the Python code for the status display Docker container

My next challenge was that I couldn't access any serial devices (such as the Arduino) from inside the container. After spending a while searching online, I found that if I started the docker container with the command: "docker run -v /dev:/dev --privileged -d --tty redstarbird/raspberry-pi-arduino-status", it would work. That fixes the problem because it sets the "/dev" directory as a volume in the container meaning that the real "/dev" directory (where the file descriptors for USB devices are) so the python script could access them. After I had made this change, the circuit started working and the screen started showing the correct value for the temperature! :)

Setting up the 2TB hard drive

The final thing I did for this setup, was set up a 2TB hard drive, which can be used for high storage applications such as a database or a large git server perhaps. Because the hard drive is an external one, all I needed to do was just plug the USB cable into the Raspberry Pi. Although I think I might be supposed to format the hard drive to a specific file system I'm not too sure but it doesn't seem to be a problem currently.

Conclusion

Ways this setup could be improved

Obviously, this setup isn't perfect because this was just a simple project, I didn't have much budget to spend on it and some of the software I used was for the first time, like Docker.

So, some ways this setup could be improved, in no particular order, are:

What's next?

This project was very fun but there are still a lot of ideas I have for this Raspberry Pi setup. Since I made the video for this set-up, I have improved the security by setting up SSH key authentication.

Setting up SSH keys on the Raspberry Pi