Skip to content
Latent Bytes
Go back

Building a Web Farm with Docker and Raspberry Pi

Updated:
Edit page

WARNING

This article was originally published in 2019. As a result, versions of software that are referenced, as well as information about ARM support that is written in this article, is largely outdated.

Raspberry Pi cluster

Table of Contents

Open Table of Contents

Components of a Web Farm

A web farm is a configuration of two or more servers running on a network to serve the same web application. Incoming web requests are routed to a load balancer, which forwards each request to one of the servers serving the web application.

Web Farm Structure

The benefit of this approach is that adding more nodes to the network is trivial, and therefore the request-handling capacity of the network can be scaled appropriately. It also allows you to update the application without taking it offline, as each node can be individually upgraded.

In this article, I will explain how to set up and deploy an ASP.NET Core application on a web farm. To do this, I will use Nginx as the load balancer and Kestrel as the web server. This setup is extremely lightweight and performant, and both pieces of software are cross-platform, meaning this guide applies to macOS, Linux, and Windows. You can swap out components wherever you wish — for example, you could replicate this setup with a Node or Flask app.

Before starting, you should have a basic understanding of Linux, Git, and Docker. You’ll also need a web application ready to deploy.

Choosing Hardware

Raspberry Pi Boards

As per the definition of a web farm, we are going to be needing a minimum of two computers available for running our web application. Depending on how heavy a web application you want to run and your budget will depend on what model(s) of Raspberry Pi you require. When writing this article, I tested the setup on a cluster of Raspberry Pi model 4B.

For the most part, you will not need a lot of RAM to run a web application, unless you are heavily utilising caching. CPU power is a factor when it comes to how quickly you want a request to be handled, so generally a CPU with a higher clock speed is better. More cores will allow more concurrent requests and so more traffic at one time.

Storage

For storage, each Raspberry Pi will need a micro SD card to store the OS and its files. Storage capacity required depends on whether your web application will store data and what type of data it will store. Generally you will want to isolate your persisted storage away from your web farm nodes, so for your nodes you will want cheap, low capacity cards.

Networking

A web farm would prefer to run on ethernet than on WiFi as the network will be one of the biggest factors when it comes to speed. It is possible to run a web farm on WiFi and performance certainly won’t be terrible, however a wired connection will always be faster. Therefore to run several nodes you should have an ethernet switch and cables.

Power

Several ways exist to supply power to Raspberry Pi boards but by far the most convenient when using ethernet is to use PoE (power over ethernet). This can be achieved with a PoE HAT, although do note that you will need an ethernet switch that explicitly supports PoE.

The Hardware Setup

Here is the hardware used in this guide, totalling around £210 (or as cheap as £150 with only 2 Raspberry Pi boards):

Preparing the Raspberry Pis

Operating System Setup

First, flash the operating system onto each SD card. Use Raspbian Lite, which can be written to the card easily using Etcher. Insert your SD card into a PC, run Etcher, select your downloaded OS image and select the drive. The process shouldn’t take more than a minute.

Once your card is flashed, create an empty file named SSH on the root of the drive. This enables SSH access so you can remotely connect to the Raspberry Pi.

New-Item $drive/SSH -type file

The next step is to assemble the hardware: attach the PoE HATs to the boards, insert the SD cards, connect the PoE HATs to the ethernet switch, and connect the ethernet switch to your router. Spacers can be used to stack the boards neatly.

Securing Each Device

To connect to a Raspberry Pi you will need its local IP address and an SSH client such as PuTTY. Find the IP address via your router dashboard and connect via SSH on port 22.

PuTTY Configuration

The default credentials for Raspbian are pi as the username and raspberry as the password.

PuTTY Login

Once connected, run sudo raspi-config to change the password, set a hostname, and access other useful configuration settings.

Raspberry Pi Setup

Then update all software on the Raspberry Pi.

sudo apt update
sudo apt upgrade

Installing Docker

Docker makes deploying your application straightforward. Raspberry Pi boards have ARM processors, so every image needs to be built from a base image that supports ARM/AArch — but support is rapidly increasing.

Run this command to install Docker:

curl -sSL https://get.docker.com/ | sh

Then add your user to the docker group so you can run Docker commands without sudo:

