POSTS / 6 min read
The Lazy Dev's Self-hosting Services: One Script To Rule Them-all
Tired of manually restarting your self-hosted services? So was I, here's how I automated the process with a single script. Less work, more uptime.
Published Nov 18, 2024
I’ve always been a fan of self-hosting my services. Not only does it give me complete control over my data, but there’s also something satisfying about setting up and managing services on my own. In my setup, I primarily use Docker containers because they provide a good balance of simplicity and flexibility—though they do come with a trade-off in higher resource consumption.
As my setup grew, managing all my services became a challenge. Each time I needed to restart my machine, I had to go into each service’s folder and manually run commands to bring everything back up. This routine quickly became a time drain, and I realized it wasn’t sustainable.
I needed a way to automatically start each service without manual navigation. So, I designed a simple structure that allows me to start all my services at once with a single script.
To keep things organized, I created a dedicated folder structure for each service. Here’s how it works:
- Each service has its own dedicated folder.
- Inside each folder, I place either a
docker-compose.yml
file (for services using Docker Compose) or arun.sh
script for those that require custom docker run commands with specific parameters.
This structure keeps everything for each service in one place, making my setup cleaner and easier to manage. The run.sh
scripts are used for services where Docker Compose isn’t necessary. With this folder setup, I can automate the startup of all my services with one script that checks each folder for the right file.
So my base directory looks like this
base_directory/
├── ...other scripts
├── miniflux
├── nextcloud
├── linkace
├── privatebin
├── watchtower
├── homepage
├── pihole
├── speedtest
├── psitransfer
├── plant-it
├── wireguard
├── portainer
├── ntfy
├── tracky
├── trilium
└── immich
Here’s the script that ties it all together. It’s simple but has made my workflow much smoother, saving me time.
#!/bin/bash
# Define the list of services to start
SERVICES=(
"miniflux"
"nextcloud"
"linkace"
"privatebin"
"watchtower"
"homepage"
"pihole"
"speedtest"
"psitransfer"
"plant-it"
"wireguard"
"portainer"
"ntfy"
"tracky"
"trilium"
"immich"
)
# Define storage directories
STORAGE_DIR="/media/user/SSD";
BACKUP_DIR="/media/user/HDD_2";
# Function to mount directories if not already mounted
mount_dir () {
while true; do
read -p "Do you want to mount the $STORAGE_DIR and the $BACKUP_DIR? " yn
case $yn in
[Yy]* )
udisksctl mount -b /dev/sdc1;
udisksctl mount -b /dev/sdb1;
break;;
[Nn]* ) exit 1;
break;;
* ) echo "Please answer yes or no.";;
esac
done;
}
# Check if STORAGE_DIR is mounted, and mount if not
if [ ! -d "$STORAGE_DIR" ]; then
echo "ERROR: storage directory $STORAGE_DIR does not exist";
mount_dir;
fi
# Optional container cleanup prompt
while true; do
read -p "Do you want to remove all containers before run the services? " yn
case $yn in
[Yy]* )
docker ps -aq | xargs docker rm -f;
docker system prune -f; docker volume rm $(docker volume ls -f dangling=true -q);
break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no.";;
esac
done
# Verify STORAGE_DIR is mounted; if not, prompt for mount
if [[ $(findmnt -o TARGET --noheadings --first-only --evaluate $STORAGE_DIR) != $STORAGE_DIR ]]; then
echo "ERROR: $STORAGE_DIR is not a mount point.";
mount_dir;
fi
# Loop through each service and start if not already running
cd "$(dirname "$0")";
for i in "${SERVICES[@]}"; do
echo "checking service $i...";
exist=$(docker ps -f status=running | grep $i | wc -l);
if [[ $exist -gt 0 ]]; then
echo "service already running";
continue;
fi
echo "running service..."
cd $i;
if test -f "./run.sh"; then
./run.sh;
elif test -f "./docker-compose.yml"; then
docker compose up -d;
else
echo "ERROR: No run script or docker-compose file found";
fi
cd ..;
echo "wait 5 seconds";
sleep 5;
done
Key Steps:
- Prompt to Mount Directories: First, the script checks if a specific storage directory exists. If not, it asks whether you’d like to mount two external storage directories (where Docker data is stored) and mounts them if needed.
- Container Cleanup Option: Before starting the services, the script asks if you’d like to remove all existing containers. If confirmed, it removes all containers, prunes the system, and clears dangling volumes. This option helps clean up any outdated or unwanted containers.
- Loop Through Services: The script goes through a list of defined service directories, each containing either a docker-compose.yml file or a run.sh script for launching the service.
- Start Each Service: For each service, it checks if it’s already running. If not, it either runs docker-compose up -d or executes the run.sh script. After each service starts, it pauses for five seconds before proceeding to the next, allowing time for each to initialize smoothly.