PriceBuddy on a CloudFlare Tunnel

PriceBuddy is an open source, self-hostable, web application that allows users to compare prices of products from different online retailers. I show how to host it via a Cloudflare Zero Trust Tunnel.

We are going to create an incus container to install PriceBuddy. You could also use a dedicated system or a virtual machine. Start by creating an incus container per my usual instructions on the channel.

incus launch images:ubuntu/24.04 PriceBuddy -p default -p bridgeprofile -c boot.autostart=true -c security.nesting=true

Connect to the new container.

incus shell PriceBuddy

Accept the updates.

apt update && apt upgrade -y

Install some dependencies.

apt install openssh-server net-tools curl gnupg nano -y

Install Docker from the script on the Docker website.

curl https://get.docker.com | sh

Add a user account a put the user in the sudo and docker groups.

adduser scott
usermod -aG sudo,docker scott

Move to the new user account.

su - scott

Make an application folder and move into it.

mkdir pricebuddy
cd pricebuddy

Edit a docker compose file.

nano compose.yml

Insert the following into the nano editor.

services:
  app:
    image: jez500/pricebuddy:latest
    restart: unless-stopped
    ports:
      - "8080:80"
    volumes:
      - ./storage:/app/storage
      - ./.env:/app/.env
      - ./health.html:/var/www/html/health.html:ro
    environment:
      DB_HOST: database
      DB_USERNAME: pricebuddy
      DB_PASSWORD: pric3buddy
      DB_DATABASE: pricebuddy
      APP_USER_EMAIL: admin@example.com
      APP_USER_PASSWORD: admin
      SCRAPER_BASE_URL: http://scraper:3000
      AFFILIATE_ENABLED: "true"
    depends_on:
      database:
        condition: service_healthy
    command: >
      sh -c "
        echo 'Waiting for MySQL...' &&
        until mysqladmin ping -h database -upricebuddy -ppric3buddy --silent; do
          sleep 2;
        done &&
        echo 'MySQL is up' &&
        apache2-foreground
      "
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost/health.html || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

  database:
    image: mysql:8.2
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: pricebuddy
      MYSQL_USER: pricebuddy
      MYSQL_PASSWORD: pric3buddy
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - ./data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 1m

  scraper:
    image: jez500/seleniumbase-scrapper:latest
    restart: unless-stopped
    ports:
      - "3030:3000"
    # Remove custom command — use default in image
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS http://localhost:3000/health || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s

Save the file with a CTRL O and enter and then a CTRL X to exit the editor.

Create my super-nifty installation script.

nano setup_pricebuddy.sh

Enter the following code into the editor.

#!/bin/bash
set -euo pipefail

echo "⏳ Starting PriceBuddy setup..."

############################################
# 1️⃣ Prompt for REQUIRED domain + credentials
############################################

while true; do
  read -rp "Enter full domain (e.g. pricebuddy.mydomain.com): " DOMAIN
  if [ -n "$DOMAIN" ]; then break; fi
  echo "❌ Domain is required."
done

while true; do
  read -rp "Admin username: " ADMIN_USERNAME
  if [ -n "$ADMIN_USERNAME" ]; then break; fi
  echo "❌ Username is required."
done

while true; do
  read -rp "Admin email: " ADMIN_EMAIL
  if [ -n "$ADMIN_EMAIL" ]; then break; fi
  echo "❌ Email is required."
done

while true; do
  read -rsp "Admin password: " ADMIN_PASSWORD
  echo
  read -rsp "Confirm password: " ADMIN_PASSWORD_CONFIRM
  echo
  if [ "$ADMIN_PASSWORD" = "$ADMIN_PASSWORD_CONFIRM" ] && [ -n "$ADMIN_PASSWORD" ]; then
    break
  fi
  echo "❌ Passwords do not match or are empty. Try again."
done

APP_URL="https://${DOMAIN}"

############################################
# 2️⃣ Create or update .env
############################################
if [ ! -f .env ]; then
  cat <<EOF > .env
