I have a quite elaborate script that does quite a lot of things. It does require you to setup the correct ownerships like every laravel/webserver project does.
The ownerships in a nutshell are something like this (these are not exact steps, just some helpers):
Development Server Setup Instructions
Setting up on a new server you might encounter premission issues. The folders /database/ and /storage/ must belong to the user:group www-data:www-dat or the webserver will have issues. Also the /database/database.sqlite file must be writable by the webserver! Furthermore chmod 775 ... is advisable.
Examples:
# add database owner (webserver)
sudo chown www-data:www-data database/database.sqlite
# make database writeable
sudo chmod 775 database/database.sqlite
# make executable recursively
sudo find storage/ -type d -exec chmod 775 {};
# make editable by user group and not just for the user (recursively)
sudo find storage/ -type d -exec chmod g+s {};
# do the same for database (recursively)
sudo find database/ -type d -exec chmod g+s {};
The Github Workflow Action:
name: 🥭 Build and Deploy to Hetzner 🙌
# your credentials (requires ssh key setup on server/github for RSync)
env:
SERVER_HOST: 'YOUR_IP'
SERVER_USERNAME: 'YOUR_USER'
SERVER_PATH: '/var/www/YOUR_FOLDER/'
# which branch should trigger the action
on:
push:
branches:
- main
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 🚙 Checkout code
uses: actions/checkout@v4
- name: 🐘 Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
extensions: mbstring, pdo_mysql, ctype, iconv, zip
- name: 💾🎶 Cache Composer dependencies
id: cache-composer
uses: actions/cache@v4
with:
path: vendor
key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }}
restore-keys: |
${{ runner.os }}-php-
# --no-dev will make sure no developer dependencies are installed. So you can't run "php artisan migrate" on the webserver, since the package is installed in the dev packages.
- name: 🎶 Install Composer dependencies
run: composer install --no-dev --optimize-autoloader
# adjust to desired version
- name: 🥗 Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '23.11.1'
- name: 💾🚛 Cache Node.js modules
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- name: 🚛 Install NPM dependencies
run: npm ci
- name: 👷🏾🏗️ Build assets
run: npm run build
- name: 🎛️ Prepare production artifact
run: |
mkdir -p deployment
rsync -av --progress --exclude='deployment' . ./deployment/
rm -rf deployment/node_modules deployment/.git deployment/.github
# optionally make a backup of your SQLite to some location
- name: 💾 Backup SQLite Database
uses: appleboy/ssh-action@master
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd ${{ env.SERVER_PATH }}
BACKUP_DIR="BACKUP"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DB_FILE="database/database.sqlite"
mkdir -p $BACKUP_DIR
if [ -f $DB_FILE ]; then
cp $DB_FILE $BACKUP_DIR/${TIMESTAMP}_database.sqlite
echo "Backup created: $BACKUP_DIR/${TIMESTAMP}_database.sqlite"
else
echo "Database file $DB_FILE not found. Skipping backup."
fi
# make sure static files or backups etc are not being overwritten by rsync (it would delete them if they aren't in your github repository otherwise
- name: 🔃 Deploy with rsync
uses: burnett01/[email protected]
with:
remote_host: ${{ env.SERVER_HOST }}
remote_user: ${{ env.SERVER_USERNAME }}
remote_key: ${{ secrets.SERVER_SSH_KEY }}
remote_path: ${{ env.SERVER_PATH }}
path: './deployment/'
switches: -vzr --delete --exclude '.env' --exclude 'database/database.sqlite' --exclude 'storage/' --exclude 'BACKUP'
- name: 🛄 Run post-deployment commands via SSH
uses: appleboy/ssh-action@master
with:
host: ${{ env.SERVER_HOST }}
username: ${{ env.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd ${{ env.SERVER_PATH }}
php artisan storage:link
php artisan migrate --force
php artisan filament:cache-components
php artisan optimize
Note: the laravel application is being built on the github machine, then deployed. I think this keeps a more consistent outcome, than building on the deployment server.