Setting up my new Raspberry Pi

I think the Raspberry Pi’s are a guilty pleasure for many developers. We will use docker and docker-compose to begin setting the Pi up.

Conceptually, we will split the software we intend to install in two major groups:

  • Applications, which are to be installed on the Pi and are meant to be a base for everything and help us run services easy;
  • Services, which are the software which actually solves what we want from our Pi.

The first step is to setup the Raspberry Pi’s operating system. We intend to use this as a server, so we don’t care to have a desktop environment installed. A Debian server version from the list of default options will do just fine. And the main user of the system will be pi, of course 🙂

More info:

Applications

At the first login, we need to update the packages and install git, as it’s one of the most used tools for a developer:

  • sudo apt update
  • sudo apt upgrade
  • sudo apt install git

As far as security goes, at the moment we will keep the Pi inside the local network, we will not do much at the moment. The main thing will be to avoid using the root user for local commands or in docker.

Apart from that, we will enable auto updates (we can skip this if we want to setup webmin later):

  • sudo apt install unattended-upgrades
  • sudo dpkg-reconfigure -plow unattended-upgrades
  • sudo systemctl enable unattended-upgrades
  • sudo systemctl start unattended-upgrades

More info:

zsh

We want a pretty interface, so the first thing we want to do is install zsh, which is a more customizable shell, together with antigen.

More information here:

Webmin

Of course I see the usefulness of the command line! However, I am not a die hard fan of doing things the complicated way, thus I prefer to have an interface for managing certain aspects of the Pi.

  • curl -o setup-repos.sh https://raw.githubusercontent.com/webmin/webmin/master/setup-repos.sh
  • chmod +x ./setup-repos.sh
  • ./setup-repos.sh
  • sudo apt-get install webmin --install-recommends
  • rm setup-repos.sh
  • apt-get install libsocket6-perl

Once installed, you will be able to access webmin on port 10000. The first and most important thing to do is to change this port to some other value, by going to Webmin > Webmin Configuration > Ports and Addresses.

An interesting alternative is Cockpit, but I think webmin is the more mature product of the two.

More info:

udevil and smartmontools

With using a Debian system as a base, especially the server version, we might need some help managing the usb drives. Here is where udevil comes in handy.

  • sudo apt install udevil

smartmontools is a handy tool for monitoring the health and performance of your storage devices to ensure data integrity and prevent data loss due to hardware failures.

  • sudo apt install smartmontools

More info:

btop

We are interested in knowing the performance of our Pi. For this we can use the htop utility, or we can install something more custom, like btop:

  • sudo apt install btop

More info:

docker

Docker is the tool at the root of our configuration. We will use it as a base for any service we will run on the Pi. To install it on debian, we will run the following commands:

# Add Docker's official GPG key:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

Code language: PHP (php)

At this point, the next thing to do is actually install docker on the Pi:

  • sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

The last thing to do is to solve the permission issues when you run the docker command without sudo:

  • sudo groupadd docker
  • sudo usermod -aG docker $USER

More information:

lazydocker

A quick way to get a visual representation for the status of your containers, you can use lazydocker in your terminal. For some weird reason, I could not set the DIR variable properly, so I just let the script install where it does by default and then moved and cleaned up:

  • curl https://raw.githubusercontent.com/jesseduffield/lazydocker/master/scripts/install_update_linux.sh | bash
  • chmod +x ~/.local/bin/lazydocker
  • sudo mv ~/.local/bin/lazydocker /usr/local/bin
  • rm -Rf ~/.local

More info:

samba

We will use samba to share files across the network. To install it, run:

  • sudo apt-get install samba samba-common-bin
  • sudo mkdir -m 1777 /share
  • sudo nano /etc/samba/smb.conf

Add these lines in the samba config file:

[share]
Comment = Pi shared folder
Path = /share
Browseable = yes
Writeable = Yes
only guest = no
create mask = 0777
directory mask = 0777
Public = yes
Guest ok = yes
force user = piCode language: PHP (php)

The last thing to do is to restart the samba server:

  • sudo systemctl restart smbd

More info:

autofs

We will have at least one usb drive connected to the Pi, so we will use autofs to mount it. It will generally be always connected, so fstab would have worked as well. But for these few situations when it’s not there, autofs seems like the better choice.

  • sudo apt install autofs
  • sudo blkid #used to show the list of available devices and their UUID
  • sudo nano /etc/auto.master
  • add the following line:
    /home/pi/media /etc/auto.usb --timeout=60 --ghost
  • sudo nano /etc/auto.usb
  • add the following line:
    drivename -fstype=auto,uid=pi,gid=pi,rw UUID="111CCC222DDD"
  • sudo systemctl restart autofs.service

We will use /home/pi/media to mount the device. It’s a yolo approach taken especially because the file will be owned by this user. It helps that we will mainly have only one user on the device. Using /media, a special user and better permissions would be a cleaner approach.

More info:

Services

As far as the services go, the aim is to show the more important ones. The main thing we will do is create a folder for all our services:

  • mkdir ~/docker-services

If done so and considering that our username is pi, the absolute path for this folder will be /home/pi/docker-services. Should we want to keep track of the changes here, we can also create a git repository.

