Simple GitHub Actions + Your Own Server Setup
An easy guide to hooking up GitHub Actions with your self-hosted server without the hassle of self-hosted runners.
If you’re anything like me, you love pushing your code to GitHub — it feels clean, organized, done. But when it comes to hosting? You’re not deploying to Vercel, Netlify, or Cloudflare Pages — unless it’s a static site (like this blog, lol). Nope. You’re probably running a sketchy VPS somewhere, maybe even a Raspberry Pi behind a port-forwarded router. But you still want that sweet GitHub Actions integration because pushing code and having it magically deploy is awesome.
Now there are better ways like a self-hosted runner (I talk about that later). Also, I wouldn't say this is very suitable for a team of more than 2-4 people, but for personal projects or small teams, this is a great way to get started in my opinion.
Sometimes you want to be extra and hook directly into your server. Don't @ me.
The Setup
1. Generate a Dedicated SSH Key for GitHub Actions
First, let's get your SSH situation sorted. On your local machine:
ssh-keygen -t rsa -b 4096 -C "Comment to identify this key"
When prompted, save it as something memorable like:
/home/username/.ssh/github_actions_key
For the passphrase, you can leave it empty for simplicity, but if you want extra security, set a passphrase (you'll need to add it as a secret later). I do recommend setting one though.
This gives you two files:
github_actions_key(private key - keep this secret)github_actions_key.pub(public key - this goes on your server)
2. Set Up SSH Access to Your Server
Copy the public key to your server:
# Copy the public key to your server
ssh-copy-id -i ~/.ssh/github_actions_key.pub user@host
# Or if ssh-copy-id isn't available, do it manually:
cat ~/.ssh/github_actions_key.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
Test that it works:
ssh -i ~/.ssh/github_actions_key user@host
3. Create Your Deployment Script
SSH into your server and create a deployment script:
sudo nano /home/username/deploy.sh
A small template for a deployment script:
#!/bin/bash
cd /path/to/your/project
git pull origin main
# Do your deployment stuff here
docker-compose down && docker-compose up -d
Here's my (cursed) deployment script I use for a .NET console app:
#!/bin/bash
PROJECT_DIR="/home/ubuntu/Sushi-v3"
LOG_FILE="$PROJECT_DIR/deploy.log"
echo "$(date): Starting deployment" >> $LOG_FILE
cd "$PROJECT_DIR" || { echo "$(date): Failed to change directory to $PROJECT_DIR" >> $LOG_FILE; exit 1; }
git pull >> $LOG_FILE 2>&1
if [ $? -eq 0 ]; then
echo "$(date): Git pull successful" >> $LOG_FILE
echo "$(date): Stopping existing app" >> $LOG_FILE
screen -S sushi -X quit 2>/dev/null || true
echo "$(date): Building .NET application" >> $LOG_FILE
dotnet build Sushi.csproj --configuration Release >> $LOG_FILE 2>&1
if [ $? -eq 0 ]; then
echo "$(date): Build successful, starting application" >> $LOG_FILE
BUILD_PATH="$PROJECT_DIR/bin/Release/net6.0"
if [ ! -f "$BUILD_PATH/Sushi" ]; then
echo "$(date): ERROR - Executable not found at $BUILD_PATH" >> $LOG_FILE
exit 1
fi
screen -dmS sushi bash -c "cd $BUILD_PATH && ./Sushi"
sleep 3
if screen -list | grep -q "sushi"; then
echo "$(date): Application started successfully in screen session 'app'" >> $LOG_FILE
else
echo "$(date): Application failed to start" >> $LOG_FILE
exit 1
fi
else
echo "$(date): Build failed" >> $LOG_FILE
exit 1
fi
echo "$(date): Deployment completed successfully" >> $LOG_FILE
else
echo "$(date): Git pull failed" >> $LOG_FILE
exit 1
fi
Main components are just pulling the latest code from your repo, building it, and starting it in a screen session or whatever you use.
Make it executable:
chmod +x /home/username/deploy.sh
Test it manually:
./deploy.sh
4. Set Up GitHub Repository Secrets
In your GitHub repo, go to Settings → Secrets and variables → Actions → New repository secret.
Add these four secrets:
HOST: Your server's public IP address or domain.
USERNAME: Your SSH username.
SSH_KEY: Your private key content
# Copy the private key content
cat ~/.ssh/github_actions_key
Copy the entire output (including -----BEGIN OPENSSH PRIVATE KEY----- and -----END OPENSSH PRIVATE KEY-----) and paste it as the SSH_KEY secret.
SSH_PASSPHRASE: If you set a passphrase when generating your SSH key, add it here. If not, you can skip this.
5. Create the GitHub Actions Workflow
In your repo, create .github/workflows/deploy.yml:
name: Deploy to My Server
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy via SSH
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }} # Optional, if you set a passphrase on your SSH key
script: |
/home/${{ secrets.USERNAME }}/deploy.sh
6. Test Your Setup
Push something to your main branch and watch the Actions tab in GitHub. You should see your workflow run, and if everything's set up right, your server should update automatically.
Check the deployment log on your server:
tail -f /var/log/deploy.log
Troubleshooting (Because Nothing Ever Works the First Time)
Permission Denied on SSH: Make sure your private key permissions are restrictive and the public key is correctly added to ~/.ssh/authorized_keys on your server.
chmod 600 ~/.ssh/github_actions_key
chmod 700 ~/.ssh
# Ensure your public key is in ~/.ssh/authorized_keys on the server
# Identify it using the comment you added when generating the key
ssh -i ~/.ssh/github_actions_key user@host "cat ~/.ssh/authorized_keys"
Git Authentication Issues: If your repo is private, you'll need to set up SSH access for git too:
# On your server, generate another key for git
ssh-keygen -t rsa -b 4096 -C "server-git-access"
# Add the public key to your GitHub account under SSH keys
Deploy Script Fails: Check the log file.
Workflow Doesn't Trigger: Double-check your branch name in the workflow file. Is it main or master? GitHub's been switching defaults.
Pro Tips (aka Lessons from My Mistakes)
Use Absolute Paths: Your deployment script might run from a different directory than you expect. Always use full paths like /home/username/project instead of ~/project.
Test the SSH Action Locally: You can simulate what GitHub Actions does:
ssh -i ~/.ssh/github_actions_key user@host "/home/username/deploy.sh"
GitHub Actions Logs: If your workflow fails, check the logs in the Actions tab. They can be super helpful for debugging.
Set Up Health Checks: Add a simple check to verify your app is running.
I did that using screen:
# At the end of your deploy script
if screen -list | grep -q "sushi"; then
echo "$(date): App is running in screen session" >> $LOG_FILE
else
echo "$(date): App not found in screen sessions" >> $LOG_FILE
fi
Using systemd: If you're running a service, consider using systemd to manage it. It can restart your app if it crashes and gives you better logging. screen or tmux are usable for quick and dirty setups, but systemd is more robust. Although, it is a bit more complex to set up but the benefits are worth it.
Consider Using a Deploy Key: Create a deploy key specifically for your GitHub repo. This way, you can keep your personal SSH keys separate from deployment tasks.
More:
- Backup your ssh keys — GitHub Secrets don't give them back.
- Use
pull_requestevents if you want to preview before deploying. - Throw in
uptime/df -h/htopcommands to check your server mid-deployment.
Why Not Just Use a Self-Hosted Runner?
Valid question. Self-hosted runners are probably the "right" way to do this. They're more secure, give you better control, and integrate seamlessly with Actions.
But:
- They require more setup.
- More resource usage.
- They can be overkill for small projects or personal use.
Sometimes you just want something simple that works without setting up another service. This approach is basically using GitHub Actions as a fancy webhook system, and honestly? It works fine for small projects.
If you want to learn more about self-hosted runners, check out the GitHub docs.
Reality Check
This setup works great for personal projects and small teams. If you're doing anything serious with compliance requirements or multiple environments, please use proper CI/CD tools. Your DevOps team will thank you.
Also remember - your server needs to be accessible from the internet for this to work. If you're behind NAT or a restrictive firewall, you'll need to sort that out first.
Wrapping Up
That’s the gist.
You push to main, GitHub deploys your code to your janky but lovable server. You didn’t pay for fancy CI/CD. You didn’t cry setting up a cloud pipeline. You just… automated your life a bit.
Clean. Controlled. Kinda cursed (just a little).