Jenkins, for the jobs that are not builds

Keep Jenkins for builds.
Move the 3 a.m. jobs.

This is not a pitch to rip out Jenkins. Jenkins is a build system and it is good at that. But most long-lived Jenkins installs have grown a second population of jobs that were never builds: a freestyle job holding a shell script on a timer. Database backups. Certificate rotation. Log pruning. The monthly report. They ended up in Jenkins because Jenkins was already there and already had cron. SwitchTender takes that half, with one command, and gives it the approvals, host history, and provable audit trail an operational change needs. Your pipelines stay where they are.

Why a backup script on a timer is a bad fit for a CI server

Not because Jenkins is bad. Because a build and a production change are different things, and only one of them is safe to re-run.

Nobody approved it and nothing recorded it

A build is meant to run on every commit without asking. A script that touches production is not. In Jenkins, a freestyle job runs when its timer fires, and what you keep afterward is console output on a retention policy. SwitchTender holds a run for approval where a policy covers it, records who launched it and what they changed, and writes the result into a hash-chained audit trail you can hand to an auditor and they can verify without trusting you or the server.

A shell script inherits the whole plugin tree

The job that runs backup.sh lives inside a JVM with a plugin set that has to be kept current for the builds around it. Every upgrade is a change to the thing your backup runs on. SwitchTender is one static Go binary and one SQLite file, so the schedule that matters at 3 a.m. is not downstream of a plugin bump made for a pipeline.

No memory of the machines

Jenkins remembers builds. Operational work needs the other axis: which host has failed the last four times, what changed on it, when it last came clean. SwitchTender keeps per-host history across every run, spots flaky hosts, and detects drift from a dry run, because the question is not "did the job pass" but "is that machine right."

One command reads your jobs directory

Jenkins has no single export file. Point the importer at the tree, or upload a zip of it, and it reads every job in place. Nothing is created until you ask.

switchtender import jenkins /var/jenkins_home --inventory prod

Folders are followed and each job keeps its full name. Shell steps become one Bash template in order. Parameters become a survey. Build triggers become schedules. Leave off --apply and it only reports what it would create.

The four things the importer refuses to guess

An importer that half-translates a job is worse than one that says it cannot. Each of these is named in the report and left out.

Pipeline jobs

A Jenkinsfile is a Groovy program. There is no honest mechanical translation from one into a template, so Pipeline, multibranch, matrix, and Maven jobs are skipped by name rather than imported as a shell that would not do what the job did. Those are usually your actual builds, and they should stay in Jenkins.

Poll SCM triggers

A poll trigger looks like a schedule and is not one: it asks the repository whether anything changed and builds only if it did, so a job polling every five minutes usually does nothing. Imported as a plain schedule it would run for real every five minutes. For a deploy job that is an incident, not a migration artifact.

Remote build tokens

A job's remote build token is a shared secret that would hand a launch to anybody holding it, so it is refused and named. A password parameter does come across, as a secret survey field whose answer is sealed the way Jenkins encrypted it. Its default is Jenkins ciphertext, so it stays behind and the report names it.

Jenkins build variables

A step reading $WORKSPACE or $BUILD_NUMBER gets an empty string here, and rm -rf $WORKSPACE/ with WORKSPACE unset is the worst version of that. Every occurrence is found and reported so you supply it or rewrite the step, rather than discovering it at 3 a.m.

Your H schedules survive the trip

Jenkins writes H where a number would go and hashes the job name to spread load, so H 2 * * * means "some minute past two, the same one every time." No cron parser accepts the letter, so a naive import drops every one of those jobs as invalid, and the job silently never runs again. Here they are resolved rather than approximated: the cadence and the window are kept, @daily and its family are expanded the way Jenkins expands them, a spec holding several lines becomes several schedules, and Sunday written as 7 is renumbered to 0 instead of being rejected. The report names the expression each one became.

Where each tool wins

Rows where Jenkins is the right answer are in this table too. It is a build server, and this is not one.

CapabilitySwitchTenderJenkins
Build on commit, per-branch pipelinesNot its job
Test reports and build artifactsNot its job
Plugin ecosystemDeliberately smallVery large
Runs without a JVM
One binary, no plugins to run a job
Approval gates enforced by policyPlugin
Tamper-evident, signed audit trail
Verifiable offline by a third party
Per-host fleet memory and flaky-host detection
Ansible, Terraform, and OpenTofu as first-class toolsVia plugins
Drift detection from a dry run
Imports the other tool's jobsAWX, Semaphore, Rundeck, Jenkins, crontab

Honesty first

Jenkins has two decades of production history, an ecosystem no new tool will match, and an enormous amount of institutional knowledge inside your company. If your freestyle jobs are working and nobody is asking who approved them, there is no reason to move them. This is worth your time when somebody starts asking that question: an audit, a compliance program, a change-control review, or a 3 a.m. job that did something nobody can now reconstruct. Move that subset, keep the pipelines, and run both. Nothing here asks you to choose.

See what it would import, in five minutes

The dry run creates nothing. It reads your jobs and prints exactly what it would make and what it refused.

  1. 1

    Install it

    brew install kordloom/tap/switchtender
  2. 2

    See what it would import

    switchtender import jenkins /var/jenkins_home --inventory prod
  3. 3

    Create the objects

    switchtender import jenkins /var/jenkins_home --inventory prod --apply

The migration guide covers every mapping and every refusal, and the get started guide has prebuilt binaries for Linux, macOS, and Windows with published checksums.

Common questions

The four asked most often when somebody is comparing this against what they already run.

Does it need an agent on each host?

No. It reaches the machines it manages over SSH, the same way Ansible does, and installs no agent on them. There is no per-host daemon to deploy, patch, or account for. You run the one server binary. Worker processes for extra throughput are a Team feature, and they are pool members rather than agents belonging to a host.

Can it read secrets from AWS Secrets Manager, Azure Key Vault, or Vault?

All three, resolved at launch rather than copied into this database. Vault dynamic secrets go further: a short-lived credential is minted for each run and revoked when the run ends. Azure authenticates from the attached managed identity with no stored key, AWS takes an access key from the credential or the standard AWS environment variables, and anything else resolves through a command whose output is the secret.

Can I run a Terraform plan, hold it for approval, then run Ansible?

Yes, and it is what pipelines are for here. Steps mix tools freely on a dependency graph with parallel branches, built on a drag-and-drop canvas, and an approval step holds the pipeline for a person before the steps after it run. A policy decides which runs are held, and an agent's run is held unless a written policy exempts it. When the approval has to bind an exact Terraform plan, run the apply on its own: it plans first, and the approval binds the saved plan, so it cannot be approved as one thing and executed as another. By default an admin may approve a run they launched. Requiring a different person is a Team rule.

What happens if KordLoom stops?

An install keeps running as it is. There is no license server and no phone-home, a paid license keeps verifying offline through its term, and your data and receipts stay yours, checked by an open verifier that does not depend on us. Every release converts to Apache 2.0 two years after it ships, and at once if KordLoom ceases business.

Let Jenkins build. Let this one answer for production.