One thing we must pay attention to is using the tag latest in our docker compose configs. If used, it will keep us close to the bleeding edge as far as versions go, but it might lead to compatibility issues with the data we have in our volumes.

Another thing that we already mentioned is that some containers run with elevated privileges. Since we don’t usually use root, we might end up having some permission issues. One fix is to update the owner of the files:

  • sudo chown -R pi:pi /home/pi/docker-services

Dockge

The first thing we want to install is a helper to use when managing the containers we intend to use. Classic choices here are Portainer and Yacht. They are both great and complex tools, which will probably solve anything you might throw at them. A solution with a very-very nice interface is CasaOS, but this seems to bring too much pretty to a developer’s server. 🙂

However, this is a small homeserver, so we don’t need too much fancy stuff in this matter. Thus, the choice for Dockge, which is rather simple and comes with the clear advantage of easily storing the configuration files on your local filesystem.

  • mkdir -p /home/pi/docker-services/dockge
  • cd /home/pi/docker-services/dockge
  • touch compose.yml
  • nano compose.yml
  • docker compose up -d
services:
  dockge:
    image: louislam/dockge:latest
    restart: always
    ports:
      - 5001:5001
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./data:/app/data
      - /home/pi/docker-services:/home/pi/docker-services
    environment:
      - DOCKGE_STACKS_DIR=/home/pi/docker-services
    networks:
      - raspberry
    labels:
      - project=pi-docker-services

networks:
  raspberry:
    external: trueCode language: JavaScript (javascript)

More info:

homarr

When running many services on a home server, it’s nice to have a dashboard to see all of them, instead of remembering all the ports and addresses. My choice for this is homarr, as it seems to be quite a mature project. Some alternatives are Heimdall, Dashy, Flame, Homer.

To install, run:

  • mdkir -p /home/pi/docker-services/homarr
  • cd /home/pi/docker-services/homarr
  • touch compose.yml
  • nano compose.yml
  • docker compose up -d

The content of compose YAML is:

services:
  homarr:
    container_name: homarr
    image: ghcr.io/ajnart/homarr:latest
    restart: unless-stopped
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
      - ./configs:/app/data/configs
      - ./icons:/app/public/icons
      - ./data:/data
    ports:
      - 80:7575
    networks:
      - raspberry
    labels:
      - project=pi-docker-services

networks:
  raspberry:
    external: trueCode language: PHP (php)

More info:

Pi Hole

To protect ourselves from unwanted ads, we can use Pi Hole as a DNS provider, to block the ads at that level as well.

services:
  pihole:
    image: pihole/pihole:latest
    container_name: pihole
    restart: unless-stopped
    environment:
      TZ: Europe/Bucharest
      WEBPASSWORD: pi
    ports:
      - 53:53/tcp
      - 53:53/udp
      - 67:67/udp
      - 88:80/tcp
    volumes:
      - ./etc-pihole:/etc/pihole
      - ./etc-dnsmasq.d:/etc/dnsmasq.d
    networks:
      - raspberry
    labels:
      - project=pi-docker-services

networks:
  raspberry:
    external: true
Code language: JavaScript (javascript)

To enhance the default list, we can look in Github (https://github.com/topics/pi-hole?o=desc&s=stars) to find popular repositories managing lists of ads providers. The ones I have used are at:

DuckDNS

DuckDNS provides free and quick updates on hosts for dynamic ip addresses. In other words, if you don’t have a static IP address, then this service will offer that to you in a simple and free of charge manner.

services:
  duckdns:
    container_name: duckdns
    image: lscr.io/linuxserver/duckdns:latest
    restart: unless-stopped
    environment:
      - PUID=1000 #optional
      - PGID=1000 #optional
      - TZ=Etc/UTC #optional
      - SUBDOMAINS=subdomain1,subdomain2
      - TOKEN=token
      - UPDATE_IP=ipv4 #optional
      - LOG_FILE=false #optional
    volumes:
      - ./config:/config #optional
    labels:
      - project=pi-docker-services
Code language: PHP (php)

More info:

Jenkins

Jenkins is an open source very flexible task automation solution.

services:
  jenkins:
    image: jenkins/jenkins:2.443-jdk17
    container_name: jenkins
    user: root
    privileged: true
    restart: unless-stopped
    ports:
      - 8082:8080
      - 50000:50000
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - ./jenkins-home:/var/jenkins_home
    networks:
      - raspberry
    labels:
      - project=pi-docker-services

networks:
  raspberry:
    external: trueCode language: JavaScript (javascript)

More info:

Nx for a better structure

The current implementation is fine, but it feels that it’s missing some sense and structure. We will try to provide that by using a monorepo structure with Nx.

  • npx nx@latest init

The new fie structure will be:

.data/
.nx/
services/
|-- pihole
| |-- .gitignore
| |-- docker-compose.yml
| |-- README.md
|-- jenkins
| |-- docker-compose.yml
| |-- ...
| ...
scripts/
|-- setup.sh
|-- run.sh
|-- ...
.env
.env.example
.gitignore
nx
nx.bat
nx.json
README.md
...

The downside with nx is that node is necessary to run it, as it’s based on javascript. However, you can use such a structure without nx itself.