As you may be aware, we (fairly) recently, took over the development and operation of Homebox, the home inventory tracking software (https://homebox.software) - As part of this, it was clear that we required a demo instance to enable us to showcase the solution to our community.
With this, https://demo.homebox.software and https://nightly.homebox.software were created. In this blog, I will detail the technical details of how these two demo instances run, and why you may also like to follow a similar configuration.
The configuration
These two instances both operate using Docker compose, and are ran from my lab environment host (this is quite a powerful server, and so the resource was spare to put to use.)
On the server, I created a new folder for the demos at ~/docker/homebox-demos. Under this folder, I then created three new subfolders:
logsDemoScripts
These will become clear in a moment.
Next, it was time to create a bespoke Docker-compose.yml file for these. I changed directory to Demo and ran nano docker-compose.yml - In here, the contents were:
services:
homebox-demo:
# image: ghcr.io/sysadminsmedia/homebox:latest
image: ghcr.io/sysadminsmedia/homebox:latest-rootless
container_name: homebox-demo
restart: always
environment:
- HBOX_LOG_LEVEL=info
- HBOX_LOG_FORMAT=text
- HBOX_WEB_MAX_UPLOAD_SIZE=10
- HBOX_DEMO=true
volumes:
- homebox-data:/data/
networks:
- Homebox-Demos
homebox-nightly:
# image: ghcr.io/sysadminsmedia/homebox:nightly
image: ghcr.io/sysadminsmedia/homebox:latest-rootless
container_name: homebox-nightly
restart: always
environment:
- HBOX_LOG_LEVEL=info
- HBOX_LOG_FORMAT=text
- HBOX_WEB_MAX_UPLOAD_SIZE=10
- HBOX_DEMO=true
volumes:
- homebox-data:/data/
networks:
- Homebox-Demos
tunnel:
container_name: cloudflared-tunnel
image: cloudflare/cloudflared
restart: unless-stopped
command: tunnel --pq run
environment:
- TUNNEL_TOKEN={Token}
networks:
- Homebox-Demos
networks:
Homebox-Demos: {}
volumes:
homebox-data:
driver: localHomebox demos docker compose file
This is where it gets interesting - Lets break this YAML file down.
- You will note that the Nightly and Demo instances both operate on the same docker-compose. This is to allow easy management of a single Docker network for these two instances (and shared resources to make our demo instance easier for the community).
- You will note that there are no exposed ports - The Cloudflare Tunnel handles this for us.
- You will note that the demo instances both operate on the Homebox-Demos network. This is a custom Docker network, which allows us to segregate the Docker network traffic from other instances on the host.
The Cloudflare configuration
On Cloudflare, as we have pointed the TUNNEL_TOKEN environment variable to our tunnel, we should now see that this is available and healthy.
The configuration for our Homebox Cloudflare points to the docker containers (and ports) - Remember we didn’t expose the ports? Cloudflare handles this for us.

Automating the rebuilds
To ensure that our demo instances are constantly up-to-date and that they pull down the relevant updates as required, I crafted a shell script. Given that these instances are only for demo data and we do not care about the contents of the instances, we discard the volumes on every rebuild (this also ensures that there is no ballooning of orphaned volumes!)
In docker/homebox-demos/scripts we created a new file called deployments.sh:
#!/bin/bash
# Function to post failure message to Discord webhook
post_failure() {
local title=$1
local description=$2
local date=$(date +"%Y-%m-%d %H:%M:%S")
local webhook_url="{Discord_webhook}"
curl -H "Content-Type: application/json" -X POST -d "{
\"embeds\": [{
\"title\": \"$title\",
\"description\": \"$description on $date. Please review and remediate.\"
}]
}" $webhook_url
}
# Function to check if the service is available
check_service() {
local url=$1
local failure_title=$2
local failure_description=$3
if ! curl -s --head --request GET $url | grep "200" > /dev/null; then
post_failure "$failure_title" "$failure_description"
else
echo "Service at $url is up and running."
fi
}
# Step 1: Change directory to ~/docker/homebox-demos/demo
cd ~/docker/homebox-demos/demo || { echo "Failed to navigate to ~/docker/homebox-demos/demo"; exit 1; }
# Step 2: Remove old Docker volumes
docker compose down -v
# Step 3: Pull the latest versions of the images using docker compose
docker compose pull
# Step 4: Start the updated containers
docker compose up -d
# Step 5: Check if nightly demo is available
check_service "https://nightly.homebox.software" "Nightly deployment FAILED" "The latest deployment of the nightly demos has failed to deploy to https://nightly.homebox.software"
# Step 6: Check if demo is available
check_service "https://demo.homebox.software" "Demo deployment FAILED" "The latest deployment of the demo has failed to deploy to https://demo.homebox.software"
Let’s break this script down.
- We created a new function for posting about failed deployments. This sends a message to the Homebox Discord server ( https://discord.homebox.software ) if the deployment fails for whatever reason.
- We also created a function to handle the checks to ensure that our instances are online - These both run a grep on the URL to check for a status code of 200. If a status of 200 is not returned, the service is deemed to be down.
- Then, we run the usual commands:
- Change directory to where the demos are stored
- Remove old Docker volumes
- Pull the latest versions of the images
- Start the updated containers
- Check if the services are up
Crontask
To tie this all in, we then run a crontask which runs every hour. The reason for running this every hour is to ensure that the services are always operating at their optimum, and that any changes are pushed through. This also ensures that the demos are reverted back to a clean slate if anyone decides that they want to spend their time to deface it (or create obscene items, which we’ve had)
The crontask runs as follows:
0 * * * * > ~/docker/homebox-demos/logs/deployment.log && /bin/bash ~/docker/homebox-demos/scripts/deployment.sh >> ~/docker/homebox-demos/logs/deployment.log 2>&1 Breaking this down, this means that every hour, the deployment log gets cleared, and the deployment shell script is ran. The output of this is then saved to the deployment logs for if this goes wrong.