Home webserver setup on a Raspberry pi, using balena and Nginx
Tags: nginx, balena, raspberrypi, webserver
Published: 2020-04-26 16:22:27

The post was originally posted on my [blog](https://odyslam.me/blog/balena-nginx-rpi/), which is actually hosted on this setup, at my home.

# Introduction

In this post, we will be using a spare Raspberry pi 4 to host our very own *website* using the internet connection of our house.

We will start with some introductory terms to get a lay of the land and then we will continue with the tutorial itself. 

If you are familiar with the relevant terms (*IP, Domain Name*, etc.), go ahead and jump to [Let's get to it](#lets-get-to-it).

# Table of Contents

- [Introduction](#introduction)
- [Table of Contents](#table-of-contents)
  - [Static Website](#static-website)
  - [Webserver](#webserver)
  - [Blogging software - Jekyll](#blogging-software---jekyll)
  - [Domain](#domain)
    - [What's an IP](#whats-an-ip)
    - [But what it has to do with domains?](#but-what-it-has-to-do-with-domains)
    - [Dynamic IPs aka "The Plot Thickens"](#dynamic-ips-aka-%22the-plot-thickens%22)
    - [Wait a minute, I don't have a domain name](#wait-a-minute-i-dont-have-a-domain-name)
  - [balena.io](#balenaio)
    - [So what's the deal, exactly?](#so-whats-the-deal-exactly)
  - [Complimentary Software:](#complimentary-software)
    - [Certbot](#certbot)
    - [Netdata](#netdata)
  - [Architecture](#architecture)
    - [Components](#components)
- [Let's get to it](#lets-get-to-it)
  - [Provisioning the Device](#provisioning-the-device)
  - [Installing the Software](#installing-the-software)
    - [Jekyll](#jekyll)
    - [Installing nginx-in-balena](#installing-nginx-in-balena)
  - [Configuring the Software](#configuring-the-software)
    - [Environment Variables](#environment-variables)
    - [nginx configuration](#nginx-configuration)
    - [ddiclient configuration](#ddiclient-configuration)
  - [Configuring the environment](#configuring-the-environment)
    - [Static IP](#static-ip)
    - [Port Forwarding](#port-forwarding)
  - [Deploy](#deploy)
    - [Deploy the project to the device](#deploy-the-project-to-the-device)
    - [Generating the SSL certificate](#generating-the-ssl-certificate)
    - [Updating your certificates](#updating-your-certificates)
    - [Push new content to the website](#push-new-content-to-the-website)
    - [Push new content to the website - For advanced users](#push-new-content-to-the-website---for-advanced-users)
- [Comments](#comments)

## Static Website 

  

So, this idea came to me when I was considering alternatives for a new blog I wanted to start. Up to this point, I used Github pages which hosts for free  any *static* website that belonged to an organization, a project or a person.

  

For those who are not familiar with web programming, as we read from Wikipedia:

> A static web page (sometimes called a flat page or a stationary page) is a web page that is delivered to the user's web browser exactly as stored, in contrast to dynamic web pages which are generated by a web application.

  

So, the website is not supported by a back-end web application, but it's only a set of `.html`, `.css`, `.js` files that the server sends to the browser for the user to view the website. Wordpress sites, for example, **are not** static, since it's supported by a `PHP` server, a `SQL` database and various other components.

  

## Webserver 

In our case, to keep things as simple and **as lightweight** as possible, we will be serving a static website using a static webserver, **Nginx**. It is one of the oldest and most performant webservers, allowing us to serve up to 1000 users without our Raspberry breaking a sweat.

  

Nginx is a very robust web server, allowing the user to perform a myriad of different uses, from serving a static website to performing inverse proxy. Its configuration is very straightforward (as we will see below) and as we will find out soon enough and in essence we will simply dictate the server to serve a set of static files (our website), each time there is a connection at a specific port. ([What is a computer port](https://www.tutorialspoint.com/computer_fundamentals/computer_ports.htm)?)

  

## Blogging software - Jekyll

  

As we are *lazy*, we don't want to write a blog website from scratch, as it would entail considerable overhead for each new post we want to make. What we want, is a framework that will have a certain *theme* and which will generate the static files of the blog **for us**, allowing us to focus solely on the content of the blog.

  

Luckily for us, there is a very easy-to-use framework, called **Jekyll**. It was created by Github's co-founder Tom Preston-Werner. As we read from the project's [repository](https://github.com/jekyll/jekyll) `readme.md`:

  

>Jekyll is a simple, blog-aware, static site generator perfect for personal, project, or organization sites. Think of it like a file-based CMS, without all the complexity. Jekyll takes your content, renders Markdown and Liquid templates, and spits out a complete, static website ready to be served by Apache, Nginx or another web server. Jekyll is the engine behind [GitHub Pages](https://pages.github.com/), which you can use to host sites right from your GitHub repositories.

  

The power of Jekyll is that it is super easy to use, so easy, that you don't even need programming knowledge (Verified from personal experience). In essence, you configure a Jekyll `theme` using a central configuration file and then you write the blog posts in `markdown` format (More on markdown [here](https://www.markdownguide.org/getting-started/)).

  

## Domain

Now that we have the core pieces of the website, it is time to think about the `domain name` and the *potential* issue of `dynamic IP`.

  

If this doesn't sound familiar, let's spend a minute for a computer science 101 super-mini-course.

  

### What's an IP

Each computer that is connected to a network is identified by a unique address, or `IP`, very much like your home address. The Internet is a global network of computers, thus each server has an `IP`.

  

As your home router is connected to the internet, it has it's own `IP` address, which you can find using a service like [whatsmyipaddress](https://whatismyipaddress.com/). At the same time, the router creates a local network in which all your computers at home are connected, thus each computer has a `local IP` and all your computers, since they are connected to the Internet **through the router**, will have the same global `IP`, that of the router.

### But what it has to do with domains?

  

Because it is hard for a person to remember an `IP`, there are services that have **Huge** registries, in which an `IP` is tied to a human-understandable **word**, or **Domain**. When you pay for a `domain name`, you pay to register your `IP` to these registries and tie that `IP` to the `domain name` that you have bought.

  

Now, each time someone enters that domain name, the computer automatically connects to several Domain Name Registries searching for an `IP` that is tied to that specific `domain name`. When it is found, the browser connects to the server using the `IP` and loads the content.
  

The problem arises when our IP is not `static` but it changes continuously, in other words, it's `dynamic`.

  

### Dynamic IPs aka "The Plot Thickens"

  

Many ISPs (Internet Service Providers) around the world offer a `dynamic IP`, meaning that the `IP` doesn't stay the same but changes now and then, according to the policies of each ISP. This creates a challenge, as the domain name will have to point to a new `IP` each time our `IP` changes.

  

Luckily for us, most `domain name` providers offer a service called **Dynamic DNS**. This service allows the customer to use their API to update the `IP` to which the `domain name` must point to. We will be using a small program called [ddclient](https://ddclient.net/) which supports most of the known `domain name` providers.

  

### Wait a minute, I don't have a domain name

If you don't have a domain, go ahead and grab one from one of the major Domain Name providers (just google `buy domain name`). Make sure that the Domain Name provider that you choose supports `Dynamic DNS` and `ddclient`. This `guide` was tested using `Namecheap`.

  

## balena.io

At this point, it is apparent that we will be needing to install and configure a bunch of software not only to bootstrap the website but to also keep it up to date. To do that, we will be using [balena.io](https://balena.io) to *develop* and *deploy* our software to the Raspberry pi with the ease and speed of using a *cloud-service* provider.

  

That's right, using balena to *provision* and *manage* our embedded IoT device, we will be having the same tools and workflows that one would expect from *AWS*.

  

### So what's the deal, exactly?

  

The team behind balena.io were the first people to port docker to the Raspberry family, showcasing how the `container` visualization paradigm could serve the domain of the Internet of Things.

  

Balena now offers a full feature-set that enables us to manage-literally thousands- devices, such as a Raspberry pi, as easily as ever

  

We will develop our application as a `multi-container` application, meaning that the distinct services from which the project is constructed will run as distinct containers, completely isolated one from another.

  

> You can think the ~~docker engine~~ balena-engine (our optimized for the IoT version of docker) as an **oven** where you can bake both a *fish* and a *cake* at the same time, while each will taste and smell just fine when you take them out.

  

In the same sense, each service can run independently, without having to worry with incompatible libraries or different versions. **They wil**l ~~taste~~ **work just fine.**

  

Finally, balena allows us to 
1. easily access the device's logs
2. `ssh` into the host OS or one of the containers
3. `push` a new release by simply running a command.

  

This last part is pure **black magic**.

  

![](https://media.giphy.com/media/AisOYaOZdrS1i/giphy.gif)

  

You simply define your application in a `docker-compose.yaml`, you define a couple of `Dockerfiles` and then you just push your project. **balena** takes care of building the project specifically for your device on its build-servers and then it simply sends the built project to the device. The smart `supervisor` is responsible for downloading and setting up your application according to the `docker-compose` file.

  

Developing and managing IoT devices have **never** been so easy and *beautiful*. Here is a sneak peek of the dashboard for our device:

  

![](https://i.imgur.com/Mp9pB7M.png)

  


**Disclaimer**: I work at balena.io in the product team.  Thus, you *could* say that I am a bit biased.


## Complimentary Software:

  

### Certbot

  

`Certbot` is a service offered by [letsencrypt](https://letsencrypt.org/), a nonprofit Certificate Authority providing TLS certificates for anyone who may ask. This way, users will be able to connect securely on our website, using `https`.

  

We will be using the certbot-CLI program to request a certificate for our website. In essence, certbot will place a special file for our webserver to serve. When the authority tests the website, it will find the specific file and verify that the website (and thus the domain) is indeed ours. **Giving us a certificate for 90 days**.

  

### Netdata

  

`Netdata` is a monitoring agent that runs on the device, aggregates, visualizes and presents various data about the operation of the machine. From fairly simple, such as `RAM` usage, to more complicated such as `CPU interrupts`. Moreover, it has collectors for specific apps that can auto-detect if it's running and start gathering data.

  

Although Netdata is fairly complex and customizable, we will be using it because:

1. It's super light (about 5% CPU consumption) and thus ideal for the constraint nature of a Raspberry pi 4.

2. It can auto-detect `nginx` and start gathering data using the `stub_page` of the webserver. You can read more about `stub_page` [here](https://easyengine.io/tutorials/nginx/status-page/).

  

## Architecture

  

We will be using the multi-container functionality of the platform, thus the project will consist of several different containers, each one running a specific component. This architecture enables us to isolate one component from another, facilitating the management and configuration of the application.

  

### Components

  

1. **webserver:** Runs the `nginx` service and the `certbot` for SSL generation

2. **ddclient:** Runs the `ddiclient` service

3. **netdata monitoring:** Runs an instance of [Netdata Monitoring software](https://github.com/netdata/netdata) to overview the load of the server.

  

# Let's get to it

  
## Provisioning the Device

The first order of business would be to provision our device, a Raspberry pi 4.

  

To do that, we need a new account at balena.io and we need to head over to the [Get Started Guide](https://www.balena.io/docs/learn/getting-started/raspberrypi4-64/python/) of the balena platform and finish it. It will prompt you to install a `demo-app` in your device, but that's ok. We need you to get familiar with the platform before we continue.

  

**Disclaimer:** After you finish with the guide, don't turn off the device, we will need it for later. Just leave it be. Ok? Cool.

  

Go ahead, **I'll wait.**

  

![Waiting..](https://media0.giphy.com/media/pFZTlrO0MV6LoWSDXd/giphy.gif?cid=ecf05e47767a6b2830a15c5a45f4bad24ff65678e446b4d5&rid=giphy.gif)

<br>
**One Note**: The first 10 devices on balena.io are for **Free**, so using your new account will be more than enough for this project.

  

Before going forward, we assume that:

1. You have balena-CLI installed

2. You have balena-etcher installed

3. You have logged in balena-CLI

4. You have logged in balena dashboard 

  

  

## Installing the Software

  

###  Jekyll

  

Although this blog post focuses mainly on setting up a balena-powered raspberry pi webserver, we want to give some insight into how to create a website in the first place.

  

1. **Visit** Jekyll's website and follow the [Get started guide](https://jekyllrb.com/docs/).

2. Get yourself familiar with the Jekyll templating engine

3. **Search** for a Jekyll theme that is appealing to you:
    1. [http://jekyllthemes.org/](http://jekyllthemes.org/)
    2. [https://jekyllthemes.io/](https://jekyllthemes.io/)

4. **Download** the theme locally and configure it according to the **theme's** and **Jekyll's documentation**.

5. **Build** the website according to the **theme's documentation**, the `source files` will be placed in a directory called `_site`.

6. Upload the files inside `_site` to a *Github Repository*.

  
<br>
**Disclaimer:** If you haven't used Github again:
<br>
<br>


1) Follow ths [guide](https://help.github.com/en/enterprise/2.13/user/articles/creating-a-new-repository) to create a new `repository` to Github.

2) Follow this [guide](https://help.github.com/en/github/managing-files-in-a-repository/adding-a-file-to-a-repository) to upload all your website's source files to the `repository` you just created. Simply drag and drop all of them as it is shown in the guide.

3) **Congratulations!** You have your very first project on Github!

  

### Installing nginx-in-balena

  

To install the software, we did all the heavy lifting for you. We aggregated all the relevant software and made sure that it can support the Raspberry pi 4 with a balenaOS 64bit. You only have to go download a local copy of the project:

```shell

git clone https://repo

cd repo

```

  

We are now into the project's directory. **Let's configure it!**

  

>Possibly, the same software will run without problems on a Raspberry pi 3 with either 32 or 64 bit OS. If you test it successfully on a Raspberry pi 3, please do leave a comment and we will update the blog-post accordingly.

  

  

## Configuring the Software

  

### Environment Variables

  

1. Go [here](https://github.com/balena-io-playground/balena-nginx) and download the project's repository locally. If you are not familiar with Github, please refer to their [documentation](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository).

2. `cd` into the repository using a terminal program (cmd in windows).

3. Open the file `.balena/balena.yml` using your favorite code editor. If you don't have one, go ahead and install [Visual Studio Code](https://code.visualstudio.com/?wt.mc_id=vscom_downloads)

4. In this file, we need to change a couple of `environment variables` which we make available to the services via their respective `Dockerfiles`. Go ahead and change the variables:

	1. In order to find the `REPO_ZIP_URL`, go to your Github repository, *click* on **clone or download** and then *right-click* on **Download ZIP** and *click* on **Copy Link Address**.

	2. Fill in the domain of your website according to this certbot example. Please note that `www.domain.com` and `domain.com` are two different domains, it is best to include both.

  

```shell
REPO_ZIP_URL=https://github.com/OdysLam/odyslam.github.io/archive/master.zip

REPO_NAME=odyslam.github.io

CERTBOT_MAIL=hi@odyslam.me

CERTBOT_DOMAIN_1=www.example.com

CERTBOT_DOMAIN_2=example.com

```

This environment variables are not expected to change while the server runs, thus we prefer to define them at build time.

On the other hand, there are 2 environment variables that can be set using `balena dashboard` and when the `nginx` container reloads, it will pick them up.


1. **SYNC_WEBSITE:** If this environment variable is set to "1", the container will always download the latest version of the website every time it restarts.

2. **CERTBOT_FORCED:** If this environment variable is set to "1", the container will always request a new certification every time it restarts. If the current certification is still valid, it will simply inform the user that the certification is up-to-date and will exit.


You can read more about `environment variables` in balena, in the [documentation](https://www.balena.io/docs/learn/manage/serv-vars/).
You can read more about `build-time secrets` in balena, in the [documentation](https://www.balena.io/docs/learn/more/masterclasses/cli-masterclass/#81-build-time-secrets).

### nginx configuration

1. Run the commands bellow to generate a private key that will be used by nginx for *SSL* related functionality. As it might take some minutes, go ahead and read about it in this [Stack Overflow Question](https://security.stackexchange.com/questions/94390/whats-the-purpose-of-dh-parameters). Welcome to the world of *cryptography*.

  

```shell

cd nginx

openssl dhparam -out dhparam.pem 2048

cd ..

```

2. Using a text editor, open `nginx.conf` which will find the `nginx` directory. Head over to the following excerpt and replace the `www.example.com` and `example.com` with your domain name.

```conf

server {
listen 443 ssl http2;
server_name www.example.com example.com

```

```
server {

listen 80;
listen [::]:80;
server_name www.example.com example.com;

```

If you want to read more about the `nginx` configuration file and what the various fields mean, you can read more about it here:

1. Digital ocean [article](https://www.digitalocean.com/community/tutorials/understanding-the-nginx-configuration-file-structure-and-configuration-contexts)

2. Nginx [Beginner's Guide](http://nginx.org/en/docs/beginners_guide.html)

### ddiclient configuration

1. Create a configuration file for `ddclient` using a text editor:

   1. You can find examples in the `ddclient`'s [documentation](https://ddclient.net/#configuration)

   2. If you used Namecheap, you can find an example configuration in the [namecheap documentation](https://www.namecheap.com/support/knowledgebase/article.aspx/583/11/how-do-i-configure-ddclient)

2. Place the configuration file you just created into the `ddclient` folder

  

  

## Configuring the environment

  

It's time to move on configuring our environment. In our case we need to allow ingoing connections from the router and make sure that the server will always have a `static ip` in the local network.

  

### Static IP

  

Balena allows us to set our device to static IP in a breeze.

  

1. Find out the `IP form` that the router uses to assign `IP`s.

   1. Go to your balenaCloud dashboard, you can find the IP of your device at the summary page.

    ![](https://i.imgur.com/BgMJUfv.png)

2. Use a text editor to open the file `static-ip` that you will find in the directory `tools` of the repository you downloaded.

3. Replace the field `ROUTER_IP` with the `IP` of the router.

4. Replace the field `DEVICE_IP` with the `IP` of your balena device with a small change. Change the digits after the last `.` to `100`.
5. Close the text editor.

Here is an example, note that here the `IP` has the format `192.168.1.X`

```

[connection]

id=my-ethernet

type=ethernet

interface-name=eth0

permissions=

secondaries=

  

[ethernet]

mac-address-blacklist=

  

[ipv4]

#This is the important line
address1=192.168.1.100/24,192.168.1.1 

dns=8.8.8.8;8.8.4.4;

dns-search=

method=manual

  

[ipv6]

addr-gen-mode=stable-privacy

dns-search=

method=auto

```  

  

### Port Forwarding

  

Since we are building a server, we need to allow people to be able to connect to our Raspberry Pi. Normally a home router will block all ingoing connections (connections that are not initiated from a device inside the local network), thus we need to create a rule and tell the router that each connection that is made to a specific **port** should be **forwarded** to the same **port** of the **Raspberry**.

  

This is because when someone attempts to connect to our **IP**, in essence, he tries to connect to our *router* since it functions as the gateway that stands between the Internet and our local network. Thus we want to tell the *router* that any time someone tries to continue to the ports that we will specify, in essence, he wants to connect to our server, thus the router must forward the connection to the Raspberry pi.

  

In other words, we need to forward **ports** `80` and `443` to the Raspberry pi.

1. **Visit** [portforward](https://portforward.com/)

2. Find your router

3. Follow instructions and forward the ports to the device's IP

  

We are almost there, let's deploy the device.

  

## Deploy

  

Now that we have everything configured, let's start deploying the various components.

  

  

  

### Deploy the project to the device

  

1. Open balenaCloud and create a new application for `Raspberry pi 4`. You can name it whatever you want. We will assume that you name it `bananas`.

2. Download an `development` image for that application. Don't bother filling in your `wifi` credentials, we will be using `ethernet` as `wifi` is not very reliable for a webserver.

    ![](https://media.giphy.com/media/5hc2bkC60heU/giphy.gif)

  

3. Burn the image using the best image format app in the world, **balena-etcher**.

4. Pull out the sd card and re-insert it in the computer. Copy the file named `static-ip` we created earlier into the `system-connections` directory of the sd card.

5. Insert the card into the Raspberry pi and connect it to power.

6. Go to the `balena-nginx` directory, where your `docker-compose.yaml` is located, and push the project

```shell

balena push bananas

```

  

### Generating the SSL certificate

  

To generate the SSL certificate, we don't have to do anything.

  

The server will detect the absence of the certificates and will run the `certbot` service to register your website. Afterward, it will simply start the server and will serve your website.

  

The `certificates` will be saved into a persistent directory with a functionality called `named volumes`. This enables the device to persist your certificates (or any data in that directory)

  

### Updating your certificates

  

You are very organized and want to plan ahead? Sure, cerbot will email you a couple of days before your certificates are invalidated so you can renew.

When you receive that e-mail, you only need to:
  

1. **Log in** to balena dashboard
2. **Go to** device summary
3. **Open ssh session** to the `nginx` container
4. **Type** `certbot renew`
5. **Done**

  
### Push new content to the website

All we have to do in order to push new content to the website is to update the `source files` in the directory from which we have configured `nginx` to serve our website. In our case  `/usr/share/nginx/html/`.ping 

To do that, we have a script inside the `nginx` container, which downloads the latest version of our website from the `GitHub` repository we have defined. 

To run the script:

1. SSH into `nginx` container, preferably using `balena dashboard`
2. ` /update-blog.sh`

But, what happens if we want to add new content to our website? 

We have to upload the new source files into our `GitHub Repository` and then we have to run the `update-blog.sh` script in order to download the new version in the server.

So, in order to push new content to the website:



1. Change the website's source files
2. Upload the new files to the Repository and add a `commit message` to describe the changes
3. From `balena dashboard`, `ssh` into the `nginx` container and run: `/update-blog.sh`



### Push new content to the website - For advanced users



The whole *process* has be **automated**, and you can simply run the script `deploy-dev-loca.sh` from the local directory of the project, like this:

`./deploy-dev-local.sh "<commit message>"`

The field `<commit message>` must be replaced with a commit message for the addition to the `GitHub` repository, just like you did when you uploaded the files using the website of `GitHub`.

Before you can you can use the script, you have to open the file using your favorit text editor and replace the following fields:

```shell
# If you use 
export J_OUTPUT= Absolute path to the directory of the website's source files

export REPO = Absolute path to the directory of the website's source files

export DEV_UUID= UUID of the device, can be found from the device's dashboard
```

# Comments