API remota do runner

v2.161.0+. O runner pode expor uma API HTTP para disparar deploy, enviar sinais e consultar apps de fora do servidor. Ela roda como serviço do systemd, fica atrás do Traefik com TLS e exige X-API-Key em toda rota menos /health.

1. Criar e ligar o serviço

O modo server nasce desativado. Ligar é sempre um passo deliberado:

runner service init --enable-api   # grava service.yml e gera a API key; a unit fica DESATIVADA
runner service start               # liga: inicia agora e passa a subir no boot

O init grava /opt/runner/service.yml (permissão 0600) e instala a unit runner.service sem habilitá-la. Sem --enable-api, a API fica desligada e sem chave; para ligá-la depois, use runner service config --enable-api. A API escuta só em 127.0.0.1:9090.

Guarde num gerenciador de senhas a chave que o comando imprime.

2. Publicar num domínio

O DNS do domínio tem que apontar para este servidor.

runner service expose --domain runner.exemplo.com --vpn-only

O comando:

  1. detecta como o Traefik alcança o host e escolhe o endereço de escuta:

    • Traefik na rede do host: 127.0.0.1;
    • Traefik numa rede bridge: o gateway dessa rede.

    A API nunca escuta em 0.0.0.0.

  2. grava a rota runner-api.yml no diretório dinâmico do Traefik, com o certResolver das apps. Só /health e /api/v1/* são publicados;

  3. reinicia o serviço e espera https://runner.exemplo.com/health responder.

--vpn-only acrescenta o middleware vpn-only@file, e a API só responde para quem está na VPN. --middleware <nome> acrescenta outros middlewares.

Quando a API escuta no gateway de uma bridge, o expose grava o IP do container do Traefik em api.trusted_proxies. Só ele e o próprio host falam com a API. Outro container recebe 403 origem_nao_permitida, então ninguém contorna os middlewares falando direto com o gateway. Se o container do Traefik for recriado com outro IP, rode o expose de novo.

3. Chamar a API

curl -H "X-API-Key: {API_KEY}" https://runner.exemplo.com/api/v1/apps

Sem a chave, ou com a chave errada, a resposta é 401.

4. Disparar um deploy

curl -X POST -H "X-API-Key: {API_KEY}" -H "Content-Type: application/json" \
     -d '{"force": true}' https://runner.exemplo.com/api/v1/deploy/minha-app
# 202 {"deploy_id":"3f9a1c2b","status":"rodando","app":"minha-app","instance":"production"}

curl -H "X-API-Key: {API_KEY}" https://runner.exemplo.com/api/v1/deploy/3f9a1c2b
# 200 {"deploy_id":"3f9a1c2b","status":{"app":"minha-app","estado":"sucesso","versao":"1.4.0"}}
Resposta Quando
202 Deploy aceito; acompanhe pelo deploy_id
409 lock_contended Já há uma operação na app (cron, outro deploy). holder diz qual
404 App inexistente, ou deploy_id desconhecido
413 Corpo maior que 64 KiB. Atrás do Traefik pode chegar como 502
503 desligando O serviço está parando. Tente de novo em instantes

O status por deploy_id fica na memória do serviço e some num restart. O histórico durável é o runner logs history <app>.

Parar ou reiniciar o serviço

systemctl stop runner (e o restart que o expose e o config fazem) espera os deploys disparados pela API terminarem, por até 10 minutos. Nesse intervalo, deploy novo recebe 503.

5. Trocar a chave ou despublicar

runner service config --api-key   # gera chave nova; com o serviço ativo, reinicia para aplicar
runner service unexpose           # remove a rota; a API volta a escutar só em 127.0.0.1

Erros ao subir

Mensagem Causa
api_key_ausente API habilitada sem chave. Rode runner service init ou service config --api-key
api_key_formato Chave fora do formato rk_<8 hex>_<24 hex>
bind_invalido api.bind não é um endereço IP
mtls_incompleto mTLS ligado com arquivo de certificado faltando
mtls_atras_do_traefik expose com mTLS ligado: o Traefik termina o TLS
expose_sem_resposta O domínio não respondeu em 60 s. Confira DNS e certificado

Ver também

By Borlot.com.br on 22/10/2026