APP_KEY=
DB_HOST=database
DB_USERNAME=pricebuddy
DB_PASSWORD=pric3buddy
DB_DATABASE=pricebuddy
SCRAPER_BASE_URL=http://scraper:3000
AFFILIATE_ENABLED=true
APP_URL=${APP_URL}
ASSET_URL=${APP_URL}
SESSION_SECURE_COOKIE=true
APP_USER_EMAIL=${ADMIN_EMAIL}
APP_USER_PASSWORD=${ADMIN_PASSWORD}
EOF
  echo "✅ .env created"
else
  echo "ℹ️ .env exists, updating values"
  sed -i "s|^APP_URL=.*|APP_URL=${APP_URL}|" .env
  sed -i "s|^ASSET_URL=.*|ASSET_URL=${APP_URL}|" .env
  sed -i "s|^APP_USER_EMAIL=.*|APP_USER_EMAIL=${ADMIN_EMAIL}|" .env
  sed -i "s|^APP_USER_PASSWORD=.*|APP_USER_PASSWORD=${ADMIN_PASSWORD}|" .env
fi

export $(grep -v '^#' .env | xargs)

############################################
# 3️⃣ Create required directories
############################################
mkdir -p data
mkdir -p storage/framework/{cache,views,sessions}
mkdir -p bootstrap/cache
echo "✅ Storage directories created"

############################################
# 4️⃣ Create health.html
############################################
cat <<'EOF' > health.html
<!DOCTYPE html>
<html>
<head>
  <title>OK</title>
</head>
<body>
  OK
</body>
</html>
EOF
echo "✅ health.html created"

############################################
# 5️⃣ Start Docker
############################################
docker compose up -d >/dev/null 2>&1
echo "✅ Docker containers started"

############################################
# 6️⃣ Wait for MySQL
############################################
echo "⏳ Waiting for database..."
until docker exec pricebuddy-database-1 \
  mysqladmin ping -h "127.0.0.1" -u"$DB_USERNAME" -p"$DB_PASSWORD" --silent 2>/dev/null; do
  sleep 2
done
echo "✅ Database is ready"

############################################
# 7️⃣ Laravel setup
############################################
docker exec -i pricebuddy-app-1 bash -c "
set -e
chown -R www-data:www-data storage bootstrap/cache 2>/dev/null || true
chmod -R 775 storage bootstrap/cache 2>/dev/null || true

php artisan key:generate --force >/dev/null 2>&1
php artisan migrate --force >/dev/null 2>&1

php artisan config:clear >/dev/null 2>&1
php artisan cache:clear >/dev/null 2>&1
php artisan route:clear >/dev/null 2>&1
php artisan view:clear >/dev/null 2>&1
php artisan config:cache >/dev/null 2>&1
"

############################################
# 8️⃣ Create admin user (idempotent)
############################################
echo "⏳ Ensuring admin user exists..."
docker exec -i pricebuddy-app-1 php -d display_errors=0 -r "
require '/app/vendor/autoload.php';
\$app = require '/app/bootstrap/app.php';
\$kernel = \$app->make(Illuminate\Contracts\Console\Kernel::class);
\$kernel->bootstrap();

use App\Models\User;
use Illuminate\Support\Facades\Hash;

User::updateOrCreate(
    ['email' => '${ADMIN_EMAIL}'],
    [
        'name' => '${ADMIN_USERNAME}',
        'password' => Hash::make('${ADMIN_PASSWORD}'),
        'email_verified_at' => now(),
    ]
);
echo \"✅ Admin user ready\\n\";
"

############################################
# 9️⃣ Done
############################################
echo
echo "🎉 PriceBuddy setup complete!"
echo "Admin email: ${ADMIN_EMAIL}"
echo "Site URL:    ${APP_URL}"
echo "Healthcheck: ${APP_URL}/health.html"

Save the file with a CTRL O and Enter and then a CTRL X to exit the editor.

Grant the script file execute privilege.

chmod +x setup_pricebuddy.sh

Execute the script. Note: You must have a domain and a Cloudflare account in order to configure the Cloudflare tunnel in steps that follow.

./setup_pricebuddy.sh

Here’s an example of when I ran the script from the tutorial. You will of course enter a subdomain name of your choosing on your domain.

