Automatic Deploy on VPS via GitHub Actions – a step-by-step guide
Main chat
A chat for vibe coders: news, guides, live cases, marketplace, and finding executors.
You wrote the code, the AI helped, everything works locally. Time to roll out to the server. Open the terminal, connect via SSH, pull git pull, restart the process. And you have to do it every time. Every fix. Every update.
It’s not a workflow – it’s manual labor that eats up time and sooner or later leads to mistakes.
CI/CD (Continuous Integration/Continuous Deployment) is when the server updates itself as soon as you make git push. No SSH manually, no "wait, I'll shut down." It's in main, and in a minute, the new version lives on the market.
In this guide - how to configure exactly that: autodeploy on VPS through GitHub Actions and SSH, from zero to working pipiline.
What's happening "under the hood"
When you configure CI/CD with GitHub Actions, the scheme looks like this:
You make a git push in the main
↓
GitHub notes push
↓
GitHub Actions launches runner (virtual machine)
↓
Runner connects to your server via SSH
↓
On the server execute commands: git pull, npm install, restart
↓
The new version of the application works in the
The whole process is automatic, 30-90 seconds, the same every time.
What you need before starting
- GitHub repository with your project.
- VPS with Ubuntu 22.04+ (any: Hetzner, DigitalOcean, Timeweb, REG.RU).
- User on the server with the right to run your application.
- Basic knowledge of SSH (ability to connect to the server).
Step 1. Create an SSH key for GitHub Actions
GitHub Actions will connect to the server as a separate user. You need your own pair of SSH keys, not the one you use personally.
On our computer (not on the server!) we generate a pair of keys:
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/github_actions_deploy
The team creates two files:
~/.ssh/github_actions_deploy– Private Key (GitHub Secrets)~/.ssh/github_actions_deploy.pub- Public key (to be sent to the server)
Step 2. Add a public key to the server
Connect to the server and add the public key to the list of authorized:
# On the server: add a public key
echo "Insert content file .pub" >> ~/.ssh/authorized keys
chmod 600 ~/.ssh/authorized keys
The content of the github_actions_deploy.pub file is one long string that begins with ssh-ed25519. Copy the whole thing.
Check that the connection is working:
# On my computer.
ssh -i ~/.ssh/github actions deploy user@your server-ip
If you connect, the keys are configured correctly.
Step 3. Add secrets to GitHub
Go to GitHub: Settings → Secrets and variables → Actions → New repository secret.
Add three secrets:
| Имя секрета | Значение |
|---|---|
SSH_PRIVATE_KEY |
Содержимое файла github_actions_deploy (приватный ключ, целиком) |
SSH_HOST |
IP-адрес или домен вашего сервера |
SSH_USER |
Имя пользователя на сервере (например, ubuntu или deploy) |
GitHub will encrypt these values and never show them in logs – even if someone gets access to the repository, the secrets will remain closed.
Step 4. Create a workflow file
At the root of the repository, create a folder and file:
.github/
└── workflows/
└── deploy.yml
Here is the basic workflow that works for most projects:
name: Deploy to VPS
on:
push:
branches:
- Main # starts only when the main
jobs:
deploy:
run-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Deploy via SSH
uses appleboy/ssh-action@v1.0.3
with:
host: ${{secrets.SSH HOST}}
username: ${{secrets.SSH USER}}
key: ${{secrets.SSH PRIVATE KEY}}
Script: |
cd /var/www/my-project # the path to the project on the server
git pull origin main # pull the latest changes
npm install -production # set dependencies
pm2 restart my-project # restart process
Replace /var/www/my-project with the actual project path on the server, and my-project in pm2 restart with the name of your pm2 process.
Enter the file and run it in main. GitHub Actions will start automatically.
Watch the deploitation go by
Open the Actions tab in the GitHub repository. You can see each launch: what step is being taken now, the logs of each team, the final status (ый or ый).
If something goes wrong – a red cross, click, see the exact line with the error. No need to guess.
Ready templates for different stacks
Node.js / Express
script: |
cd /var/www/my-project
git pull origin main
npm install --production
pm2 restart my-project
Next.js
script: |
cd /var/www/my-project
git pull origin main
npm install
npm run build
pm2 restart my-project
Python / Telegram bot
script: |
cd /home/ubuntu/my-bot
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
systemctl restart my-bot.service
Docker Compose
script: |
cd /var/www/my-project
git pull origin main
docker compose pull
docker compose up -d --build
Deployment only with successful tests
If you have tests, run the deploy only when they have passed. This ensures that the broken code does not get to the server:
name: Test and Deploy
on:
push:
branches:
- main
jobs:
test:
run-on: ubuntu-latest
steps:
- uses actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm install
run: npm test # If tests fall, the deplo will not start
deploy:
Needs: Test # awaits successful completion of job test
run-on: ubuntu-latest
steps:
- uses actions/checkout@v4
- name: Deploy via SSH
uses appleboy/ssh-action@v1.0.3
with:
host: ${{secrets.SSH HOST}}
username: ${{secrets.SSH USER}}
key: ${{secrets.SSH PRIVATE KEY}}
Script: |
cd /var/www/my-project
git pull origin main
npm install --production
pm2 restart my project
Deploy with notification in Telegram
Do you want to receive a message when the deploit is completed – successfully or with an error? Add a step to the end of workflow:
- name: Notify Telegram
if: always()# sends both success and error
use: appleboy/telegram-action@master
with:
to: ${secrets. TELEGRAM CHAT ID }
Token: ${secrets. TELEGRAM BOT TOKEN }
message: |
Deploy: ${{job.status}}
Repository: ${{github.repository }}
Branch: ${{github.ref name }}
Commit: ${{github.event.head commit.message }}
Add the secrets of TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID to GitHub Secrets – and after each deploy you will receive a report in Telegram.
A separate user for the deploy (correctly)
Connecting as root for deploy is bad practice. The right approach is to create a separate deploy user with minimal rights.
# On the server.
sudo useradd -m -m /bin/bash deploy
# Give the right to restart the necessary services through sudo without a password
echo "deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart my-app"
| sudo tee /etc/sudoers.d/deploy-restart
# Give rights to the project folder
sudo chown -R deploy:deploy /var/www/my-project
After that, in the SSH_USER secret, specify deploy, not root.
What to do if the den falls
“Permission denied (publickey)”
Problem with the SSH key. Check it out
SSH_PRIVATE_KEYis the entire private key (including the-----BEGIN...-----and-----END...-----strings).- The public key is added to
~/.ssh/authorized_keyson the server. - File rights:
chmod 600 ~/.ssh/authorized_keys.
Error "Host key verification failed"
GitHub Actions does not know the fingerprint of your server. Add a parameter to the ssh-action:
with:
host: ${{secrets.SSH HOST}}
username: ${{secrets.SSH USER}}
key: ${{secrets.SSH PRIVATE KEY}}
host key algorithms: +ssh-rsa # or remove strictly if Ubuntu 22 +
Or add a separate SSH_HOST_KEY secret from the fingerprint server (receive: ssh-keyscan ваш-сервер-ip).
Deployed, but the site is not updated
The pm2/systemd is probably not restarted. Check the name of the process:
pm2 list #names of all pm2 processes
systemctl list-units #names of all systemd services
Relation to other articles
This is a basic pipeline – it covers 90% of the Vibecoder tasks. When the project grows and you need a deploit without a single second of downtime and an instant rollback - see CI/CD для вайбкодинга: деплой без простоя и откат за 1 минуту, where the Blue-Green strategy is dismantled on top of Docker.
Outcome
Autodeployment through GitHub Actions is an investment of 30 minutes once that saves time with each subsequent release. After setting up your work cycle looks like this:
wrote the code → git push in a minute the project in prode
You no longer need to remember the order of commands, connect to the server manually or worry that you forgot npm install. Actions does it for you - every time the same.
** Minimum checklist for the first depletion:**
- Create an SSH key:
ssh-keygen -t ed25519 -f ~/.ssh/github_actions_deploy - Public key in
~/.ssh/authorized_keyson the server - Private key, host, user in GitHub Secrets
- Create
.github/workflows/deploy.ymlwith the desired template - Start in
main→ open the Actions tab → watch how it works