Automated Laravel Deployment with a Self-Hosted GitHub Actions Runner

Posted on April 13, 2025 (Updated: April 13, 2025)

Automated Laravel Deployment with a Self-Hosted GitHub Actions Runner

In this setup, I configured a GitHub Actions runner to live inside my Laravel container. This allows code pushed to the main branch to automatically update the application without needing exposed SSH ports or any manual steps.


Environment

  • Laravel app running in an LXC container
  • Hosted on a Proxmox server
  • Code stored in a GitHub repository
  • Deployment triggered by GitHub Actions via a self-hosted runner

1. Create a GitHub Runner User

A dedicated user for the runner avoids using root:

adduser github-runner

2. Install GitHub Actions Runner

From GitHub:

  • Go to the repository
  • Settings → Actions → Runners → New self-hosted runner

Follow the instructions, selecting Linux and x64.

Example steps:

cd /home/github-runner
mkdir actions-runner && cd actions-runner
curl -o actions-runner.tar.gz -L https://github.com/actions/runner/releases/download/v2.323.0/actions-runner-linux-x64-2.323.0.tar.gz
tar xzf actions-runner.tar.gz
./config.sh --url https://github.com/your-user/your-repo --token YOUR_TOKEN

3. Fix Permissions

The Laravel project directory was originally owned by root. To avoid Git errors:

chown -R github-runner:github-runner /var/www/laravel

Also added this to the workflow:

- name: Trust repo directory
  run: git config --global --add safe.directory /var/www/laravel

4. GitHub Actions Workflow

.github/workflows/MainBranchDeploy.yml:

name: Deploy Laravel App

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: self-hosted

    steps:
      - name: Pull latest code
        run: |
          cd /var/www/laravel
          git pull origin main

      - name: Install PHP dependencies
        run: |
          cd /var/www/laravel
          composer install --no-interaction --prefer-dist --optimize-autoloader

      - name: Run database migrations
        run: |
          cd /var/www/laravel
          php artisan migrate --force

      - name: Optimize Laravel config
        run: |
          cd /var/www/laravel
          php artisan config:cache
          php artisan route:cache
          php artisan view:cache

5. Run the Runner as a Service

To keep the runner running and start it on boot:

Create /etc/systemd/system/github-runner.service:

[Unit]
Description=GitHub Actions Runner
After=network.target

[Service]
User=github-runner
WorkingDirectory=/home/github-runner/actions-runner
ExecStart=/home/github-runner/actions-runner/run.sh
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable and start it:

systemctl daemon-reexec
systemctl daemon-reload
systemctl enable github-runner
systemctl start github-runner

Result

Now, when I push code to the main branch on GitHub, the runner inside the container automatically pulls the latest code, runs Composer, handles migrations, and rebuilds Laravel’s cache.

This setup avoids opening ports or using remote SSH connections, and keeps deployment contained within the homelab.

May revisit this later to isolate the runner into its own container or set up a staging environment.