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:
- https://webmin.com/
- https://youtu.be/szW1fJ-tf5Y
- https://cockpit-project.org/
- https://youtu.be/L9fMWCRcqIE
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:
- https://github.com/IgnorantGuru/spacefm/wiki/Debian
- https://installati.one/install-udevil-debian-11/?expand_article=1
- https://packages.debian.org/sid/smartmontools
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:
- https://docs.docker.com/engine/install/debian/
- https://stackoverflow.com/questions/48957195/how-to-fix-docker-got-permission-denied-issue
- https://github.com/veggiemonk/awesome-docker/blob/master/README.md#terminal
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 = pi
Code language: PHP (php)
The last thing to do is to restart the samba server:
sudo systemctl restart smbd
More info:
- https://magpi.raspberrypi.com/articles/samba-file-server
- https://youtu.be/n9RjTg0aO0k
- https://superuser.com/questions/1409059/samba-change-to-user-internal-error-when-accessing-the-share
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 UUIDsudo 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:
- https://peppe8o.com/automount-usb-storage-with-raspberry-pi-os-lite-fstab-and-autofs/
- https://askubuntu.com/questions/694707/how-to-mount-a-sambashare-with-autofs-and-cifs
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: true
Code language: JavaScript (javascript)
More info:
- https://dockge.kuma.pet/
- https://youtu.be/AWAlOQeNpgU
- https://youtu.be/E805XcbTzgY
- https://www.portainer.io/
- https://yacht.sh/
- https://casaos.io/
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: true
Code language: PHP (php)
More info:
- https://homarr.dev/
- https://heimdall.site/
- https://demo.dashy.to/
- https://github.com/pawelmalak/flame
- https://github.com/bastienwirtz/homer
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: true
Code language: JavaScript (javascript)
More info:
- https://www.jenkins.io/
- https://www.digitalocean.com/community/tutorials/how-to-automate-jenkins-setup-with-docker-and-jenkins-configuration-as-code
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.