Como instalar runner self-hosted do GitHub Actions
Instale um runner self-hosted no Ubuntu 24.04 com usuário dedicado, checksum, config.sh e systemd. Veja também o risco de pull requests de forks.
O que um runner auto-hospedado do GitHub Actions faz
Um runner auto-hospedado do GitHub Actions é um programa que você instala na sua própria VPS. Ele solicita jobs ao GitHub e os executa no seu hardware. Você o registra em um repositório, instala-o como um serviço systemd e ele volta a funcionar após cada reinicialização. O GitHub agenda o job. Seu servidor executa o trabalho.
A CI (integração contínua) em uma máquina que você controla é útil por dois motivos. Os minutos de build deixam de ser contabilizados, e um job pode acessar recursos disponíveis somente na sua máquina, como um cache de build aquecido ou uma rede privada. O custo é a segurança. O runner executa tudo o que o arquivo de workflow determina, usando o usuário que você configurou. Portanto, um arquivo de workflow é execução remota de código por definição. Em um repositório privado, isso não é um problema, porque somente pessoas em quem você confia podem adicionar um arquivo. Em um repositório público, existe um risco real, e a seção sobre pull requests de forks explica o mecanismo.
Tudo abaixo usa Ubuntu 24.04 com o runner versão 2.336.0, a release atual em julho de 2026.
O que você precisa antes de começar
Comece com um VPS que tenha uma conta de administrador comum e sudo, no estado alcançado em os primeiros dez minutos em um VPS novo. Você não precisa abrir uma porta de entrada. O runner abre uma conexão HTTPS (protocolo seguro de transferência de hipertexto) de saída com o GitHub e a mantém aberta enquanto aguarda tarefas. Assim, o GitHub nunca se conecta ao seu servidor. Seu firewall pode continuar fechado para o mundo, e as tarefas ainda serão recebidas.
Você também precisa de permissões de administrador no repositório, porque o token de registro é exibido nas configurações do repositório.
Criar um usuário dedicado para o runner
Nunca execute o runner como root nem como seu próprio usuário administrador. Todo job herda as permissões do usuário do runner, portanto um workflow que chama sudo será executado com sucesso se o usuário do runner puder usar sudo. Crie um usuário sem privilégios que seja proprietário apenas do próprio diretório pessoal. Contas de usuário com privilégios mínimos em um VPS apresenta o padrão geral. Veja o caso específico.
sudo useradd -m -s /bin/bash gharunner
sudo passwd -l gharunner
sudo chmod 750 /home/gharunner
sudo install -d -m 700 -o gharunner -g gharunner /home/gharunner/actions-runnerpasswd -l bloqueia a senha, portanto ninguém pode fazer login como gharunner usando uma senha. O modo 700 no diretório do runner é importante porque o runner armazena suas credenciais nesse diretório em texto não criptografado, e um checkout pode conter código-fonte privado.
Verifique ambas as propriedades antes de continuar:
sudo passwd -S gharunner
sudo -l -U gharunnerpasswd -S imprime uma linha que começa com gharunner L, em que L significa que a senha está bloqueada. sudo -l -U gharunner deve responder com is not allowed to run sudo. Se imprimir uma lista de comandos permitidos, a conta está em um grupo sudo, e o isolamento que você acabou de criar foi perdido.
Baixe o runner e verifique o tarball
A partir daqui, trabalhe como o usuário runner.
sudo -iu gharunner
cd ~/actions-runner
RUNNER_VERSION=2.336.0
curl -fL -o actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
"https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz"Execute uname -m primeiro se não tiver certeza da arquitetura. x86_64 usa o arquivo linux-x64 acima. aarch64 usa actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz.
Agora verifique o que foi baixado. O SHA256 (algoritmo de hash seguro, de 256 bits) abaixo corresponde ao tarball x64 2.336.0. O GitHub exibe o valor da versão atual na página da versão e na tela New self-hosted runner. O valor muda a cada versão. Portanto, copie-o desses locais ao instalar outra versão.
echo "04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d actions-runner-linux-x64-2.336.0.tar.gz" | sha256sum -cUm download correto exibe uma linha:
actions-runner-linux-x64-2.336.0.tar.gz: OKUm arquivo truncado ou alterado exibe a falha e um aviso:
actions-runner-linux-x64-2.336.0.tar.gz: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchNão ignore essa verificação para que tar encontre o problema depois. Um arquivo incompleto falha com gzip: stdin: unexpected end of file e tar: Unexpected EOF in archive. Isso informa que o arquivo está corrompido, mas não se ele foi truncado ou substituído.
tar xzf ./actions-runner-linux-x64-2.336.0.tar.gz
lsO que o tarball contém e o que não contém
Após a extração, o diretório contém config.sh, run.sh, env.sh, safe_sleep.sh, bin/ e externals/. bin/ contém os binários do runner e bin/installdependencies.sh. externals/ contém o runtime Node incluído, no qual as ações JavaScript são executadas.
Ainda não existe svc.sh. A documentação do GitHub o descreve como o script "que é criado depois que o runner é adicionado com sucesso", porque ele é gravado a partir de um template com o repositório e o nome do runner incorporados ao nome do serviço. Portanto, sudo ./svc.sh install antes de ./config.sh falha com sudo: ./svc.sh: command not found. Registre primeiro e depois instale o serviço.
Instale as dependências do runner
O runner é um aplicativo .NET, portanto precisa de algumas bibliotecas compartilhadas. Mantenha o shell do usuário do runner e instale-as com sudo, porque o script grava no banco de dados de pacotes do sistema.
exit
cd /home/gharunner/actions-runner
sudo ./bin/installdependencies.shNo Ubuntu 24.04, isso instala libkrb5-3, zlib1g, liblttng-ust1t64, libssl3t64 e libicu74. O script tenta vários nomes de versão para cada biblioteca e mantém aquele fornecido pela sua versão, por isso o mesmo script funciona em versões mais antigas do Ubuntu e no Debian.
Se você ignorar esta etapa, ./config.sh será interrompido antes de fazer qualquer coisa:
Dependencies is missing for Dotnet Core 6.0
Execute sudo ./bin/installdependencies.sh to install any missing Dotnet Core 6.0 dependencies.A ausência de libicu produz a mesma recomendação com uma primeira linha diferente, Libicu's dependencies is missing for Dotnet Core 6.0. Ambos vêm do mesmo local: config.sh executa ldd nas bibliotecas incluídas antes de iniciar, portanto um link não resolvido interrompe o script em vez de causar uma falha confusa posteriormente.
Registre o runner no repositório
Obtenha um token no repositório. Abra Settings, depois Actions, Runners e New self-hosted runner. A página mostra um token de registro que começa com A. Ele expira uma hora depois de ser criado. Gere-o quando estiver pronto para colá-lo.
Faça o registro como o usuário do runner. config.sh se recusa a ser executado com sudo.
sudo -iu gharunner
cd ~/actions-runner
./config.sh --url https://github.com/YOUR-USER/YOUR-REPO \
--token PASTE_REGISTRATION_TOKEN_HERE \
--name vps-runner-1 \
--labels vps \
--work _work \
--unattended \
--replaceO que esses flags fazem. --name define como o runner aparece no repositório. Escolha um nome que você ainda reconheça daqui a seis meses. --labels adiciona seus próprios labels. O runner já recebe self-hosted, Linux e X64 automaticamente. --work define o nome do diretório onde os checkouts são armazenados, dentro do diretório do runner. --unattended responde aos prompts interativos com os valores padrão. Isso é o que você quer quando o comando fica em um script. --replace assume um registro existente com o mesmo nome em vez de falhar. Isso é o que você quer ao recriar o servidor.
Uma execução bem-sucedida termina com estas linhas:
√ Runner successfully added
√ Runner connection is good
√ Settings Saved.O registro agora está no diretório do runner como .runner, .credentials e .credentials_rsaparams. Os dois últimos identificam este runner no GitHub. Qualquer pessoa que possa lê-los pode se passar por ele. Por isso, o diretório tem o modo 700 e o usuário não tem acesso a sudo.
Instalar o runner como um serviço systemd
./run.sh em um terminal funciona para um teste, mas ele é encerrado com a sua sessão SSH. Instale o serviço para que o runner seja iniciado na inicialização do sistema. serviços e timers do systemd em um VPS explica os próprios arquivos de unidade. Aqui, svc.sh cria um arquivo para você.
exit
cd /home/gharunner/actions-runner
sudo ./svc.sh install gharunner
sudo ./svc.sh start
sudo ./svc.sh statussvc.sh requer root porque grava uma unidade em /etc/systemd/system e a habilita. O argumento depois de install é o usuário como o qual o serviço é executado. Passe gharunner explicitamente. Sem um argumento, o script usa $SUDO_USER, que é a sua conta administrativa. Nesse caso, todos os jobs são executados como um usuário que pode usar sudo.
A unidade recebe o nome do repositório e do runner, no formato actions.runner.YOUR-USER-YOUR-REPO.vps-runner-1.service. Você não precisa digitar esse nome:
systemctl list-units 'actions.runner.*'
sudo journalctl -u 'actions.runner.*' -n 20 --no-pagerUm runner em funcionamento registra √ Connected to GitHub e depois uma linha terminada em Listening for Jobs. A página Runners do repositório o mostra como Idle. Um runner exibido como Offline não está em execução ou não consegue acessar o GitHub pela porta 443.
Enviar um job para o runner
runs-on seleciona um runner por rótulo. Solicite self-hosted e também seu próprio rótulo, para que um job não seja executado em um runner diferente do planejado.
name: build
on:
push:
branches: [main]
jobs:
build:
runs-on: [self-hosted, linux, vps]
steps:
- uses: actions/checkout@v5
- run: uname -aSe o job ficar aguardando em Waiting for a runner to pick up this job, os rótulos não correspondem. Cada rótulo em runs-on deve existir no runner. Uma palavra adicional deixa o job na fila sem gerar erro. Compare a lista com os rótulos exibidos ao lado do runner nas configurações do repositório.
Por que self-hosted runners e repositórios públicos não combinam
Esta é a parte que muitas pessoas ignoram. A orientação do GitHub é direta: self-hosted runners "quase nunca devem ser usados em repositórios públicos" e "não oferecem garantias de execução em máquinas virtuais efêmeras e limpas. Além disso, podem ser comprometidos de forma persistente por código não confiável em um workflow".
O mecanismo é simples. Um pull request de um fork traz sua própria cópia do arquivo de workflow. Se o repositório público executar workflows de pull request no seu runner, qualquer pessoa que possa criar um fork do repositório poderá propor um workflow que execute comandos na sua VPS. Não é necessário ter acesso de gravação, porque aquilo que a pessoa está propondo é justamente o que será executado.
As configurações de aprovação reduzem o risco, mas não o eliminam. A política padrão de um repositório público solicita que um mantenedor aprove o workflow de fork de um contribuidor pela primeira vez. Depois que você aprova essa pessoa uma vez, os pull requests seguintes dela são executados sem um novo aviso. Portanto, a proteção depende de uma pessoa ler um diff todas as vezes. É fácil não perceber um payload escondido três níveis abaixo em um script de build.
Um pull request de um fork não recebe seus secrets, e o GITHUB_TOKEN é somente leitura. Isso limita os danos dentro do GitHub. Não protege o servidor. O invasor obtém um shell como gharunner. Assim, pode ler todos os arquivos que esse usuário pode ler, acessar qualquer recurso que a VPS possa acessar na rede privada e deixar algo em ~/.bashrc ou em uma unidade systemd do usuário que seja executada durante o próximo job.
O registro com --ephemeral faz o runner aceitar um job e depois cancelar o registro. Assim, um job não pode ler o workspace do job seguinte. Isso só ajuda se algo recriar a máquina ou o container para cada job, porque um backdoor gravado no diretório pessoal do usuário do runner sobrevive a um novo registro.
As regras seguintes são curtas. Use self-hosted runners em repositórios privados. Se precisar conectar um a um repositório público, não execute pull requests de forks nele, não mantenha mais nada nesse servidor e trate a máquina como descartável.
Tarefas do Docker e o grupo que equivale a root
Tarefas de container, containers de serviço e qualquer etapa do workflow que chame docker build precisam de um daemon do Docker no host do runner. Instale o Docker da forma usual, conforme descrito em Docker e Docker Compose em um VPS, e adicione o usuário do runner ao grupo docker.
Entenda o impacto antes de fazer isso. A associação ao grupo docker equivale a root, porque um container pode montar / com bind mount e ser executado como root dentro dele. Portanto, um workflow que consiga acessar o socket do Docker pode ler e gravar todos os arquivos do VPS, incluindo /etc/shadow. Em um repositório privado com colaboradores confiáveis, esse pode ser um custo aceitável. Em qualquer outro contexto, isso elimina a finalidade do usuário sem privilégios. O Docker rootless mantém os builds dos containers dentro das permissões do próprio usuário do runner, mas usa um driver de armazenamento mais lento e não permite containers privilegiados.
Atualizações e remoção correta do runner
Um runner auto-hospedado é atualizado por padrão. Ele detecta uma nova versão, substitui os próprios arquivos e reinicia o serviço. Portanto, normalmente, você não precisa fazer nada. ./config.sh --disableupdate desativa a atualização automática quando você precisa manter uma versão fixa. Depois disso, a atualização fica sob sua responsabilidade: a documentação do GitHub informa explicitamente que um runner configurado com --disableupdate precisa ser atualizado manualmente.
Uma atualização manual preserva o registro, porque .runner e .credentials não estão no arquivo tarball. Pare o serviço, baixe o novo tarball e verifique seu checksum como gharunner, extraia-o sobre o mesmo diretório com tar xzf e inicie o serviço novamente:
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh startPara remover o runner, desinstale primeiro o serviço e depois remova o registro. O token de remoção vem da mesma página Runners, no botão Remove do próprio runner.
cd /home/gharunner/actions-runner
sudo ./svc.sh stop
sudo ./svc.sh uninstall
sudo -iu gharunner
cd ~/actions-runner
./config.sh remove --token PASTE_REMOVAL_TOKEN_HEREExcluir o diretório sem remover o registro deixa o runner listado como Offline no repositório, porque o GitHub só sabe que ele foi removido quando o runner informa isso ou quando um administrador exclui a entrada manualmente.
Modos de falha e as mensagens exibidas
Must not run with sudo. config.sh exibe esta mensagem e encerra quando é executado como root. A verificação é intencional, porque arquivos pertencentes a root em _work interrompem todas as tarefas posteriores executadas como o usuário do serviço. Execute ./config.sh como gharunner. A variável RUNNER_ALLOW_RUNASROOT ignora a verificação, mas usá-la apenas adia a falha.
sudo: ./svc.sh: command not found. Você está no diretório correto. svc.sh ainda não existe, porque config.sh ainda não concluiu um registro. Registre o runner e instale o serviço.
Http response code: NotFound from 'POST https://api.github.com/actions/runner-registration'. O token não é um token de registro válido. Ele pode ter expirado, pois permanece válido por uma hora, ou pode ter sido colado um personal access token no lugar do token de registro da página Runners. Gere um token novo e cole-o novamente.
Dependencies is missing for Dotnet Core 6.0. Execute sudo ./bin/installdependencies.sh no diretório do runner como root e registre novamente.
Runner offline após uma reinicialização. Execute systemctl is-enabled 'actions.runner.*'. Se nada for listado, ./svc.sh install nunca foi executado. Nesse caso, o runner só existiu na sua sessão do terminal. Se a unidade estiver habilitada e o runner continuar offline, leia journalctl -u 'actions.runner.*' e verifique o HTTPS de saída.
O disco fica cheio. Checkouts, caches de build e imagens do Docker se acumulam em _work e no diretório pessoal do usuário do runner, e nada os remove automaticamente. Monitore du -sh /home/gharunner/actions-runner/_work e adicione uma limpeza agendada antes que o disco fique cheio.
FAQ
Por que sudo ./svc.sh install informa que o comando não foi encontrado?
Porque svc.sh não está no tarball do runner. Ele é gerado no diretório do runner quando ./config.sh termina o registro, usando o nome do repositório e do runner para criar o nome do serviço. Execute ./config.sh primeiro como o usuário do runner. Depois disso, sudo ./svc.sh install gharunner encontra o script e grava uma unidade chamada actions.runner.OWNER-REPO.RUNNER-NAME.service em /etc/systemd/system.
Preciso abrir uma porta no firewall para um runner auto-hospedado?
Não. O runner abre uma conexão HTTPS de saída com o GitHub e a mantém aberta enquanto aguarda jobs. Portanto, o GitHub nunca inicia uma conexão com seu VPS. Permita conexões de saída na porta 443 e mantenha fechadas as regras de entrada. Se o runner aparecer como Offline enquanto o serviço estiver em execução, verifique a filtragem de saída e o DNS, não as regras de entrada.
Posso usar um runner auto-hospedado em um repositório público?
Pode, mas o GitHub não recomenda isso. Um pull request de um fork contém seu próprio arquivo de workflow. Assim, qualquer pessoa que possa criar um fork do seu repositório pode propor comandos que serão executados na sua máquina. O prompt de aprovação abrange apenas a primeira execução de um contribuidor. Se você associar um runner a um repositório público, desative nele os workflows de pull request de forks, não mantenha outros dados nesse servidor e recrie a máquina regularmente.
Por que o registro falha com Http response code: NotFound?
A chamada de registro retorna NotFound quando a credencial está incorreta, e não apenas quando a URL está incorreta. Isso torna a mensagem enganosa. Os tokens de registro expiram uma hora depois de serem exibidos, e um personal access token não é aceito nessa chamada. Abra novamente Settings, Actions, Runners, New self-hosted runner, copie o token novo e confirme que o valor de --url aponta para um repositório no qual você tem permissões de administrador.