sudo usermod -aG docker $USER

Verify the installation with docker info. Repeat this on each Raspberry Pi board.

Installing Docker Compose

Docker Compose is useful for configuring containers per device. The official image does not support ARM, so install it via pip instead:

sudo apt update
sudo apt install -y python python-pip libffi-dev python-backports.ssl-match-hostname
sudo pip install docker-compose

Installing Git

Git can be used to easily pull configuration files on to each device.

sudo apt-get install git

Persisting Data

Choosing a Storage Mechanism

If your web application requires persisted storage (i.e. it does more than serve static files), you will want to run some form of storage server accessible across the local network — a SQL/NoSQL server or NAS storage, depending on your needs.

Due to limited ARM support, both MySQL and PostgreSQL can bring up issues when using an ORM such as Entity Framework. A good alternative is MariaDB, which has a well-documented image maintained by the LinuxServer community with good Entity Framework support. MongoDB, Redis, and Cassandra also support ARM and have Docker images available.

Running MariaDB with Docker

Create the following Docker Compose file and commit it to a Git repo so you can clone it onto your Raspberry Pi:

version: "3"
services:
  mysqldb:
    image: linuxserver/mariadb
    container_name: mariadb
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - MYSQL_ROOT_PASSWORD=RootPassword
      - MYSQL_DATABASE=DatabaseName
      - MYSQL_USER=DatabaseUser
      - MYSQL_PASSWORD=DatabaseUserPassword
    ports:
      - 3306:3306
    restart: unless-stoppeddocker-compose.yml

The ports configuration maps port 3306 on the Raspberry Pi to port 3306 inside the container. Set the TZ variable to your local time zone, and update the database credentials accordingly.

Set the connection string in your web application to match:

Server=192.168.1.180;Port=3306;Database=DatabaseName;Uid=DatabaseUser;Pwd=DatabaseUserPassword;SslMode=Preferred;

Clone the Docker Compose file onto your Raspberry Pi and run docker-compose up to create and start the container.

Setting up a Load Balancer

LetsEncrypt + Nginx

SSL certificate generation can be automated by Certbot. Since the official Certbot image doesn’t support ARM, use the LinuxServer LetsEncrypt image, which bundles Nginx and Certbot together.

version: "3"
services:
  letsencrypt:
    image: linuxserver/letsencrypt
    container_name: nginx
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Europe/London
      - URL=yourdomain.com
      - SUBDOMAINS=www
      - VALIDATION=http
      - EMAIL=your@emailaddress.com
      - STAGING=false
    volumes:
      - ./yourdomainname.conf:/config/nginx/site-confs/yourdomainname.conf
      - ./nginx.conf:/config/nginx/nginx.conf
    ports:
      - 80:80
      - 443:443
    restart: unless-stoppeddocker-compose.yml

The URL and SUBDOMAINS variables configure the certificate domain. The simplest validation method is HTTP; DNS validation is more comprehensive and supports wildcard certificates.

Nginx configuration is done by mounting .conf files into /config/nginx/site-confs/. Create yourdomainname.conf to define the upstream web servers, redirect HTTP to HTTPS, and proxy traffic:

# list web application server IPs
upstream webservers {
  server 192.168.1.210;
  server 192.168.1.220;
}

# redirect all HTTP traffic to HTTPS
server {
  listen 80;
  server_name yourdomainname.com www.yourdomainname.com;
  return 301 https://$host$request_uri;
}

# route traffic to web application servers
server {
  listen 443 ssl;
  server_name yourdomainname.com www.yourdomainname.com;

  add_header X-Frame-Options "SAMEORIGIN";
  add_header X-Content-Type-Options "nosniff";

  location / {
    proxy_pass http://webservers;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection keep-alive;
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}yourdomainname.conf

What’s Left?

With the Nginx image running and your web servers up, the web farm is configured. The remaining steps are DNS setup and port forwarding on your router. Most home routers have dynamic IP addresses, so you’ll want to configure a dynamic DNS service. Fortunately, the Nginx configuration does not need to change when using dynamic DNS.


Edit page
Share this post on:

Previous Post
Bitcoin's Trojan Horse
Next Post
Continuously Deploy a Static Website with Azure Pipelines