Self-Hosted GitHub Actions for Zero-Cost Automated Deployment

April 20, 2026

If you're running your own servers (AWS EC2, Oracle Cloud OCI, any VPS), you can set up self-hosted GitHub Actions runners for fully automated deployment at zero additional cost.

This is the exact setup I use across all CloudFold Studio client projects and my own infrastructure.

Phase 1: One-Time Server Setup

Step 1: SSH Into Your Server

Connect to your server via SSH.

Step 2: Create a Dedicated Runner User

# Create a user for GitHub Actions sudo adduser github-runner # Add to necessary groups (PM2 access, site directories) sudo usermod -aG www-data github-runner # Switch to this user sudo su - github-runner

Or use your existing deployment user if you have one (e.g., deployment on CloudPanel).

Step 3: Set Password for Deployment User

# Exit to root user exit # Set password sudo passwd deployment # Switch back su - deployment

Step 4: Register the GitHub Runner

For a GitHub Organization: Navigate to: github.com/organizations/YOUR-ORG/settings/actions/runners

For a personal repository: Navigate to: Repository → Settings → Actions → Runners → New self-hosted runner

Select your OS (Linux), copy the download and configure commands, and run them on your server.

Step 5: Install Runner as a Service

# Install as a system service (auto-starts on boot) sudo ./svc.sh install # Start the service sudo ./svc.sh start # Verify it's running sudo ./svc.sh status # Should show: "active (running)"

Step 6: Set Up Directory Access

Run this as root to give your deployment user access to all site directories:

# Create a shared group groupadd -f deploy-access # Change owning group of all site home directories for homedir in /home/*/; do user=$(basename "$homedir") # Skip system users if [[ "$user" == "cloudpanel" || "$user" == "deployment" || "$user" == "root" ]]; then continue fi echo "Fixing $homedir → group deploy-access" chgrp deploy-access "$homedir" chmod 750 "$homedir" done # Fix htdocs directories (so deployment can write) for htdocs in /home/*/htdocs; do [[ -d "$htdocs" ]] || continue chgrp -R deploy-access "$htdocs" chmod -R 775 "$htdocs" done

Test it immediately:

cd /home/yoursite/htdocs/www.yoursite.com touch .test-deploy && rm .test-deploy

Phase 2: Configure a Site for Deployment

Step 7: Create the Workflow File

In your repository, create .github/workflows/deploy.yml:

name: Deploy Your Site on: push: branches: - main workflow_dispatch: jobs: deploy: runs-on: self-hosted steps: - name: Clean workspace run: | git checkout -- . 2>/dev/null || true git clean -fd 2>/dev/null || true - uses: actions/checkout@v4 with: clean: true - uses: actions/setup-node@v4 with: node-version: '24' cache: 'npm' - run: npm ci - run: npm run build - name: Deploy files run: | rsync -rlptDv --delete \ --exclude 'node_modules' \ --exclude '.git' \ --exclude '.env' \ --no-perms --no-owner --no-group --no-times \ ./ /home/SITE_USER/htdocs/www.YOURDOMAIN.com/ - name: Install production dependencies run: | cd /home/SITE_USER/htdocs/www.YOURDOMAIN.com npm ci --production - name: Restart PM2 run: | cd /home/SITE_USER/htdocs/www.YOURDOMAIN.com pm2 reload your-app-name || pm2 start npm --name "your-app-name" -- start pm2 save - run: echo "✅ Deployed successfully!"

Replace:

  • SITE_USER → your server's site user
  • www.YOURDOMAIN.com → your actual domain
  • your-app-name → your PM2 process name

Step 8: Commit and Push

git add .github/workflows/deploy.yml git commit -m "Add GitHub Actions deployment" git push origin main

Step 9: Watch It Deploy

  1. Go to your GitHub repo
  2. Click the Actions tab
  3. Your workflow should be running
  4. Click on it to see live logs

If everything is green, you now have automated deployment on every push to main.

Phase 3: Adding More Sites

For each additional site or repository, register the same runner with a new repository:

cd ~/actions-runner ./config.sh --url https://github.com/YOUR-USERNAME/NEW-REPO --token NEW-TOKEN # Use the same runner name — one runner handles multiple repos

Then copy the workflow file to the new repo and change:

  • Site user
  • Domain
  • PM2 process name

My Full DevOps Stack

This is part of my complete self-hosted infrastructure:

  • OCI / AWS EC2 — primary compute
  • CloudPanel — manages Node.js, WordPress, PHP, static, and reverse proxy sites
  • Self-hosted PostgreSQL — primary relational database
  • Self-hosted MongoDB — document store for SaaS projects
  • Self-hosted Redis + BullMQ — background job queues
  • GitHub Actions self-hosted — automated deployment across all projects

The result: zero per-deployment costs, full control over infrastructure, and automated deployments that run on every push.

GitHub
LinkedIn
X