Automatický deploy na VPS přes GitHub Actions a SSH
Automatické nasazení na VPS po pushi do main – samostatný SSH klíč, secrets, known_hosts, deploy skript, restart služby a omezení přístupu klíče.
Ruční nasazování („přihlásím se na server, udělám git pull, restartuji službu“) funguje, dokud na něco nezapomenete. S GitHub Actions se deploy spustí sám po každém pushi do main – a jen tehdy, když projdou testy.
Jak to bude fungovat
- Pushnete do
main. - GitHub Actions spustí testy.
- Pokud projdou, runner se přes SSH připojí na VPS.
- Na serveru se spustí deploy skript:
git pull, instalace závislostí, migrace, restart.
Krok 1: deploy uživatel a klíč
Na serveru vytvořte uživatele, pod kterým aplikace poběží (pokud ho ještě nemáte):
sudo adduser --disabled-password deploy
Na svém počítači vygenerujte samostatný klíč jen pro CI (bez passphrase, protože ho bude používat automat):
ssh-keygen -t ed25519 -f ./gh_deploy -N "" -C "github-actions-deploy"
Veřejnou část přidejte na server:
ssh-copy-id -i ./gh_deploy.pub [email protected]
Ověřte připojení: ssh -i ./gh_deploy [email protected].
Krok 2: deploy skript na serveru
Logiku nasazení držte ve skriptu na serveru, ne v YAML. Snáz se ladí a lze ho spustit i ručně. Příklad pro Python aplikaci se systemd službou, /home/deploy/deploy.sh:
#!/usr/bin/env bash
set -euo pipefail
cd /home/deploy/app
git fetch origin main
git reset --hard origin/main
source venv/bin/activate
pip install -r requirements.txt --quiet
python manage.py migrate --noinput
sudo /usr/bin/systemctl restart myapp
echo "Deploy hotový: $(git rev-parse --short HEAD)"
chmod +x /home/deploy/deploy.sh
git reset --hard origin/main zajistí, že server má přesně obsah větve, i kdyby na něm někdo ručně něco upravil. Repozitář na serveru musí být naklonovaný a mít přístup na GitHub – postup s deploy klíčem je v článku Git na serveru a deploy klíče.
Aby uživatel deploy mohl restartovat službu bez hesla, povolte mu jen tento jeden příkaz přes sudo visudo -f /etc/sudoers.d/deploy:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp
Krok 3: secrets v GitHubu
Na serveru zjistěte otisk hostitele, aby se runner nepřipojoval naslepo:
ssh-keyscan -t ed25519 vas-server.cz
V repozitáři otevřete Settings → Secrets and variables → Actions a přidejte:
| Secret | Hodnota |
|---|---|
SSH_HOST |
vas-server.cz |
SSH_USER |
deploy |
SSH_KEY |
celý obsah souboru gh_deploy (privátní klíč) |
SSH_KNOWN_HOSTS |
výstup ssh-keyscan |
Poté lokální soubor gh_deploy smažte nebo bezpečně uložte.
Krok 4: workflow
.github/workflows/deploy.yml:
name: Test & Deploy
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-production
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install -r requirements.txt
- run: pytest -q
deploy:
needs: test
runs-on: ubuntu-latest
environment: production
steps:
- name: Příprava SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
echo "${{ secrets.SSH_KNOWN_HOSTS }}" > ~/.ssh/known_hosts
- name: Deploy
run: ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} '/home/deploy/deploy.sh'
Klíčové detaily:
needs: test– deploy proběhne jen po úspěšných testech.concurrencyscancel-in-progress: false– dva deploye nikdy nepoběží současně a rozběhnutý se nepřeruší.environment: production– v Settings → Environments můžete nastavit povinné schválení před nasazením nebo omezení na větevmain.
Místo ručního nastavení SSH lze použít komunitní akce (např. appleboy/ssh-action), ale čisté ssh má výhodu, že přesně vidíte, co se děje, a nezávisíte na třetí straně.
Krok 5: zpevnění bezpečnosti
Klíč CI může na server cokoli, co smí uživatel deploy. Omezte ho v ~/.ssh/authorized_keys tak, aby uměl spustit jen deploy skript:
command="/home/deploy/deploy.sh",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA... github-actions-deploy
S volbou command= se při každém připojení spustí deploy skript bez ohledu na to, jaký příkaz klient pošle.
Další doporučení:
- Port SSH nemusíte otevírat celému světu, ale rozsahy IP adres GitHub runnerů jsou široké a mění se – spolehlivější je omezený klíč a fail2ban.
- Nedávejte uživateli
deployplnésudo.
Ověření a rollback
Po deployi přidejte kontrolu, že aplikace odpovídá:
- name: Health check
run: curl -fsS --retry 5 --retry-delay 3 https://vas-server.cz/health
Rollback je jednoduchý: git revert problematického commitu a push – workflow nasadí opravenou verzi. V nouzi na serveru git reset --hard <předchozí-hash> a restart.
Varianta s Dockerem
Pokud aplikace běží v Docker Compose, deploy skript se zjednoduší:
cd /home/deploy/app
git pull --ff-only
docker compose up -d --build
docker image prune -f
Uživatel pak musí být ve skupině docker – mějte na paměti, že to fakticky odpovídá root přístupu.
Tip
Pokud se deploy na serveru zasekne, připojte VPS v ColdHost AI a v režimu Server a DevOps nechte AI projít logy (journalctl -u myapp). Každý navržený příkaz před spuštěním schvalujete vy.