Servery a DevOps

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.

12. 10. 2026 · 4 min čtení

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

  1. Pushnete do main.
  2. GitHub Actions spustí testy.
  3. Pokud projdou, runner se přes SSH připojí na VPS.
  4. 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.
  • concurrency s cancel-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ětev main.

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 deploy plné 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.