---
title: "Deploy com GitHub Actions na VPS: pipeline passo a passo"
description: "Deploy com GitHub Actions na VPS: usuário sem root, chave SSH dedicada, secrets, envio por rsync, reinício da aplicação e concurrency contra conflito."
url: "https://streethosting.com.br/guias/vps/deploy-github-actions-vps"
category: "vps"
slug: "deploy-github-actions-vps"
datePublished: "2026-09-28"
dateModified: "2026-09-28"
author: "Equipe StreetHosting"
difficulty: "intermediario"
language: "pt-BR"
keywords:
  - "deploy github actions vps"
  - "github actions ssh deploy"
  - "github actions rsync vps"
  - "deploy automatico vps"
  - "workflow deploy servidor linux"
---

# Deploy automático na VPS a cada push com GitHub Actions

Cada push na branch principal pode compilar, enviar e reiniciar sua aplicação na VPS sem ninguém abrir o terminal. Veja como montar esse fluxo com um usuário de deploy sem root, secrets bem guardados e proteção contra deploys simultâneos.

> **Resposta rápida**
>
> Para fazer **deploy com GitHub Actions na VPS**, crie no servidor um usuário de deploy sem root com uma chave SSH só dele, guarde host, usuário, chave privada e known_hosts como secrets e escreva um workflow que faz checkout, build, envia os arquivos por rsync e reinicia a aplicação. Libere para esse usuário apenas o comando de reinício no sudoers e use concurrency para que dois deploys nunca rodem ao mesmo tempo.

## Como o deploy funciona

O GitHub Actions executa o workflow em uma máquina temporária do GitHub, chamada runner. Ela baixa o código, instala dependências, roda os testes e gera o build. No fim, abre uma conexão SSH com a sua VPS, copia o resultado e manda a aplicação reiniciar. A VPS não precisa de Git, de credencial do GitHub nem de ferramenta de build: ela só recebe arquivos prontos.

Esse desenho tem duas vantagens práticas. O processamento pesado do build fica fora do servidor, que continua atendendo usuários enquanto a próxima versão é compilada. E a VPS nunca guarda um token com acesso ao repositório, então uma invasão no servidor não vira acesso ao seu código.

1. Um push na branch main dispara o workflow.
2. O runner faz checkout, instala dependências, testa e compila.
3. O runner carrega a chave de deploy a partir dos secrets.
4. O rsync envia os arquivos para a VPS pela conexão SSH.
5. Um comando remoto instala as dependências de produção e reinicia o serviço.

