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 updatesudo apt upgradesudo 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-upgradessudo dpkg-reconfigure -plow unattended-upgradessudo systemctl enable unattended-upgradessudo 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.shchmod +x ./setup-repos.sh./setup-repos.shsudo apt-get install webmin --install-recommendsrm setup-repos.shapt-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 dockersudo 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 | bashchmod +x ~/.local/bin/lazydockersudo mv ~/.local/bin/lazydocker /usr/local/binrm -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-binsudo mkdir -m 1777 /sharesudo 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:
- 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 autofssudo 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/dockgecd /home/pi/docker-services/dockgetouch compose.ymlnano compose.ymldocker 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:
- 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/homarrcd /home/pi/docker-services/homarrtouch compose.ymlnano compose.ymldocker 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:
- 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:
To help with malware and ads, we can also use public DNS:
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:
- 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.

