BLOG/ENTERPRISE AUTOMATION
THEME
ENTERPRISE AUTOMATION

Onboarding and offboarding automation

Provisioning and deprovisioning people across SSO, groups, and hardware — without the half-applied state a failed script leaves behind. Build idempotent onboard/offboard tasks in OrchStep.

Apr 25, 2026 OrchStep Team 6 minROLE: Platform EngineerSCALE: Enterprise
RUNNABLE DEMO
Full source for this post: blog/onboarding-offboarding
VIEW SOURCE

Onboarding a new hire touches half a dozen systems: SSO account, group memberships, a laptop, a welcome checklist. Offboarding undoes all of it. Most teams run this from a script, and the script has one nasty property — when it fails on step four, it leaves the person half-provisioned, and re-running it either errors out or double-creates whatever step one already did.

The cure is idempotency: every step is phrased so running it twice is a no-op. "Ensure the account exists," not "create the account." Then a failed run is just a re-run, and a partially-offboarded person is one more orchstep run offboard away from clean. This post builds both tasks that way.

Two tasks, one file

onboard and offboard live in the same workflow — the systems they touch are the same, just in reverse. Each step "ensures" a state rather than performing a one-shot action.

orchstep.yml
name: identity
# Provision (onboard) and deprovision (offboard) a person across systems.
# Every step is phrased to be idempotent: running it twice is a no-op.
defaults:
  person: jordan

tasks:
  # `orchstep run onboard --var person=jordan`
  onboard:
    steps:
      - name: account
        func: shell
        do: echo "ensuring an SSO account exists for {{ vars.person }}"
      - name: groups
        func: shell
        do: echo "ensuring {{ vars.person }} is in the engineering groups"
      - name: laptop
        func: shell
        do: echo "ensuring a laptop is assigned to {{ vars.person }}"
      - name: welcome
        func: shell
        do: echo "{{ vars.person }} onboarded — sending the welcome checklist"

  # `orchstep run offboard --var person=jordan`
  offboard:
    steps:
      - name: revoke
        func: shell
        do: echo "ensuring {{ vars.person }}'s SSO sessions are revoked"
      - name: groups
        func: shell
        do: echo "ensuring {{ vars.person }} is removed from all groups"
      - name: archive
        func: shell
        do: echo "ensuring {{ vars.person }}'s mailbox is archived"
        finally:
          - name: record
            func: shell
            do: echo "offboarding of {{ vars.person }} recorded for audit"

Every do: here is echo-only so the demo runs anywhere. In your version each step shells out to the real API — Okta, your IdP, an MDM endpoint — but the phrasing is the contract: each call is a "make it so," safe to repeat.

Why idempotent steps change the failure story

A one-shot script and an idempotent workflow diverge the moment something fails halfway:

ONE-SHOT SCRIPT
create_sso_account "$USER"      # already ran last time -> errors "exists"
add_to_groups "$USER"
assign_laptop "$USER"           # this is where it died
send_welcome "$USER"
# re-run? step 1 now fails, and you're stuck editing the script by hand
IDEMPOTENT TASK
- name: account
  do: echo "ensuring an SSO account exists for {{ vars.person }}"
- name: groups
  do: echo "ensuring {{ vars.person }} is in the engineering groups"
- name: laptop
  do: echo "ensuring a laptop is assigned to {{ vars.person }}"
# re-run? every step is a no-op until it reaches the one that failed

With the idempotent version, recovery is the same command you ran the first time. No "where did it stop" archaeology, no commenting out the steps that already succeeded. The offboard task adds a finally: so the audit record is written even if a deprovisioning step fails — because the one thing you can't afford to lose is the record that you tried to offboard someone.

Preview before you provision

Onboarding touches real accounts, so seeing the plan first matters. A dry run resolves the person and prints every step without calling a single API:

orchstep run onboard --var person=jordan --dry-run

Swap the name, read the plan, then run it for real. More: Previewing with Dry Run.

What you gained

Concernone-shot scriptOrchStep
Failed halfwaymanual surgeryre-run the same command
Onboard + offboardtwo scriptstwo tasks, one file
Audit on failurelost if it crashedfinally: record step
Who to provisionedit the script--var person=

If your team adds one person a quarter, a checklist is fine. At enterprise hiring volume, the half-applied state is the expensive part — idempotent steps make it cheap.

Where to go next

Got an onboarding script that breaks when you re-run it? Phrase every step as "ensure," and recovery becomes a repeat.

#PLATFORM#ONBOARDING#IDEMPOTENT#AUTOMATION
Try it in two minutes — one binary, no signup.
curl -fsSL https://orchstep.dev/install.sh | sh

RELATED — ENTERPRISE AUTOMATION