After the script completes, the application is up and running, but it is configured to use your subdomain name. We have additional steps before you can access PriceBuddy. You can check the running app.

docker ps

At this point, your file structure should look like this.

ls -al

The next step is to install and configure Cloudflared which is the Cloudflare Zero Trust tunneling daemon. Start by moving to your home folder.

cd

Download the software.

git clone https://github.com/Lalatenduswain/install-cloudflared.git

Move into the folder that “git” just created and provide the script execute privilege.

cd install-cloudflared
chmod +x install-cloudflared.sh

Run the installation script.

./install-cloudflared.sh

Move back to your home folder.

cd

Login to your cloudflared tunnel. Pro Tip: If you are logged into Cloudflare on your default browser, this is simplified.

cloudflared tunnel login

Copy the URL that is displayed as part of the login and then head over to your browser and visit that URL. Click on your zone aka domain in the list.

Click the “Authorize” button and you will see a success screen pretty quickly.

Go back to your terminal you will notice that the login has completed.

You now have a “.cloudflared” folder that has been created.

ls -al

Move into the new folder.

cd .cloudflared
ls -al

Notice that you have a certificate named cert.pem.

Create the tunnel up to Cloudflare. The tunnel name is arbitrary and I called mine pricebuddy2 because I already had one named pricebuddy for my production application.

cloudflared tunnel create pricebuddy2

List the files in the folder and you will have a json file containing your tunnel credentials. Treat this as a password because it provides encrypted access to this container.

ls

Create a configuration file for your tunnel.

nano config.yml

Insert the following into the file.

tunnel: XXXXXXXXXXXXXXXXX  # Replace with your tunnel ID
credentials-file: /home/scott/.cloudflared/XXXXXXXXXXXXXXXXX.json  # Your tunnel credentials file

ingress:
  - hostname: pricebuddy.mydomain.com
    service: http://localhost:8080  # Replace with your app's local service, e.g., port 80
  - service: http_status:404  # This will return a 404 for all other requests

Substitute the XXXXXXXXXXXXXXXXX in the two places with your tunnel ID which is the name of the json file minus the extension.

Also change the “scott” to the name of your home folder. Your hostname should match the subdomain that you provided when you ran the script. Port 8080 is correct for PriceBuddy. Do a CTRL O and enter to save the file and a CTRL X to exit the editor.

Set the permissions on your json file and be sure to use the name of your file and the correct path.

sudo chmod 644 /home/scott/.cloudflared/XXXXXXXXXXXXXXXXX.json

Configure your tunnel using the same name and domain as what you used to create it.

cloudflared tunnel route dns pricebuddy2 pricebuddy.scottibyte.com

Run the Cloudflare tunnel interactively from your terminal to see that it works.

cloudflared tunnel run pricebuddy2

Head on up to your browser and type the subdomain name for your PriceBuddy and you should see a login.

Use the email and password that you used in the script we ran earlier.

We aren’t done, because if you exit the tunnel in the terminal, it will disconnect the application.

To solve this, we are going to create a systemd service to run the tunnel.

sudo nano /etc/systemd/system/cloudflared.service

Insert the following in the file.

[Unit]
Description=Cloudflare Tunnel
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=scott
WorkingDirectory=/home/scott/.cloudflared
Environment=HOME=/home/scott
Environment=TUNNEL_ORIGIN_CERT=/home/scott/.cloudflared/cert.pem
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/cloudflared tunnel run XXXXXXXXXXXXXXXXX
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Be sure to change “scott” above to your username in all the places it occurs. Also be sure to insert your tunnel ID in place of the XXXXXXXXXXXXXXXXX on the “tunnel run” line. Save the file with a CTRL O and enter and a CTRL X to exit the nano editor.

Reload the systemd service files to update the changes.

sudo systemctl daemon-reload

Define the service to work on reboot.

sudo systemctl enable cloudflared

Start the service on the running system now.

sudo systemctl start cloudflared

Check the status of your service.

sudo systemctl status cloudflared

Your PriceBuddy service is now available and running over a Cloudflare tunnel.

1 Like