My Self Hosting Stack
In my previous post I wrote about the journey that led to discovering self hosting and the solutions that I am using right now. This post…
My Self Hosting Stack
In my previous post I wrote about the journey that led to discovering self hosting and the solutions that I am using right now. This post, as promised, will cover the technical details on how I host them, and the automation and security solutions to keep them safe.
Let us get the hardware details out of the way first. All my services are running on a mini PC that has an Intel N95 with 16GB RAM and a 512GB boot SSD. There is another 512GB SSD and a couple of 4TB Seagate IronWolf drives running in RAID1 (mirrored for redundancy) for my storage needs. The SSD is the primary storage for my Immich library and the HDDs in mirror give 4TB of space for my personal cloud storage. Both HDDs are configured using hardware RAID on the QNAP 4Bay DAS enclosure.

My Hardware
On the software side of things, the mini PC runs Ubuntu Server 24.04 LTS and all the services are hosted using Docker and managed using Docker Compose. All my Docker Compose files are hosted in my git repo and I create .env_template files under each service folder to supply secret values. I copy this file to .env on the machine and fill in the values; on the repo only the templates exist. While this is not the most secure way of storing secrets — the best way would be to use a password vault or secret manager — the tolerance is a little higher in a homelab environment and .env files serve me well.
Coming to the services that I am running. For Immich, I use the default compose manifest provided by the developers on their git repo. I just add the Tailscale sidecar container to enable secure access and HTTPS certificates through Tailscale DNS. For CopyParty, I use templates from an amazing resource called LinuxServer.io. This is a community that maintains Docker images and template Docker Compose files for various self-hostable services. Go ahead and give it a look — you might find your next service there.
On my git repo, I have a directory named “server_automation” which is also added to the PATH variable on my server. It contains scripts that give me a quicker way to start, stop, restart and update all the Docker Compose stacks running on my server. Here is a sample snippet for updating services or a specified service.
#!/bin/bash
if [[ $1 ]]
then
export services=$1;
else
export services=$(docker ps --filter "label=com.docker.compose.project" -q | xargs docker inspect --format='{{index .Config.Labels "com.docker.compose.project"}}'| sort | uniq)
fi
echo -e "\e[32mFollowing services are running:\e[0m"
echo $services
for service in $services;
do
echo -e "\e[32mWorking on $service .....\e[0m"
if [[ "$service" = "samba" ]];
then
cd ~/homelab/samba &&
docker compose down &&
docker compose pull &&
docker compose up -d;
elif [[ "$service" = "immich" ]];
then
cd ~/immich-app &&
docker compose down &&
docker compose pull &&
docker compose up -d;
elif [[ "$service" = "copyparty" ]];
then
cd ~/homelab/copyparty &&
docker compose down &&
docker compose pull &&
docker compose up -d;
elif [[ "$service" = "karakeep" ]];
then
cd ~/homelab/karakeep &&
docker compose down &&
docker compose pull &&
docker compose up -d;
elif [[ "$service" = "jellyfin" ]];
then
cd ~/homelab/jellyfin &&
docker compose down &&
docker compose pull &&
docker compose up -d;
elif [[ "$service" = "arr" ]];
then
cd ~/homelab/arr &&
docker compose down &&
docker compose pull &&
docker compose up -d;
else
echo -e "\e[31mDid not find $service in configuration\e[0m";
fi
done;
docker image prune -f
echo -e "\e[32mFinished updating the following services:\e[0m"
echo $services
I also have cron jobs running that trigger backup scripts to take a backup of my home directory along with configurations and application data of the services into the RAID volume. Another job takes a DB backup of my Immich instance and the library. All these jobs run every day at 5 AM.

cronjobs

immich backup script
From a security perspective, I have UFW acting as the host-based firewall on the server. I have a few simple rules: a default rule to block all incoming traffic, a couple of rules to allow incoming and outgoing traffic on Tailscale (further locked down by Tailscale Access Grants, about which I wrote in my Tailscale article), and a couple more rules to allow SSH from my laptop and phone on the local network through static IPs (which have been statically assigned using MAC binding on my router).

UFW
As I continue to experiment with new services and devices, I have a few different ideas for how I could architect this setup further — more network segmentation using VLANs, granular local network controls, and more. I will make sure to write about them as soon as I implement them. Until then, happy hosting!
메타데이터
- post_id
- 55c8fe66f5c2
- slug
- my-self-hosting-stack-55c8fe66f5c2
- url
- https://medium.com/@blabber_ducky/my-self-hosting-stack-55c8fe66f5c2
- canonical_url
- https://medium.com/@blabber_ducky/my-self-hosting-stack-55c8fe66f5c2
- author_url
- https://medium.com/@blabber_ducky
- status
- ok
- fetched_at
- 2026-06-23 03:48:11