Jobs agendados
Jobs agendados executam uma rotina recorrente sem que você precise manter uma aplicação ativa o tempo todo. Cada execução tem horário programado, status, logs, métricas opcionais e cobrança calculada separadamente do tempo de parede.
Como funciona
- Horário em UTC: use uma expressão
cronde exatamente cinco campos, sem segundos. O horário configurado é sempre interpretado em UTC. - Execução sequencial: um projeto tem no máximo uma execução ativa. Se o próximo horário chegar enquanto outra execução estiver ativa, a ocorrência é perdida ou ignorada; ela não é enfileirada. Não existe promessa de executar todas as ocorrências atrasadas depois.
- Limite de duração: uma execução pode permanecer
runningaté terminar, ser cancelada ou alcançar 60 minutos. Ao alcançar o limite, termina comodeadline_exceeded. - Sem retry automático: uma falha encerra a execução atual. O produto não cria outra tentativa automática para a mesma ocorrência.
- Ciclo público: a API e o Console exibem o ciclo de cobrança atual. Registros internos de execução e métricas são retidos por até 90 dias. O uso materializado e os registros financeiros permanecem disponíveis para auditoria e não são apagados pelo reinício do ciclo público; não aplique a eles o limite de 90 dias de execução e métricas.
Fontes de execução: Docker/OCI e GitHub
Imagem Docker/OCI
Use uma imagem Docker/OCI pública ou privada quando o artefato já estiver pronto em um registry. Na API, informe a imagem em config.image, o cron em config.job.cron e, quando necessário, config.job.command e config.job.args. Variáveis de ambiente ficam em config.envs. Os objetos públicos storage e job concentram, respectivamente, o armazenamento e o contrato de execução.
O fluxo V1 do Console usa essa fonte: escolha Job agendado, um plano de Job, a imagem, o horário, as variáveis e o armazenamento. O Console não solicita porta, domínio, exposição ou instâncias porque um Job não é uma aplicação HTTP.
O CLI V1 também aceita somente uma imagem OCI pronta para Jobs. A configuração deve incluir explicitamente o array envs: use envs: [] quando não houver variáveis ou um array de objetos {name, value}; não omita o campo. Nesse fluxo, não informe github, job.command ou job.args; use a entrada padrão da imagem. Para definir comandos/argumentos ou usar um repositório GitHub, use a API avançada descrita abaixo.
Repositório GitHub
Na API avançada, substitua config.image por config.github para criar a partir de um repositório. Conecte a conta GitHub à Zenifra e informe repository_owner, repository_name e branch; runtime, version, start_command, pre_build_command, build_command e auto_deploy completam o fluxo de build conforme a origem escolhida.
A criação inicia um build. Consulte o status e os logs do build em Builds GitHub, usando project.logs.read para leitura e project.deploy.trigger para disparos manuais. Logs de build são diferentes dos logs de uma execução do Job. Depois de um build bem-sucedido, config.job.command e config.job.args continuam sendo a forma explícita de escolher o processo batch; se forem omitidos, os padrões da imagem são usados.
Processo, comando e argumentos
O processo executado pela imagem define o resultado da execução:
- código de saída
0significasucceeded; - qualquer código de saída diferente de zero significa
failed;1é apenas um exemplo comum, não o único código de falha; - quando
commandeargsnão são informados, a Zenifra mantém o entrypoint e oCMDdefinidos pela imagem; commandsubstitui o executável eargsfornece os argumentos. Se apenas um deles for informado, o padrão não informado continua vindo da imagem;- o status público da execução é a fonte de verdade. O DTO de execução não expõe o número bruto do código de saída.
Imagens de servidor web, como as que ficam ouvindo requisições, normalmente não terminam sozinhas. Sem um comando batch explícito, elas podem continuar running até o limite de 60 minutos e então virar deadline_exceeded. Para uma rotina recorrente, prefira um processo que conclua e saia.
Estados, cancelamento e deadline
Os status públicos são running, succeeded, failed, deadline_exceeded e cancelled.
running: o processo começou ou ainda está sendo observado;succeeded: o processo terminou com código0;failed: o processo terminou com qualquer código diferente de zero;deadline_exceeded: o processo não terminou antes de 60 minutos;cancelled: uma pessoa autorizada encerrou a execução.
Quem tiver project.job-run.cancel pode cancelar uma execução ativa. O cancelamento é idempotente: se a execução já estiver em estado terminal, a API apenas retorna esse estado. A interrupção primeiro permite até 30 segundos para encerramento gracioso; somente depois disso a limpeza forçada é solicitada. A cobrança de uma execução cancelada vai até o momento da solicitação de cancelamento; o tempo de encerramento gracioso não é cobrado. Se a execução já não existir mais no ambiente de execução quando for cancelada, ela é marcada como cancelled e cobrada somente até a última vez em que a Zenifra a viu em execução, nunca até o momento do cancelamento. Depois disso, o projeto pode ser excluído. Uma execução que nunca começou não gera uso.
O cancelamento afeta somente a execução ativa identificada pelo seu ID; ele não pausa o cron nem impede horários futuros. Para impedir novas ocorrências, pause o projeto. Uma solicitação bem-sucedida só é confirmada depois que a execução parou. Se a execução terminar enquanto a solicitação estiver em andamento, a API pode retornar o estado terminal observado em vez de cancelled. Execuções ativas mais antigas podem não ter suporte a cancelamento seguro; nesse caso, aguarde a execução terminar ou atingir o limite de 60 minutos. As próximas execuções têm suporte ao cancelamento seguro.
Se o cancelamento não puder ser feito com segurança, a API retorna 409 com JOB_RUN_CANCELLATION_UNAVAILABLE e a mensagem This execution cannot be cancelled safely. Wait for it to finish or reach its time limit.. Essa resposta não é sucesso e não confirma que a execução parou. Aguarde um estado terminal ou o limite de duração; a cobrança já materializada permanece conforme as regras da execução e o tempo efetivamente consumido.
Pelo CLI, cancele uma execução específica com uma destas formas:
zenifra project runs cancel --project <id> --run <id>
zenifra project runs cancel --project <id> --run <id> --jsonA operação não possui a opção --wait. Com --json, o CLI mantém os campos públicos atuais da execução, como ID, status, horários, duração, minutos faturados, plano, moeda e valor, e omite detalhes internos.
Exemplos batch
Os exemplos abaixo usam command e args para deixar o comportamento explícito. A mesma estrutura pode ser enviada dentro de config.job no POST /v1/project.
Sucesso
{
"command": ["/bin/sh", "-c"],
"args": ["printf 'relatorio pronto\\n'; exit 0"]
}Resultado esperado: succeeded, com logs contendo relatorio pronto.
Falha
{
"command": ["/bin/sh", "-c"],
"args": ["printf 'falha de validacao\\n' >&2; exit 1"]
}Resultado esperado: failed. O código 1 é somente um exemplo; qualquer código não zero tem o mesmo significado de produto.
Não terminante
{
"command": ["/bin/sh", "-c"],
"args": ["while true; do printf 'ainda executando\\n'; sleep 60; done"]
}Resultado esperado: running enquanto o processo estiver ativo; deadline_exceeded se ninguém cancelar antes de 60 minutos. Esse padrão é útil para testar o limite, mas não para uma rotina batch de produção.
Duração, cobrança e armazenamento
Duração da execução
duration_seconds representa o tempo de parede entre started_at e finished_at. A API apresenta esse valor em segundos inteiros, arredondando a fração para cima. started_at e finished_at são o início e o fim reais do container: o tempo de agendamento e de download da imagem não é cobrado, e uma execução cujo container nunca iniciou (por exemplo, imagem indisponível) não gera cobrança. Uma execução ativa pode não ter finished_at nem duração final.
Minutos faturados
billed_minutes é outra medida: a duração é arredondada para o próximo minuto inteiro, com mínimo de 1 minuto inteiro e máximo de 60. Os campos price_per_minute e amount estão em centavos de BRL; currency é brl e payment_mode é per_minute. Para exibir reais, divida o valor por 100 e as apresentações humanas podem usar até quatro casas decimais. O catálogo pode retornar frações de centavo: o valor terminal de cada execução é armazenado exato, sem arredondamento para cima (uma execução de R$ 0,0005 é guardada como amount: 0.05), com a tarifa vigente; mudanças futuras no catálogo não recalculam execuções antigas.
| Duração observada | duration_seconds | billed_minutes |
|---|---|---|
| 1 segundo | 1 | 1 |
| 59 segundos | 59 | 1 |
| 70 segundos | 70 | 2 |
| 60 minutos | 3.600 | 60 |
O valor da execução é billed_minutes × price_per_minute, armazenado sem arredondamento por execução. Assim, price_per_minute: 2 representa R$ 0,02 por minuto; uma execução de 70 segundos gera 2 minutos faturados e amount: 4. O custo do ciclo soma os valores persistidos das execuções terminais, não o preço atual do catálogo. Falhas, cancelamentos depois do início e deadline_exceeded cobram o tempo consumido. Uma execução ainda ativa não cria cobrança parcial, e uma execução que não começou não gera uso.
Reinício do ciclo
O histórico público, o custo total, a contagem de execuções, os minutos faturados, os logs e as métricas reiniciam juntos na data de cobrança. O reinício usa a data agendada independentemente da liquidação financeira. Cada execução pertence ao ciclo em que começou; se atravessar a virada, ela não muda de ciclo ao terminar. O Console mostra a próxima data de reinicialização e o custo terminal aparece quando o uso terminal é materializado, antes da liquidação. A API exclusiva GET /v1/project/:id/job-runs/cost-summary exige project.billing.read e retorna total_amount, executed_runs, billed_minutes, cycle_started_at e next_reset_at; o CLI não tem comando equivalente. Os valores armazenados das execuções antigas permanecem internamente para auditoria, idempotência e cobrança; não são apagados.
Armazenamento
O armazenamento efêmero é criado para a execução e fica disponível em /data; os dados não são preservados para a próxima execução. Para manter arquivos entre execuções, use storage.persistent: true, informe capacity e defina dir_path_to_persist, como /data ou /reports. O armazenamento persistente é cobrado separadamente por GB-hora enquanto o projeto existir, inclusive pausado, até a exclusão; armazenamento efêmero não adiciona cobrança de armazenamento.
O preço pode mudar no catálogo. Leia price_per_minute, currency, payment_mode e as permissões do plano antes de apresentar uma estimativa. Se Jobs estiver desabilitado, a API retorna 503 com SCHEDULED_JOBS_UNAVAILABLE.
Logs, métricas e histórico
Logs da execução
Abra uma execução no Console ou consulte GET /v1/project/:id/job-runs/:runId/logs. A resposta associa o texto à execução solicitada e limita o corpo de logs a 50 KiB por resposta. A permissão necessária é project.logs.read. Use os logs da execução para entender o processo batch; para um repositório GitHub, consulte separadamente os logs do build.
Métricas por execução
Consulte GET /v1/project/:id/job-runs/:runId/metrics com project.metrics.read. O endpoint é limitado ao projeto e à organização autorizados e retorna somente o DTO público da execução:
{
"run_id": "507f1f77bcf86cd799439011",
"status": "collecting",
"sampled_at": "2026-09-01T03:00:12.000Z",
"window": {
"started_at": "2026-09-01T03:00:02.000Z"
},
"cpu": {
"latest_cores": 0.3
},
"memory": {
"latest_bytes": 16777216
},
"samples": 2
}Enquanto a execução está ativa, status: collecting mostra os últimos valores observados; a atualização normalmente ocorre em torno de 10 segundos, então uma execução menor que esse intervalo pode não ter amostra. Depois do término, status: available apresenta médias e picos de CPU e memória. status: unavailable significa que não houve dados suficientes, que a execução foi curta ou que a fonte de métricas não estava disponível. Campos desconhecidos são omitidos: a API nunca transforma ausência em zero fabricado.
Histórico e retenção
Os registros de execução e os resumos de métricas permanecem internamente por até 90 dias. O uso materializado e os registros financeiros permanecem disponíveis para auditoria e não são apagados pelo reinício do ciclo público; não aplique a eles o limite de 90 dias de execução e métricas. A API e o Console expõem somente o ciclo de cobrança atual. A retenção interna do Job é independente do histórico de builds GitHub. Uma execução terminal pode continuar armazenada mesmo quando as métricas ficaram unavailable.
Limpeza e troubleshooting
Excluir um Job
DELETE /v1/project/:id interrompe novos horários antes de remover o projeto. Se houver execução ativa, execução ainda não observada ou cobrança ainda não registrada, a API retorna 409 com JOB_RUNS_PENDING. Nesse caso:
- cancele a execução ativa com
POST /v1/project/:id/job-runs/:runId/cancel, se tiverproject.job-run.cancel; - aguarde o histórico mostrar status terminal e os campos de cobrança;
- tente a exclusão novamente.
Não repita a exclusão em loop enquanto JOB_RUNS_PENDING continuar sendo retornado. A limpeza de uma execução não altera a cobrança já materializada.
JOB_RUNS_PENDING pertence à exclusão do projeto; ele não é o erro de um cancelamento. Uma tentativa de cancelar sem suporte seguro retorna JOB_RUN_CANCELLATION_UNAVAILABLE, e a execução deve terminar ou atingir o limite de 60 minutos.
Diagnóstico rápido
| Sintoma | Verificação |
|---|---|
failed | Leia os logs, confirme o caminho do executável e procure um código de saída não zero. |
deadline_exceeded | O processo não saiu em 60 minutos; use command/args para chamar uma rotina finita ou cancele a execução. |
Métricas unavailable | A execução pode ter sido menor que a primeira amostra ou a fonte de métricas pode estar indisponível; não interprete como CPU ou memória zero. |
| Nenhuma nova execução | Confirme o cron em UTC e verifique se já existe uma execução ativa; a ocorrência perdida não é enfileirada. |
| Falha antes de uma execução GitHub | Consulte o build e seus logs em Builds GitHub; build e execução têm históricos diferentes. |
409 JOB_RUN_CANCELLATION_UNAVAILABLE | A execução não pode ser cancelada com segurança; aguarde o estado terminal ou o limite de 60 minutos. Não trate a resposta como sucesso ou confirmação de parada. |
409 JOB_RUNS_PENDING ao excluir | Cancele quando autorizado, aguarde a reconciliação do histórico e da cobrança e tente uma vez mais. |
Próximos passos
Última atualização em