Os exemplos usam uma aplicação Node.js rodando como serviço do systemd em `/srv/minha-app`, mas a estrutura vale para Python, PHP, Go ou site estático: mudam só o build e o comando de reinício. Se a aplicação ainda não roda como serviço, comece por [PM2 ou systemd para manter a aplicação online](https://streethosting.com.br/guias/vps/configurar-pm2-startup-systemd-vps).

## Usuário de deploy e chave dedicada

O pipeline nunca deve entrar como root. Crie um usuário próprio, sem senha, que só aceita login por chave, e dê a ele a pasta da aplicação:

```
sudo adduser --disabled-password --gecos "" deploy
sudo mkdir -p /srv/minha-app
sudo chown deploy:deploy /srv/minha-app
```

No seu computador, gere um par de chaves exclusivo para o GitHub. Não reaproveite a chave que você usa no dia a dia: se algum dia ela vazar pelo pipeline, você revoga só ela.

```
ssh-keygen -t ed25519 -C "github-actions-deploy" -f ./deploy_github -N ""
```

O comando cria `deploy_github` (privada, vai para o GitHub) e `deploy_github.pub` (pública, vai para a VPS). Instale a pública no usuário de deploy:

```
sudo mkdir -p /home/deploy/.ssh
sudo nano /home/deploy/.ssh/authorized_keys   # cole o conteúdo de deploy_github.pub
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keys
```

Teste do seu computador com `ssh -i ./deploy_github deploy@IP_DA_VPS`. Se o conceito de par de chaves ainda é novo, o guia de [chave SSH sem senha na VPS](https://streethosting.com.br/guias/vps/configurar-chave-ssh-sem-senha-vps) explica a parte pública e a privada, e o de [usuário com sudo na VPS Linux](https://streethosting.com.br/guias/vps/criar-usuario-sudo-vps-linux) mostra como tirar o root do uso diário.

> **Atenção**
>
> O login por senha precisa estar desativado no SSH. Um usuário de deploy que aceita senha é alvo tão bom quanto o root. Com chave obrigatória e [Fail2ban protegendo o SSH](https://streethosting.com.br/guias/vps/configurar-fail2ban-ssh-vps), a porta pode ficar aberta para os runners do GitHub, que usam faixas de IP amplas e variáveis, difíceis de listar no firewall.

## Secrets do repositório

Secrets ficam criptografados no GitHub, só chegam ao workflow e aparecem mascarados nos logs. Cadastre os quatro em Settings, Secrets and variables, Actions. Se for usar um environment de produção, como mostra a seção mais abaixo, cadastre dentro dele.

| Secret | Conteúdo | Como obter |
| --- | --- | --- |
| DEPLOY_HOST | IP ou domínio da VPS | Painel da VPS ou registro A do domínio |
| DEPLOY_USER | Nome do usuário de deploy | O usuário criado na etapa anterior |
| DEPLOY_SSH_KEY | Chave privada inteira, com as linhas BEGIN e END | Conteúdo do arquivo deploy_github |
| DEPLOY_KNOWN_HOSTS | Chave pública do servidor SSH da VPS | Saída do keyscan abaixo, conferida na própria VPS |

O known_hosts existe para evitar um atalho perigoso muito comum: desligar a verificação de host com `StrictHostKeyChecking=no`. Sem essa checagem, qualquer máquina que se passe pela sua VPS recebe o código e a sessão. Gere a linha a partir de uma rede confiável:

```
# no seu computador: coleta a chave do servidor e mostra a impressão digital
ssh-keyscan -t ed25519 IP_DA_VPS
ssh-keyscan -t ed25519 IP_DA_VPS | ssh-keygen -lf -

# dentro da VPS: a impressão digital verdadeira
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
```

Se as duas impressões baterem, cole a linha que o primeiro comando imprimiu no secret. Com SSH em outra porta, acrescente `-p 2222` ao keyscan, porque a linha gravada inclui a porta.

> **Dica**
>
> Host e usuário não são segredos de verdade e poderiam ficar em variables, que aparecem legíveis na interface. Manter os quatro em secrets também funciona e deixa tudo no mesmo lugar.

## O workflow completo

Crie o arquivo `.github/workflows/deploy.yml` no repositório:

```
name: Deploy produção

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read

concurrency:
  group: deploy-producao
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: producao
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v7

      - uses: actions/setup-node@v7
        with:
          node-version: 24
          cache: npm

      - name: Instalar, testar e compilar
        run: |
          npm ci
          npm run test --if-present
          npm run build

      - name: Carregar chave SSH
        env:
          SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          KNOWN_HOSTS: ${{ secrets.DEPLOY_KNOWN_HOSTS }}
        run: |
          install -m 700 -d ~/.ssh
          printf '%s\n' "$SSH_KEY" > ~/.ssh/deploy_key
          chmod 600 ~/.ssh/deploy_key
          printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts

      - name: Enviar arquivos
        env:
          DEST: ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}
        run: |
          rsync -az --delete \
            --exclude ".git" --exclude "node_modules" --exclude ".env" \
            -e "ssh -i ~/.ssh/deploy_key" \
            ./ "$DEST:/srv/minha-app/"

      - name: Reiniciar a aplicação
        env:
          DEST: ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}
        run: |
          ssh -i ~/.ssh/deploy_key "$DEST" \
            "cd /srv/minha-app && npm ci --omit=dev && sudo systemctl restart minha-app.service"
```

- **on:** dispara em cada push na main e libera um botão para rodar à mão pela aba Actions, útil para republicar sem commit novo.
- **permissions:** o token automático do workflow fica só com leitura do código. O deploy não precisa escrever nada no repositório.
- **Carregar chave SSH:** os secrets entram por variável de ambiente em vez de interpolados direto no script, o que impede que um caractere especial no valor quebre ou altere o comando.
- **rsync com delete:** remove do servidor o que saiu do repositório. As exclusões protegem o `.env` de produção e a pasta de dependências de serem apagados. Detalhes das opções em [transferir arquivos com SCP e rsync](https://streethosting.com.br/guias/vps/transferir-arquivos-vps-scp-rsync).
- **Reiniciar:** instala só as dependências de produção e reinicia o serviço. Se qualquer comando falhar, o job fica vermelho e você recebe o aviso do GitHub.

> **Dica**
>
> As versões `@v7` das actions de checkout e de Node eram as mais recentes em setembro de 2026. Confira o README de cada uma antes de copiar e use no runner a mesma versão do Node instalada na VPS. Para um projeto Next.js, o fluxo é o mesmo; os detalhes do build estão em [hospedar Next.js em uma VPS](https://streethosting.com.br/guias/vps/hospedar-nextjs-vps).

## Reiniciar sem dar root ao pipeline

Reiniciar um serviço do systemd pede privilégio. A saída errada é colocar o usuário de deploy no grupo sudo, o que transforma a chave do GitHub em chave de administrador. A saída certa é liberar exatamente um comando, sem senha, em um arquivo próprio do sudoers:

```
sudo visudo -f /etc/sudoers.d/deploy-minha-app

# conteúdo do arquivo, uma linha:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart minha-app.service
```

O visudo valida a sintaxe antes de salvar, o que evita um sudoers quebrado que trava todo mundo fora do sudo. Confira o resultado com `sudo -l -U deploy`. A regra só vale para o comando idêntico: com o sufixo `.service` funciona, sem ele não, e qualquer outro comando pede uma senha que o usuário de deploy nem tem.

O serviço fica em `/etc/systemd/system/minha-app.service`. Ajuste o caminho do Node e do arquivo de entrada ao seu projeto:

```
[Unit]
Description=Minha aplicação Node
After=network.target

[Service]
User=deploy
WorkingDirectory=/srv/minha-app
Environment=NODE_ENV=production
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure

[Install]
WantedBy=multi-user.target
```

Ative uma vez com `sudo systemctl daemon-reload && sudo systemctl enable --now minha-app.service`. A partir daí, quem reinicia é o pipeline.

### Alternativa com PM2

Se a aplicação roda no PM2 sob o próprio usuário deploy, nenhuma regra de sudo é necessária: o último passo vira `pm2 reload minha-app`. Em modo cluster, o reload troca os processos um a um sem derrubar conexões; em modo fork, ele se comporta como um restart. Um administrador configura uma vez o `pm2 startup` e o `pm2 save` para os processos voltarem depois de um reboot.

## Environments e concurrency

Dois recursos do GitHub transformam um script de deploy em um processo previsível.

- **Environment:** o job declara `environment: producao`. Os secrets de deploy podem morar dentro do environment, e aí só jobs que o declaram os enxergam. Dá para restringir quais branches publicam nele e, conforme o plano da conta, exigir a aprovação de uma pessoa antes do job começar. Se o environment não existe, a primeira execução o cria sem regra nenhuma; configure em Settings, Environments.
- **Concurrency:** com o grupo `deploy-producao`, só um deploy roda por vez. Se dois pushes chegam em sequência, o segundo espera o primeiro terminar, em vez de dois rsync escreverem na mesma pasta e dois reinícios se atropelarem.

No comportamento padrão, o grupo mantém no máximo um run pendente. Se três pushes chegam enquanto um deploy roda, o do meio é cancelado e só o mais recente segue, o que para deploy costuma ser o desejado, já que ele contém os commits anteriores. Se você precisa publicar cada commit em ordem, acrescente `queue: max` ao bloco. Evite `cancel-in-progress: true` em produção: ele interrompe um envio ou um reinício pela metade.

> **Atenção**
>
> Em repositório privado, environments dependem do plano da conta no GitHub: no plano gratuito, eles só existem em repositório público, e regras como aprovação obrigatória têm disponibilidade ainda mais restrita. Confira na [documentação de environments do GitHub](https://docs.github.com/en/actions/how-tos/deploy/configure-and-manage-deployments/manage-environments) antes de contar com uma regra específica.

## Erros comuns

Quando algo falha, o log do job aponta o passo exato. Estes são os tropeços que mais aparecem na primeira semana:

| Sintoma | Causa provável | Correção |
| --- | --- | --- |
| Host key verification failed | known_hosts vazio, errado ou gerado para outra porta | Gerar de novo com o keyscan, incluindo a porta, e conferir a impressão digital |
| Permission denied (publickey) | Chave pública ausente ou permissões erradas na pasta .ssh | Revisar authorized_keys, dono deploy e modos 700 e 600 |
| npm: command not found no passo remoto | Node instalado com nvm não carrega em sessão SSH não interativa | Instalar o Node por pacote do sistema ou chamar o binário pelo caminho completo |
| sudo: a password is required | Comando diferente do liberado no sudoers | Usar exatamente o mesmo caminho, verbo e nome do serviço |
| Aplicação sobe com código antigo | Serviço aponta para outra pasta ou o build saiu em pasta excluída | Conferir o destino do rsync e o WorkingDirectory do serviço |
| Uploads sumiram do servidor | rsync com delete sem exclusão para a pasta de dados | Excluir a pasta do envio ou guardar dados fora da pasta da aplicação |

Na VPS, `journalctl -u minha-app.service -n 50` mostra por que o serviço não subiu depois do reinício. Note também que este fluxo copia por cima da pasta que está rodando: durante os segundos do rsync, o disco tem uma mistura de arquivos velhos e novos. Como o reinício só acontece no fim, isso raramente causa problema. Para publicar em pasta nova, com health check e rollback automático, siga o guia de [como configurar CI/CD na VPS](https://streethosting.com.br/guias/vps/configurar-ci-cd-vps).

## Qual VPS usar

Com o build rodando no GitHub, a VPS só precisa de recursos para executar a aplicação e absorver o reinício. Para uma API ou site Node com banco na mesma máquina, 4 GB de RAM é um ponto de partida confortável; projetos pequenos cabem em 2 GB.

- **VPS Xeon E5-2680 v4:** 2 vCPU e 2 GB por R$ 23,00, ou 3 vCPU, 4 GB e 40 GB NVMe por R$ 40,00. Entrega mais vCPU por real, bom para várias aplicações e workers em paralelo.
- **VPS Ryzen 9 9950X:** 2 vCPU, 4 GB DDR5 e 40 GB NVMe por R$ 64,00, com clock de até 5,7 GHz. Vale quando o tempo de resposta de cada requisição pesa ou quando você pretende compilar na própria VPS com um runner self hosted.

As duas linhas ficam em São Paulo, com AntiDDoS incluso, acesso root e ativação em até 60 segundos depois do pagamento por Pix, boleto ou cartão. Se o projeto crescer, o upgrade é feito pelo painel e cobra só a diferença proporcional ao ciclo. Compare as configurações na [página de VPS da StreetHosting](https://streethosting.com.br/vps).

## Perguntas frequentes

### É seguro guardar a chave SSH nos secrets do GitHub?

É o caminho recomendado. Os secrets ficam criptografados, aparecem mascarados nos logs e não são entregues a workflows disparados por pull requests de forks. O restante do risco se controla na VPS: a chave deve ser exclusiva do deploy e pertencer a um usuário sem root, com sudo liberado só para o comando de reinício.

### Preciso liberar os IPs do GitHub no firewall da VPS?

Não é prático. Os runners hospedados pelo GitHub usam faixas de IP amplas que mudam com frequência. O usual é manter a porta SSH aberta com login apenas por chave e Fail2ban ativo. Se você precisa fechar a porta, use um runner self hosted na própria VPS, que só faz conexões de saída.

### Dá para fazer o build na VPS em vez do GitHub?

Dá: o workflow só conecta por SSH e roda git pull e o build no servidor. O preço é consumir CPU e RAM da VPS durante o build, o que pode deixar a aplicação lenta no mesmo momento, e manter no servidor uma credencial de leitura do repositório. Compilar no runner do GitHub costuma ser mais limpo.

### O que acontece se dois pushes chegarem ao mesmo tempo?

Com concurrency configurado, o segundo deploy espera o primeiro terminar. Por padrão só um run fica pendente: se chegarem vários, os intermediários são cancelados e o mais recente segue, já com todos os commits. Sem concurrency, dois envios e dois reinícios podem se sobrepor e deixar a pasta em estado misto.

### Como voltar para a versão anterior se o deploy quebrar?

No fluxo simples deste guia, rode de novo pela aba Actions o workflow de um commit anterior que funcionava, ou reverta o commit e faça push. Para voltar em segundos sem novo build, use releases em diretórios separados com um symlink apontando para a versão ativa, como mostra o guia de CI/CD na VPS.

## Guias relacionados

- [Como configurar CI/CD na VPS com deploy automático](https://streethosting.com.br/guias/vps/configurar-ci-cd-vps.md)
- [PM2 ou systemd: manter sua aplicação Node sempre online na VPS](https://streethosting.com.br/guias/vps/configurar-pm2-startup-systemd-vps.md)
- [Como criar um usuário com sudo na VPS Linux](https://streethosting.com.br/guias/vps/criar-usuario-sudo-vps-linux.md)
- [Como transferir arquivos para a VPS com SCP e rsync](https://streethosting.com.br/guias/vps/transferir-arquivos-vps-scp-rsync.md)
- [Como hospedar aplicação Node.js em VPS no Brasil](https://streethosting.com.br/guias/vps/hospedar-api-nodejs-vps-brasil.md)

## Produtos citados

- https://streethosting.com.br/vps
- https://streethosting.com.br/vps/xeon
- https://streethosting.com.br/vps/ryzen

## Dados estruturados

```json
[
  {
    "@context": "https://schema.org",
    "@type": [
      "Article",
      "TechArticle"
    ],
    "headline": "Deploy automático na VPS a cada push com GitHub Actions",
    "name": "Deploy com GitHub Actions na VPS: pipeline passo a passo",
    "abstract": "Cada push na branch principal pode compilar, enviar e reiniciar sua aplicação na VPS sem ninguém abrir o terminal. Veja como montar esse fluxo com um usuário de deploy sem root, secrets bem guardados e proteção contra deploys simultâneos.",
    "description": "Deploy com GitHub Actions na VPS: usuário sem root, chave SSH dedicada, secrets, envio por rsync, reinício da aplicação e concurrency contra conflito.",
    "datePublished": "2026-09-28",
    "dateModified": "2026-09-28",
    "author": {
      "@type": "Organization",
      "name": "Equipe StreetHosting"
    },
    "publisher": {
      "@type": "Organization",
      "name": "StreetHosting",
      "url": "https://streethosting.com.br"
    },
    "inLanguage": "pt-BR",
    "mainEntityOfPage": {
      "@type": "WebPage",
      "@id": "https://streethosting.com.br/guias/vps/deploy-github-actions-vps"
    }
  },
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "É seguro guardar a chave SSH nos secrets do GitHub?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "É o caminho recomendado. Os secrets ficam criptografados, aparecem mascarados nos logs e não são entregues a workflows disparados por pull requests de forks. O restante do risco se controla na VPS: a chave deve ser exclusiva do deploy e pertencer a um usuário sem root, com sudo liberado só para o comando de reinício."
        }
      },
      {
        "@type": "Question",
        "name": "Preciso liberar os IPs do GitHub no firewall da VPS?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Não é prático. Os runners hospedados pelo GitHub usam faixas de IP amplas que mudam com frequência. O usual é manter a porta SSH aberta com login apenas por chave e Fail2ban ativo. Se você precisa fechar a porta, use um runner self hosted na própria VPS, que só faz conexões de saída."
        }
      },
      {
        "@type": "Question",
        "name": "Dá para fazer o build na VPS em vez do GitHub?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Dá: o workflow só conecta por SSH e roda git pull e o build no servidor. O preço é consumir CPU e RAM da VPS durante o build, o que pode deixar a aplicação lenta no mesmo momento, e manter no servidor uma credencial de leitura do repositório. Compilar no runner do GitHub costuma ser mais limpo."
        }
      },
      {
        "@type": "Question",
        "name": "O que acontece se dois pushes chegarem ao mesmo tempo?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "Com concurrency configurado, o segundo deploy espera o primeiro terminar. Por padrão só um run fica pendente: se chegarem vários, os intermediários são cancelados e o mais recente segue, já com todos os commits. Sem concurrency, dois envios e dois reinícios podem se sobrepor e deixar a pasta em estado misto."
        }
      },
      {
        "@type": "Question",
        "name": "Como voltar para a versão anterior se o deploy quebrar?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "No fluxo simples deste guia, rode de novo pela aba Actions o workflow de um commit anterior que funcionava, ou reverta o commit e faça push. Para voltar em segundos sem novo build, use releases em diretórios separados com um symlink apontando para a versão ativa, como mostra o guia de CI/CD na VPS."
        }
      }
    ]
  }
]
```
